From 30b22e3bdb2e1a08c0ac40b2b47c0f39b89e486e Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 29 Jan 2026 15:57:16 +0530 Subject: [PATCH 01/33] feat(repo): flutter 3.38.1 (#2495) --- .github/dependabot.yml | 1 - .github/workflows/legacy_version_analyze.yml | 2 +- analysis_options.yaml | 5 +- melos.yaml | 6 +- packages/stream_chat/example/lib/main.dart | 196 +- packages/stream_chat/example/pubspec.yaml | 4 +- .../stream_chat/lib/src/client/channel.dart | 1117 +- .../src/client/channel_delivery_reporter.dart | 13 +- .../stream_chat/lib/src/client/client.dart | 954 +- .../lib/src/client/key_stroke_handler.dart | 16 +- .../lib/src/client/retry_policy.dart | 3 +- .../lib/src/client/retry_queue.dart | 16 +- .../core/api/attachment_file_uploader.dart | 7 +- .../lib/src/core/api/channel_api.dart | 6 +- .../lib/src/core/api/device_api.dart | 3 +- .../lib/src/core/api/general_api.dart | 8 +- .../lib/src/core/api/requests.dart | 70 +- .../lib/src/core/api/requests.g.dart | 128 +- .../lib/src/core/api/responses.dart | 159 +- .../lib/src/core/api/responses.g.dart | 715 +- .../lib/src/core/api/sort_order.dart | 13 +- .../lib/src/core/api/sort_order.g.dart | 10 +- .../lib/src/core/api/stream_chat_api.dart | 31 +- .../lib/src/core/api/user_api.dart | 3 +- .../lib/src/core/error/chat_error_code.dart | 46 +- .../lib/src/core/error/stream_chat_error.dart | 17 +- .../http/interceptor/logging_interceptor.dart | 43 +- .../src/core/http/stream_chat_dio_error.dart | 6 +- .../lib/src/core/http/stream_http_client.dart | 7 +- .../core/http/system_environment_manager.dart | 16 +- .../stream_chat/lib/src/core/http/token.dart | 8 +- .../lib/src/core/http/token_manager.dart | 6 +- .../lib/src/core/models/action.g.dart | 24 +- .../lib/src/core/models/attachment.dart | 183 +- .../lib/src/core/models/attachment.g.dart | 114 +- .../lib/src/core/models/attachment_file.dart | 48 +- .../src/core/models/attachment_file.g.dart | 73 +- .../core/models/attachment_giphy_info.dart | 3 +- .../lib/src/core/models/banned_user.dart | 38 +- .../lib/src/core/models/banned_user.g.dart | 43 +- .../lib/src/core/models/channel_config.dart | 7 +- .../lib/src/core/models/channel_config.g.dart | 105 +- .../lib/src/core/models/channel_model.dart | 110 +- .../lib/src/core/models/channel_model.g.dart | 71 +- .../lib/src/core/models/channel_mute.dart | 3 +- .../lib/src/core/models/channel_mute.g.dart | 29 +- .../lib/src/core/models/channel_state.dart | 32 +- .../lib/src/core/models/channel_state.g.dart | 91 +- .../lib/src/core/models/command.dart | 3 +- .../lib/src/core/models/command.g.dart | 16 +- .../lib/src/core/models/comparable_field.dart | 2 +- .../lib/src/core/models/device.g.dart | 12 +- .../lib/src/core/models/draft.dart | 16 +- .../lib/src/core/models/draft.g.dart | 44 +- .../lib/src/core/models/draft_message.dart | 41 +- .../lib/src/core/models/draft_message.g.dart | 77 +- .../lib/src/core/models/event.dart | 102 +- .../lib/src/core/models/event.g.dart | 218 +- .../lib/src/core/models/filter.dart | 26 +- .../lib/src/core/models/location.dart | 33 +- .../lib/src/core/models/location.g.dart | 45 +- .../lib/src/core/models/member.dart | 85 +- .../lib/src/core/models/member.g.dart | 87 +- .../lib/src/core/models/message.dart | 126 +- .../lib/src/core/models/message.g.dart | 185 +- .../src/core/models/message_delete_scope.dart | 3 +- .../core/models/message_delete_scope.g.dart | 26 +- .../src/core/models/message_delivery.g.dart | 9 +- .../lib/src/core/models/message_reminder.dart | 27 +- .../src/core/models/message_reminder.g.dart | 52 +- .../lib/src/core/models/message_state.dart | 57 +- .../lib/src/core/models/message_state.g.dart | 261 +- .../lib/src/core/models/moderation.dart | 19 +- .../lib/src/core/models/moderation.g.dart | 39 +- .../lib/src/core/models/mute.g.dart | 26 +- .../lib/src/core/models/own_user.dart | 66 +- .../lib/src/core/models/own_user.g.dart | 142 +- .../stream_chat/lib/src/core/models/poll.dart | 117 +- .../lib/src/core/models/poll.g.dart | 112 +- .../lib/src/core/models/poll_option.dart | 21 +- .../lib/src/core/models/poll_option.g.dart | 19 +- .../lib/src/core/models/poll_vote.dart | 58 +- .../lib/src/core/models/poll_vote.g.dart | 32 +- .../src/core/models/privacy_settings.g.dart | 91 +- .../lib/src/core/models/push_preference.dart | 6 +- .../src/core/models/push_preference.g.dart | 65 +- .../lib/src/core/models/reaction.dart | 84 +- .../lib/src/core/models/reaction.g.dart | 36 +- .../lib/src/core/models/reaction_group.dart | 17 +- .../lib/src/core/models/reaction_group.g.dart | 30 +- .../stream_chat/lib/src/core/models/read.dart | 17 +- .../lib/src/core/models/read.g.dart | 30 +- .../lib/src/core/models/thread.dart | 96 +- .../lib/src/core/models/thread.g.dart | 116 +- .../src/core/models/thread_participant.dart | 42 +- .../src/core/models/thread_participant.g.dart | 50 +- .../lib/src/core/models/unread_counts.dart | 9 +- .../lib/src/core/models/unread_counts.g.dart | 83 +- .../stream_chat/lib/src/core/models/user.dart | 92 +- .../lib/src/core/models/user.g.dart | 79 +- .../lib/src/core/models/user_block.dart | 30 +- .../lib/src/core/models/user_block.g.dart | 28 +- .../lib/src/core/util/extension.dart | 3 +- .../lib/src/core/util/serializer.dart | 10 +- .../stream_chat/lib/src/core/util/utils.dart | 3 +- .../lib/src/db/chat_persistence_client.dart | 90 +- packages/stream_chat/lib/src/event_type.dart | 21 +- .../lib/src/ws/connect_user_details.g.dart | 20 +- .../stream_chat/lib/src/ws/websocket.dart | 15 +- packages/stream_chat/pubspec.yaml | 2 +- .../channel_delivery_reporter_test.dart | 6 +- .../test/src/client/channel_test.dart | 2545 +-- .../test/src/client/client_test.dart | 1608 +- .../api/attachment_file_uploader_test.dart | 136 +- .../test/src/core/api/channel_api_test.dart | 532 +- .../test/src/core/api/device_api_test.dart | 79 +- .../test/src/core/api/general_api_test.dart | 96 +- .../test/src/core/api/guest_api_test.dart | 25 +- .../test/src/core/api/message_api_test.dart | 366 +- .../src/core/api/moderation_api_test.dart | 114 +- .../test/src/core/api/polls_api_test.dart | 188 +- .../test/src/core/api/reminders_api_test.dart | 89 +- .../test/src/core/api/responses_test.dart | 18 +- .../test/src/core/api/user_api_test.dart | 194 +- .../core/error/stream_chat_error_test.dart | 3 +- .../http/adapter/custom_adapter_test.dart | 11 +- .../interceptor/auth_interceptor_test.dart | 22 +- .../core/http/stream_http_client_test.dart | 307 +- .../src/core/http/token_manager_test.dart | 6 +- .../test/src/core/http/token_test.dart | 3 +- .../src/core/models/attachment_file_test.dart | 3 +- .../models/attachment_giphy_info_test.dart | 43 +- .../test/src/core/models/attachment_test.dart | 26 +- .../src/core/models/channel_state_test.dart | 15 +- .../src/core/models/draft_message_test.dart | 9 +- .../test/src/core/models/event_test.dart | 4 +- .../test/src/core/models/member_test.dart | 12 +- .../models/message_reaction_helper_test.dart | 9 +- .../src/core/models/message_state_test.dart | 3 +- .../test/src/core/models/message_test.dart | 41 +- .../test/src/core/models/moderation_test.dart | 6 +- .../test/src/core/models/own_user_test.dart | 3 +- .../test/src/core/models/poll_test.dart | 2 +- .../test/src/core/models/reaction_test.dart | 10 +- .../test/src/core/models/read_test.dart | 7 +- .../src/core/models/serialization_test.dart | 17 +- .../test/src/core/models/thread_test.dart | 6 +- .../test/src/core/models/user_block_test.dart | 16 +- .../test/src/core/models/user_test.dart | 15 +- .../src/core/util/event_controller_test.dart | 4 +- .../test/src/core/util/serializer_test.dart | 4 +- .../src/db/chat_persistence_client_test.dart | 105 +- packages/stream_chat/test/src/fakes.dart | 19 +- packages/stream_chat/test/src/matchers.dart | 98 +- packages/stream_chat/test/src/mocks.dart | 22 +- packages/stream_chat/test/src/utils.dart | 3 +- .../test/src/ws/websocket_test.dart | 3 +- .../example/lib/debug/channel_page.dart | 9 +- .../stream_chat_flutter/example/lib/main.dart | 160 +- .../example/lib/split_view.dart | 38 +- .../example/lib/tutorial_part_3.dart | 40 +- .../example/lib/tutorial_part_4.dart | 28 +- .../example/lib/tutorial_part_5.dart | 3 +- .../stream_chat_flutter/example/pubspec.yaml | 4 +- .../conditional_parent_builder.dart | 9 +- .../src/desktop_widget.dart | 9 +- .../src/desktop_widget_base.dart | 10 +- .../src/desktop_widget_builder.dart | 9 +- .../src/platform_widget.dart | 9 +- .../src/platform_widget_base.dart | 10 +- .../src/platform_widget_builder.dart | 9 +- .../src/element_registry.dart | 21 +- .../src/item_positions_listener.dart | 4 +- .../src/positioned_list.dart | 277 +- .../src/scroll_view.dart | 6 +- .../src/scrollable_positioned_list.dart | 73 +- .../src/viewport.dart | 22 +- .../src/wrapping.dart | 181 +- .../ai_typing_indicator_view.dart | 28 +- .../stream_typewriter_builder.dart | 15 +- .../ai_assistant/streaming_message_view.dart | 4 +- .../attachment_upload_state_builder.dart | 9 +- .../attachment/attachment_widget_catalog.dart | 9 +- .../builder/attachment_widget_builder.dart | 9 +- .../builder/mixed_attachment_builder.dart | 60 +- .../builder/url_attachment_builder.dart | 9 +- ...recording_attachment_playlist_builder.dart | 3 +- .../lib/src/attachment/file_attachment.dart | 6 +- .../src/attachment/gallery_attachment.dart | 6 +- .../lib/src/attachment/giphy_attachment.dart | 3 +- .../lib/src/attachment/handler/common.dart | 3 +- .../stream_attachment_handler_html.dart | 3 +- .../handler/stream_attachment_handler_io.dart | 7 +- .../lib/src/attachment/image_attachment.dart | 3 +- .../lib/src/attachment/poll_attachment.dart | 3 +- .../thumbnail/file_attachment_thumbnail.dart | 20 +- .../thumbnail/image_attachment_thumbnail.dart | 8 +- .../attachment/thumbnail/thumbnail_error.dart | 11 +- .../lib/src/attachment/url_attachment.dart | 59 +- .../lib/src/attachment/video_attachment.dart | 3 +- .../voice_recording_attachment.dart | 25 +- .../voice_recording_attachment_playlist.dart | 6 +- .../attachment_actions_modal.dart | 349 +- .../src/audio/audio_playlist_controller.dart | 29 +- .../lib/src/audio/audio_playlist_state.dart | 6 +- .../lib/src/audio/audio_sampling.dart | 27 +- .../src/autocomplete/stream_autocomplete.dart | 40 +- .../stream_command_autocomplete_options.dart | 4 +- .../stream_mention_autocomplete_options.dart | 38 +- .../lib/src/avatars/group_avatar.dart | 22 +- .../lib/src/avatars/user_avatar.dart | 19 +- .../stream_channel_info_bottom_sheet.dart | 92 +- .../lib/src/channel/channel_header.dart | 19 +- .../lib/src/channel/channel_info.dart | 3 +- .../lib/src/channel/channel_list_header.dart | 33 +- .../lib/src/channel/channel_name.dart | 3 +- .../src/channel/stream_channel_avatar.dart | 20 +- .../lib/src/channel/stream_channel_name.dart | 105 +- .../lib/src/context_menu/context_menu.dart | 9 +- .../src/context_menu/context_menu_region.dart | 9 +- .../lib/src/dialogs/channel_info_dialog.dart | 7 +- .../lib/src/dialogs/message_dialog.dart | 3 +- .../lib/src/fullscreen_media/fsm_stub.dart | 3 +- .../fullscreen_media/full_screen_media.dart | 69 +- .../full_screen_media_builder.dart | 3 +- .../full_screen_media_desktop.dart | 28 +- .../lib/src/gallery/gallery_footer.dart | 44 +- .../lib/src/gallery/gallery_header.dart | 19 +- .../lib/src/icons/stream_svg_icon.g.dart | 12 +- .../lib/src/indicators/typing_indicator.dart | 10 +- .../lib/src/indicators/unread_indicator.dart | 24 +- .../indicators/upload_progress_indicator.dart | 3 +- .../keyboard_shortcut_runner.dart | 3 +- .../stream_chat_localizations.dart | 3 +- .../lib/src/localization/translations.dart | 53 +- .../message_actions_builder.dart | 5 +- .../options/stream_gallery_picker.dart | 3 +- .../stream_attachment_picker.dart | 30 +- ...stream_attachment_picker_bottom_sheet.dart | 16 +- .../stream_attachment_picker_controller.dart | 41 +- .../stream_attachment_picker_option.dart | 18 +- .../stream_attachment_picker_result.dart | 3 +- .../audio_recorder_controller.dart | 10 +- .../audio_recorder_feedback.dart | 14 +- .../audio_recorder/audio_recorder_state.dart | 12 +- .../audio_recorder/stream_audio_recorder.dart | 135 +- .../message_input/dm_checkbox_list_tile.dart | 6 +- .../message_input/quoted_message_widget.dart | 21 +- .../message_input/stream_message_input.dart | 241 +- .../stream_message_input_attachment_list.dart | 206 +- .../stream_message_text_field.dart | 321 +- .../lib/src/message_input/tld.dart | 4 +- .../message_list_view/message_details.dart | 6 +- .../message_list_view/message_list_view.dart | 300 +- .../lib/src/message_list_view/mlv_utils.dart | 3 +- .../unread_indicator_button.dart | 11 +- .../message_modal/message_actions_modal.dart | 4 +- .../message_reactions_modal.dart | 8 +- .../lib/src/message_widget/bottom_row.dart | 76 +- .../src/message_widget/deleted_message.dart | 6 +- .../lib/src/message_widget/message_card.dart | 21 +- .../lib/src/message_widget/message_text.dart | 46 +- .../src/message_widget/message_widget.dart | 117 +- .../message_widget_content.dart | 39 +- .../src/message_widget/parse_attachments.dart | 13 +- .../src/message_widget/quoted_message.dart | 7 +- .../message_widget/user_avatar_transform.dart | 7 +- .../lib/src/misc/adaptive_dialog_action.dart | 30 +- .../lib/src/misc/audio_waveform.dart | 17 +- .../src/misc/connection_status_builder.dart | 6 +- .../lib/src/misc/flex_grid.dart | 26 +- .../misc/flexible_fractionally_sized_box.dart | 4 +- .../lib/src/misc/gradient_box_border.dart | 53 +- .../lib/src/misc/info_tile.dart | 6 +- .../lib/src/misc/markdown_message.dart | 33 +- .../lib/src/misc/option_list_tile.dart | 8 +- .../lib/src/misc/reaction_icon.dart | 16 +- .../misc/separated_reorderable_list_view.dart | 84 +- .../lib/src/misc/simple_safe_area.dart | 8 +- .../lib/src/misc/swipeable.dart | 31 +- .../lib/src/misc/thread_header.dart | 17 +- .../poll_option_reorderable_list_view.dart | 12 +- .../creator/stream_poll_creator_dialog.dart | 9 +- .../creator/stream_poll_creator_widget.dart | 9 +- .../interactor/poll_add_comment_dialog.dart | 15 +- .../lib/src/poll/interactor/poll_footer.dart | 3 +- .../interactor/poll_options_list_view.dart | 12 +- .../poll_suggest_option_dialog.dart | 18 +- .../src/poll/stream_poll_comments_dialog.dart | 3 +- .../poll/stream_poll_option_votes_dialog.dart | 13 +- .../src/poll/stream_poll_results_dialog.dart | 12 +- .../lib/src/poll/stream_poll_text_field.dart | 9 +- .../reactions/desktop_reactions_builder.dart | 30 +- .../indicator/reaction_indicator.dart | 20 +- .../reaction_indicator_icon_list.dart | 9 +- .../src/reactions/picker/reaction_picker.dart | 37 +- .../picker/reaction_picker_icon_list.dart | 11 +- .../lib/src/reactions/reaction_bubble.dart | 31 +- .../reactions/reaction_bubble_overlay.dart | 44 +- .../stream_channel_grid_tile.dart | 23 +- .../stream_channel_grid_view.dart | 10 +- .../stream_channel_list_tile.dart | 72 +- .../stream_channel_list_view.dart | 10 +- .../stream_draft_list_view.dart | 9 +- .../stream_member_grid_view.dart | 9 +- .../stream_member_list_view.dart | 157 +- .../stream_message_search_grid_view.dart | 9 +- .../stream_message_search_list_tile.dart | 43 +- .../stream_message_search_list_view.dart | 17 +- .../photo_gallery/stream_photo_gallery.dart | 10 +- .../stream_photo_gallery_controller.dart | 3 +- .../stream_photo_gallery_tile.dart | 26 +- .../stream_poll_vote_list_tile.dart | 29 +- .../stream_poll_vote_list_view.dart | 159 +- .../stream_scroll_view_error_widget.dart | 6 +- ...am_scroll_view_indexed_widget_builder.dart | 15 +- .../stream_scroll_view_load_more_error.dart | 6 +- ...tream_scroll_view_load_more_indicator.dart | 8 +- .../stream_scroll_view_loading_widget.dart | 8 +- .../stream_thread_list_tile.dart | 7 +- .../stream_thread_list_view.dart | 9 +- .../stream_user_grid_tile.dart | 23 +- .../stream_user_grid_view.dart | 9 +- .../stream_user_list_tile.dart | 43 +- .../stream_user_list_view.dart | 159 +- .../lib/src/stream_chat.dart | 6 +- .../lib/src/stream_chat_configuration.dart | 15 +- .../theme/audio_waveform_slider_theme.dart | 32 +- .../lib/src/theme/audio_waveform_theme.dart | 33 +- .../lib/src/theme/avatar_theme.dart | 4 +- .../lib/src/theme/channel_header_theme.dart | 24 +- .../src/theme/channel_list_header_theme.dart | 18 +- .../lib/src/theme/channel_preview_theme.dart | 39 +- .../lib/src/theme/draft_list_tile_theme.dart | 73 +- .../lib/src/theme/gallery_footer_theme.dart | 60 +- .../lib/src/theme/gallery_header_theme.dart | 24 +- .../lib/src/theme/message_input_theme.dart | 54 +- .../src/theme/message_list_view_theme.dart | 12 +- .../lib/src/theme/message_theme.dart | 142 +- .../src/theme/poll_comments_dialog_theme.dart | 50 +- .../lib/src/theme/poll_creator_theme.dart | 172 +- .../lib/src/theme/poll_interactor_theme.dart | 212 +- .../theme/poll_option_votes_dialog_theme.dart | 78 +- .../src/theme/poll_options_dialog_theme.dart | 70 +- .../src/theme/poll_results_dialog_theme.dart | 72 +- .../lib/src/theme/stream_chat_theme.dart | 107 +- .../lib/src/theme/text_theme.dart | 43 +- .../lib/src/theme/thread_list_tile_theme.dart | 158 +- .../voice_recording_attachment_theme.dart | 52 +- .../lib/src/utils/date_formatter.dart | 9 +- .../lib/src/utils/device_segmentation.dart | 8 +- .../lib/src/utils/extensions.dart | 70 +- .../lib/src/utils/helpers.dart | 53 +- .../src/utils/message_preview_formatter.dart | 2 +- .../lib/src/utils/typedefs.dart | 218 +- .../lib/src/video/video_thumbnail_image.dart | 7 +- .../lib/stream_chat_flutter.dart | 3 +- packages/stream_chat_flutter/pubspec.yaml | 4 +- .../conditional_parent_builder_test.dart | 6 +- .../test/flutter_test_config.dart | 3 +- .../desktop_widget_builder_test.dart | 3 +- .../platform_widget_builder_test.dart | 3 +- ...ontal_scrollable_positioned_list_test.dart | 220 +- .../positioned_list_test.dart | 310 +- .../reversed_positioned_list_test.dart | 168 +- ...ersed_scrollable_positioned_list_test.dart | 163 +- .../scrollable_positioned_list_test.dart | 1058 +- .../separated_positioned_list_test.dart | 153 +- ...rated_scrollable_positioned_list_test.dart | 316 +- ...ontal_scrollable_positioned_list_test.dart | 144 +- .../shrink_wrap_position_list_test.dart | 377 +- ...nk_wrap_scrollable_position_list_test.dart | 196 +- .../attachment/attachment_handler_test.dart | 12 +- .../attachment_upload_state_builder_test.dart | 4 +- ...recording_attachment_playlist_builder.dart | 16 +- .../src/attachment/file_attachment_test.dart | 10 +- .../attachment/gallery_attachment_test.dart | 16 +- .../src/attachment/giphy_attachment_test.dart | 13 +- .../src/attachment/image_attachment_test.dart | 13 +- ...ce_recording_attachment_playlist_test.dart | 16 +- .../voice_recording_attachment_test.dart | 19 +- .../attachment_actions_modal_test.dart | 28 +- .../audio/audio_playlist_controller_test.dart | 11 +- .../test/src/audio/audio_sampling_test.dart | 3 +- .../src/avatars/gradient_avatar_test.dart | 25 +- .../test/src/avatars/group_avatar_test.dart | 8 +- .../test/src/avatars/user_avatar_test.dart | 18 +- .../attachment_modal_sheet_test.dart | 108 +- .../bottom_sheets/error_alert_sheet_test.dart | 16 +- .../test/src/channel/channel_header_test.dart | 152 +- .../test/src/channel/channel_image_test.dart | 22 +- .../src/channel/channel_list_header_test.dart | 21 +- .../test/src/channel/channel_name_test.dart | 9 +- .../stream_message_preview_text_test.dart | 6 +- .../src/dialogs/confirmation_dialog_test.dart | 21 +- .../stream_chat_flutter/test/src/fakes.dart | 16 +- .../full_screen_media_test.dart | 46 +- .../test/src/gallery/gallery_footer_test.dart | 15 +- .../test/src/gallery/gallery_header_test.dart | 15 +- .../test/src/icons/stream_svg_icon_test.dart | 16 +- .../src/indicators/typing_indicator_test.dart | 38 +- .../src/indicators/unread_indicator_test.dart | 73 +- .../upload_progress_indicator_test.dart | 16 +- .../test/src/material_app_wrapper.dart | 23 +- .../message_action_item_test.dart | 24 +- .../message_actions_builder_test.dart | 19 +- .../message_input/attachment_button_test.dart | 4 +- .../stream_attachment_picker_test.dart | 12 +- .../audio_recorder_controller_test.dart | 15 +- .../stream_audio_recorder_test.dart | 37 +- .../src/message_input/message_input_test.dart | 48 +- .../stream_message_send_button_test.dart | 28 +- .../message_list_view/bottom_row_test.dart | 5 +- .../message_list_view_test.dart | 36 +- .../message_actions_modal_test.dart | 26 +- .../message_reactions_modal_test.dart | 26 +- .../moderated_message_actions_modal_test.dart | 26 +- .../message_widget/deleted_message_test.dart | 9 +- .../src/message_widget/message_text_test.dart | 38 +- .../test/src/misc/audio_waveform_test.dart | 16 +- .../test/src/misc/back_button_test.dart | 3 +- .../test/src/misc/date_divider_test.dart | 3 +- .../test/src/misc/reaction_bubble_test.dart | 12 +- .../test/src/misc/system_message_test.dart | 33 +- .../test/src/misc/thread_header_test.dart | 74 +- .../test/src/misc/timestamp_test.dart | 16 +- .../stream_chat_flutter/test/src/mocks.dart | 6 +- ...oll_option_reorderable_list_view_test.dart | 290 +- .../poll_question_text_field_test.dart | 24 +- .../stream_poll_creator_widget_test.dart | 3 +- .../src/poll/interactor/poll_footer_test.dart | 142 +- .../src/poll/interactor/poll_header_test.dart | 24 +- .../poll_suggest_option_dialog_test.dart | 27 +- .../stream_poll_interactor_test.dart | 16 +- ...oll_option_reorderable_list_view_test.dart | 24 +- .../poll/poll_question_text_field_test.dart | 24 +- .../poll/stream_poll_options_dialog_test.dart | 16 +- .../poll/stream_poll_results_dialog_test.dart | 16 +- .../reaction_indicator_icon_list_test.dart | 24 +- .../indicator/reaction_indicator_test.dart | 24 +- .../reaction_picker_icon_list_test.dart | 24 +- .../picker/reaction_picker_test.dart | 24 +- .../stream_member_list_view_test.dart | 9 +- .../stream_unread_threads_banner_test.dart | 16 +- .../src/stream_chat_configuration_test.dart | 36 +- .../test/src/theme/avatar_theme_test.dart | 15 +- .../src/theme/channel_header_theme_test.dart | 49 +- .../theme/channel_list_header_theme_test.dart | 51 +- .../src/theme/channel_preview_theme_test.dart | 59 +- .../src/theme/draft_list_tile_theme_test.dart | 19 +- .../src/theme/gallery_footer_theme_test.dart | 101 +- .../src/theme/gallery_header_theme_test.dart | 129 +- .../src/theme/message_input_theme_test.dart | 21 +- .../theme/message_list_view_theme_test.dart | 74 +- .../test/src/theme/message_theme_test.dart | 51 +- .../theme/thread_list_tile_theme_test.dart | 46 +- .../test/src/utils/extension_test.dart | 3 +- .../test/test_utils/data_generator.dart | 10 +- .../example/lib/main.dart | 256 +- .../example/pubspec.yaml | 4 +- .../lib/src/better_stream_builder.dart | 6 +- .../lib/src/lazy_load_scroll_view.dart | 12 +- .../lib/src/message_list_core.dart | 15 +- .../src/message_text_field_controller.dart | 9 +- .../lib/src/paged_value_notifier.dart | 39 +- .../lib/src/paged_value_scroll_view.dart | 241 +- .../lib/src/stream_channel.dart | 56 +- .../src/stream_channel_list_controller.dart | 19 +- .../stream_channel_list_event_handler.dart | 3 +- .../lib/src/stream_chat_core.dart | 9 +- .../lib/src/stream_draft_list_controller.dart | 22 +- .../src/stream_member_list_controller.dart | 18 +- .../src/stream_message_input_controller.dart | 48 +- ...ream_message_reminder_list_controller.dart | 25 +- ...stream_message_search_list_controller.dart | 53 +- .../lib/src/stream_poll_controller.dart | 81 +- .../src/stream_poll_vote_list_controller.dart | 31 +- .../src/stream_thread_list_controller.dart | 32 +- .../lib/src/stream_user_list_controller.dart | 18 +- .../lib/stream_chat_flutter_core.dart | 6 +- .../stream_chat_flutter_core/pubspec.yaml | 4 +- .../test/message_list_core_test.dart | 63 +- .../stream_chat_flutter_core/test/mocks.dart | 10 +- .../test/stream_chat_core_test.dart | 9 +- .../stream_draft_list_controller_test.dart | 172 +- .../stream_message_input_controller_test.dart | 9 +- ...message_reminder_list_controller_test.dart | 86 +- .../test/stream_poll_controller_test.dart | 36 +- ...stream_thread_list_event_handler_test.dart | 30 +- .../example/lib/add_new_lang.dart | 59 +- .../example/lib/main.dart | 52 +- .../example/lib/override_lang.dart | 62 +- .../example/pubspec.yaml | 4 +- .../lib/src/stream_chat_localizations.dart | 18 +- .../lib/src/stream_chat_localizations_ca.dart | 65 +- .../lib/src/stream_chat_localizations_de.dart | 53 +- .../lib/src/stream_chat_localizations_en.dart | 56 +- .../lib/src/stream_chat_localizations_es.dart | 71 +- .../lib/src/stream_chat_localizations_fr.dart | 71 +- .../lib/src/stream_chat_localizations_hi.dart | 48 +- .../lib/src/stream_chat_localizations_it.dart | 68 +- .../lib/src/stream_chat_localizations_ja.dart | 29 +- .../lib/src/stream_chat_localizations_ko.dart | 26 +- .../lib/src/stream_chat_localizations_no.dart | 62 +- .../lib/src/stream_chat_localizations_pt.dart | 68 +- .../lib/stream_chat_localizations.dart | 5 +- .../stream_chat_localizations/pubspec.yaml | 4 +- .../test/basics_test.dart | 87 +- .../test/override_test.dart | 212 +- .../test/translations_test.dart | 15 +- .../example/lib/main.dart | 54 +- .../example/pubspec.yaml | 4 +- .../voting_visibility_converter.dart | 3 +- .../lib/src/dao/channel_dao.dart | 34 +- .../lib/src/dao/channel_query_dao.dart | 62 +- .../lib/src/dao/connection_event_dao.dart | 39 +- .../lib/src/dao/connection_event_dao.g.dart | 3 +- .../lib/src/dao/draft_message_dao.dart | 19 +- .../lib/src/dao/location_dao.dart | 19 +- .../lib/src/dao/member_dao.dart | 41 +- .../lib/src/dao/message_dao.dart | 111 +- .../lib/src/dao/pinned_message_dao.dart | 114 +- .../src/dao/pinned_message_reaction_dao.dart | 39 +- .../dao/pinned_message_reaction_dao.g.dart | 3 +- .../lib/src/dao/poll_dao.dart | 33 +- .../lib/src/dao/poll_vote_dao.dart | 26 +- .../lib/src/dao/reaction_dao.dart | 39 +- .../lib/src/dao/read_dao.dart | 31 +- .../lib/src/dao/user_dao.dart | 14 +- .../lib/src/db/drift_chat_database.dart | 24 +- .../lib/src/db/drift_chat_database.g.dart | 15729 ++++++++-------- .../lib/src/db/shared/native_db.dart | 26 +- .../lib/src/entity/channel_queries.dart | 6 +- .../lib/src/entity/channels.dart | 3 +- .../lib/src/entity/draft_messages.dart | 7 +- .../lib/src/entity/locations.dart | 8 +- .../lib/src/entity/members.dart | 9 +- .../lib/src/entity/messages.dart | 12 +- .../src/entity/pinned_message_reactions.dart | 4 +- .../lib/src/entity/poll_votes.dart | 3 +- .../lib/src/entity/polls.dart | 11 +- .../lib/src/entity/reactions.dart | 12 +- .../lib/src/entity/reads.dart | 9 +- .../lib/src/mapper/channel_mapper.dart | 47 +- .../lib/src/mapper/draft_message_mapper.dart | 38 +- .../lib/src/mapper/event_mapper.dart | 12 +- .../lib/src/mapper/location_mapper.dart | 47 +- .../lib/src/mapper/member_mapper.dart | 64 +- .../lib/src/mapper/message_mapper.dart | 152 +- .../lib/src/mapper/pinned_message_mapper.dart | 155 +- .../pinned_message_reaction_mapper.dart | 38 +- .../lib/src/mapper/poll_mapper.dart | 36 +- .../lib/src/mapper/poll_vote_mapper.dart | 37 +- .../lib/src/mapper/reaction_mapper.dart | 38 +- .../lib/src/mapper/read_mapper.dart | 30 +- .../lib/src/mapper/user_mapper.dart | 48 +- .../src/stream_chat_persistence_client.dart | 42 +- packages/stream_chat_persistence/pubspec.yaml | 4 +- .../test/mock_chat_database.dart | 18 +- .../test/src/dao/channel_dao_test.dart | 9 +- .../test/src/dao/draft_message_dao_test.dart | 57 +- .../test/src/dao/location_dao_test.dart | 65 +- .../test/src/dao/member_dao_test.dart | 8 +- .../test/src/dao/message_dao_test.dart | 83 +- .../test/src/dao/pinned_message_dao_test.dart | 95 +- .../dao/pinned_message_reaction_dao_test.dart | 42 +- .../test/src/dao/poll_vote_dao_test.dart | 16 +- .../test/src/dao/reaction_dao_test.dart | 6 +- .../test/src/dao/read_dao_test.dart | 14 +- .../test/src/db/drift_chat_database_test.dart | 3 +- .../test/src/mapper/location_mapper_test.dart | 3 +- .../test/src/mapper/message_mapper_test.dart | 6 +- .../mapper/pinned_message_mapper_test.dart | 6 +- .../test/src/utils/date_matcher.dart | 6 +- .../stream_chat_persistence_client_test.dart | 483 +- pubspec.lock | 102 +- pubspec.yaml | 2 +- sample_app/lib/app.dart | 56 +- sample_app/lib/firebase_options.dart | 15 +- .../lib/pages/advanced_options_page.dart | 61 +- .../pages/channel_file_display_screen.dart | 147 +- sample_app/lib/pages/channel_list_page.dart | 57 +- .../pages/channel_media_display_screen.dart | 59 +- sample_app/lib/pages/channel_page.dart | 3 +- sample_app/lib/pages/chat_info_screen.dart | 187 +- sample_app/lib/pages/choose_user_page.dart | 50 +- sample_app/lib/pages/draft_list_page.dart | 4 +- .../lib/pages/group_chat_details_screen.dart | 184 +- sample_app/lib/pages/group_info_screen.dart | 483 +- sample_app/lib/pages/new_chat_screen.dart | 122 +- .../lib/pages/new_group_chat_screen.dart | 69 +- .../lib/pages/pinned_messages_screen.dart | 41 +- sample_app/lib/pages/reminders_page.dart | 38 +- sample_app/lib/pages/splash_screen.dart | 136 +- sample_app/lib/pages/thread_list_page.dart | 10 +- sample_app/lib/pages/thread_page.dart | 21 +- sample_app/lib/pages/user_mentions_page.dart | 12 +- sample_app/lib/routes/app_routes.dart | 27 +- sample_app/lib/routes/routes.dart | 40 +- sample_app/lib/state/init_data.dart | 3 +- sample_app/lib/utils/app_config.dart | 53 +- .../utils/local_notification_observer.dart | 13 +- sample_app/lib/utils/localizations.dart | 72 +- sample_app/lib/utils/location_provider.dart | 47 +- .../lib/utils/notifications_service.dart | 3 +- .../lib/utils/shared_location_service.dart | 14 +- sample_app/lib/widgets/channel_list.dart | 94 +- .../lib/widgets/chips_input_text_field.dart | 32 +- .../widgets/location/location_attachment.dart | 8 +- sample_app/lib/widgets/search_text_field.dart | 9 +- sample_app/lib/widgets/simple_map_view.dart | 14 +- sample_app/lib/widgets/stream_version.dart | 3 +- sample_app/pubspec.yaml | 4 +- 613 files changed, 23935 insertions(+), 27402 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a640f740b2..7a351526bb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -25,7 +25,6 @@ updates: # All packages grouped into a single configuration using multi-directory support - package-ecosystem: "pub" directories: - - "/sample_app" - "/packages/stream_chat" - "/packages/stream_chat_flutter_core" - "/packages/stream_chat_flutter" diff --git a/.github/workflows/legacy_version_analyze.yml b/.github/workflows/legacy_version_analyze.yml index 7705c53b8b..1a2e61233e 100644 --- a/.github/workflows/legacy_version_analyze.yml +++ b/.github/workflows/legacy_version_analyze.yml @@ -3,7 +3,7 @@ name: legacy_version_analyze env: # Note: The versions below should be manually updated after a new stable # version comes out. - flutter_version: "3.27.4" + flutter_version: "3.38.1" on: push: diff --git a/analysis_options.yaml b/analysis_options.yaml index d5ac4a48f4..b94dec6968 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -4,6 +4,10 @@ analyzer: - packages/*/lib/scrollable_positioned_list/** - packages/*/lib/**/*.freezed.dart +formatter: + page_width: 120 + trailing_commas: preserve + linter: rules: # these rules are documented on and in the same order as @@ -64,7 +68,6 @@ linter: - leading_newlines_in_multiline_strings - library_names - library_prefixes - - lines_longer_than_80_chars - missing_whitespace_between_adjacent_strings - non_constant_identifier_names - null_closures diff --git a/melos.yaml b/melos.yaml index 6a54c65de2..97a056fb83 100644 --- a/melos.yaml +++ b/melos.yaml @@ -1,4 +1,4 @@ -name: stream_chat_flutter +name: stream_chat_flutter_workspace repository: https://github.com/GetStream/stream-chat-flutter packages: @@ -18,9 +18,9 @@ command: bootstrap: # Dart and Flutter environment used in the project. environment: - sdk: ^3.6.2 + sdk: ^3.10.0 # We are not using carat '^' syntax here because flutter don't follow semantic versioning. - flutter: ">=3.27.4" + flutter: ">=3.38.1" # List of all the dependencies used in the project. dependencies: diff --git a/packages/stream_chat/example/lib/main.dart b/packages/stream_chat/example/lib/main.dart index 0744726424..f85f4d99b3 100644 --- a/packages/stream_chat/example/lib/main.dart +++ b/packages/stream_chat/example/lib/main.dart @@ -17,8 +17,7 @@ Future main() async { User( id: 'cool-shadow-7', name: 'Cool Shadow', - image: - 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', + image: 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', ), '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo''', ); @@ -60,9 +59,9 @@ class StreamExample extends StatelessWidget { @override Widget build(BuildContext context) => MaterialApp( - title: 'Stream Chat Dart Example', - home: HomeScreen(channel: channel), - ); + title: 'Stream Chat Dart Example', + home: HomeScreen(channel: channel), + ); } /// Main screen of our application. The layout is comprised of an [AppBar] @@ -87,30 +86,31 @@ class HomeScreen extends StatelessWidget { body: SafeArea( child: StreamBuilder?>( stream: messages, - builder: ( - BuildContext context, - AsyncSnapshot?> snapshot, - ) { - if (snapshot.hasData && snapshot.data != null) { - return MessageView( - messages: snapshot.data!.reversed.toList(), - channel: channel, - ); - } else if (snapshot.hasError) { - return const Center( - child: Text( - 'There was an error loading messages. Please see logs.', - ), - ); - } - return const Center( - child: SizedBox( - width: 100, - height: 100, - child: CircularProgressIndicator(), - ), - ); - }, + builder: + ( + BuildContext context, + AsyncSnapshot?> snapshot, + ) { + if (snapshot.hasData && snapshot.data != null) { + return MessageView( + messages: snapshot.data!.reversed.toList(), + channel: channel, + ); + } else if (snapshot.hasError) { + return const Center( + child: Text( + 'There was an error loading messages. Please see logs.', + ), + ); + } + return const Center( + child: SizedBox( + width: 100, + height: 100, + child: CircularProgressIndicator(), + ), + ); + }, ), ), ); @@ -168,80 +168,80 @@ class _MessageViewState extends State { @override Widget build(BuildContext context) => Column( - children: [ - Expanded( - child: ListView.builder( - controller: _scrollController, - itemCount: _messages.length, - reverse: true, - itemBuilder: (BuildContext context, int index) { - final item = _messages[index]; - if (item.user?.id == widget.channel.client.uid) { - return Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.all(8), - child: Text(item.text ?? ''), - ), - ); - } else { - return Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.all(8), - child: Text(item.text ?? ''), - ), - ); - } - }, - ), - ), - Padding( - padding: const EdgeInsets.all(8), - child: Row( - children: [ - Expanded( - child: TextField( - controller: _controller, - decoration: const InputDecoration( - hintText: 'Enter your message', - ), - ), + children: [ + Expanded( + child: ListView.builder( + controller: _scrollController, + itemCount: _messages.length, + reverse: true, + itemBuilder: (BuildContext context, int index) { + final item = _messages[index]; + if (item.user?.id == widget.channel.client.uid) { + return Align( + alignment: Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.all(8), + child: Text(item.text ?? ''), + ), + ); + } else { + return Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8), + child: Text(item.text ?? ''), + ), + ); + } + }, + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _controller, + decoration: const InputDecoration( + hintText: 'Enter your message', ), - Material( - type: MaterialType.circle, - color: Colors.blue, - clipBehavior: Clip.hardEdge, - child: InkWell( - onTap: () async { - // We can send a new message by calling `sendMessage` on - // the current channel. After sending a message, the - // TextField is cleared and the list view is scrolled - // to show the new item. - if (_controller.value.text.isNotEmpty) { - await widget.channel.sendMessage( - Message(text: _controller.value.text), - ); - _controller.clear(); - _updateList(); - } - }, - child: const Padding( - padding: EdgeInsets.all(8), - child: Center( - child: Icon( - Icons.send, - color: Colors.white, - ), - ), + ), + ), + Material( + type: MaterialType.circle, + color: Colors.blue, + clipBehavior: Clip.hardEdge, + child: InkWell( + onTap: () async { + // We can send a new message by calling `sendMessage` on + // the current channel. After sending a message, the + // TextField is cleared and the list view is scrolled + // to show the new item. + if (_controller.value.text.isNotEmpty) { + await widget.channel.sendMessage( + Message(text: _controller.value.text), + ); + _controller.clear(); + _updateList(); + } + }, + child: const Padding( + padding: EdgeInsets.all(8), + child: Center( + child: Icon( + Icons.send, + color: Colors.white, ), ), ), - ], + ), ), - ), - ], - ); + ], + ), + ), + ], + ); } /// Helper extension for quickly retrieving diff --git a/packages/stream_chat/example/pubspec.yaml b/packages/stream_chat/example/pubspec.yaml index 421f34dce5..f4690ca13f 100644 --- a/packages/stream_chat/example/pubspec.yaml +++ b/packages/stream_chat/example/pubspec.yaml @@ -17,8 +17,8 @@ version: 1.0.0+1 # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: cupertino_icons: ^1.0.3 diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 1619d0d468..f63116877c 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -75,25 +75,25 @@ class Channel { String? name, String? image, Map? extraData, - }) : _cid = _id != null ? '$_type:$_id' : null, - _extraData = { - ...?extraData, - if (name != null) 'name': name, - if (image != null) 'image': image, - } { + }) : _cid = _id != null ? '$_type:$_id' : null, + _extraData = { + ...?extraData, + if (name != null) 'name': name, + if (image != null) 'image': image, + } { _client.logger.info('New Channel instance created, not yet initialized'); } /// Create a channel client instance from a [ChannelState] object. Channel.fromState(this._client, ChannelState channelState) - : assert( - channelState.channel != null, - 'No channel found inside channel state', - ), - _id = channelState.channel!.id, - _type = channelState.channel!.type, - _cid = channelState.channel!.cid, - _extraData = channelState.channel!.extraData { + : assert( + channelState.channel != null, + 'No channel found inside channel state', + ), + _id = channelState.channel!.id, + _type = channelState.channel!.type, + _cid = channelState.channel!.cid, + _extraData = channelState.channel!.extraData { _initState(channelState); // Initialize the state immediately. } @@ -144,16 +144,11 @@ class Channel { } /// Returns true if the channel is muted. - bool get isMuted => - _client.state.currentUser?.channelMutes - .any((element) => element.channel.cid == cid) == - true; + bool get isMuted => _client.state.currentUser?.channelMutes.any((element) => element.channel.cid == cid) == true; /// Returns true if the channel is muted, as a stream. Stream get isMutedStream => _client.state.currentUserStream - .map((event) => - event?.channelMutes.any((element) => element.channel.cid == cid) == - true) + .map((event) => event?.channelMutes.any((element) => element.channel.cid == cid) == true) .distinct(); /// True if the channel is a group. @@ -456,15 +451,12 @@ class Channel { } /// List of user permissions on this channel - List get ownCapabilities => - state?._channelState.channel?.ownCapabilities ?? []; + List get ownCapabilities => state?._channelState.channel?.ownCapabilities ?? []; /// List of user permissions on this channel Stream> get ownCapabilitiesStream { _checkInitialized(); - return state!.channelStateStream - .map((cs) => cs.channel?.ownCapabilities ?? []) - .distinct(); + return state!.channelStateStream.map((cs) => cs.channel?.ownCapabilities ?? []).distinct(); } /// Channel extra data as a stream. @@ -593,80 +585,85 @@ class Channel { } } - return Future.wait(attachments.map((it) { - client.logger.info('Uploading ${it.id} attachment...'); - - final throttledUpdateAttachment = updateAttachment.throttled( - const Duration(milliseconds: 500), - ); - - void onSendProgress(int sent, int total) { - throttledUpdateAttachment([ - it.copyWith( - uploadState: UploadState.inProgress(uploaded: sent, total: total), - ), - ]); - } + return Future.wait( + attachments.map((it) { + client.logger.info('Uploading ${it.id} attachment...'); - final isImage = it.type == AttachmentType.image; - final cancelToken = CancelToken(); - Future future; - if (isImage) { - future = sendImage( - it.file!, - onSendProgress: onSendProgress, - cancelToken: cancelToken, - extraData: it.extraData, + final throttledUpdateAttachment = updateAttachment.throttled( + const Duration(milliseconds: 500), ); - } else { - future = sendFile( - it.file!, - onSendProgress: onSendProgress, - cancelToken: cancelToken, - extraData: it.extraData, - ); - } - _cancelableAttachmentUploadRequest[it.id] = cancelToken; - return future.then((response) { - client.logger.info('Attachment ${it.id} uploaded successfully...'); - - // If the response is SendFileResponse, then we might also be getting - // thumbUrl in case of video. So we need to update the attachment with - // both the assetUrl and thumbUrl. - if (response is SendFileResponse) { - updateAttachment( + + void onSendProgress(int sent, int total) { + throttledUpdateAttachment([ it.copyWith( - assetUrl: response.file, - thumbUrl: response.thumbUrl, - uploadState: const UploadState.success(), + uploadState: UploadState.inProgress(uploaded: sent, total: total), ), + ]); + } + + final isImage = it.type == AttachmentType.image; + final cancelToken = CancelToken(); + Future future; + if (isImage) { + future = sendImage( + it.file!, + onSendProgress: onSendProgress, + cancelToken: cancelToken, + extraData: it.extraData, ); } else { - updateAttachment( - it.copyWith( - imageUrl: response.file, - uploadState: const UploadState.success(), - ), + future = sendFile( + it.file!, + onSendProgress: onSendProgress, + cancelToken: cancelToken, + extraData: it.extraData, ); } - }).catchError((e, stk) { - if (e is StreamChatNetworkError && e.isRequestCancelledError) { - client.logger.info('Attachment ${it.id} upload cancelled'); - - // remove attachment from message if cancelled. - updateAttachment(it, remove: true); - return; - } + _cancelableAttachmentUploadRequest[it.id] = cancelToken; + return future + .then((response) { + client.logger.info('Attachment ${it.id} uploaded successfully...'); + + // If the response is SendFileResponse, then we might also be getting + // thumbUrl in case of video. So we need to update the attachment with + // both the assetUrl and thumbUrl. + if (response is SendFileResponse) { + updateAttachment( + it.copyWith( + assetUrl: response.file, + thumbUrl: response.thumbUrl, + uploadState: const UploadState.success(), + ), + ); + } else { + updateAttachment( + it.copyWith( + imageUrl: response.file, + uploadState: const UploadState.success(), + ), + ); + } + }) + .catchError((e, stk) { + if (e is StreamChatNetworkError && e.isRequestCancelledError) { + client.logger.info('Attachment ${it.id} upload cancelled'); + + // remove attachment from message if cancelled. + updateAttachment(it, remove: true); + return; + } - client.logger.severe('error uploading the attachment', e, stk); - updateAttachment( - it.copyWith(uploadState: UploadState.failed(error: e.toString())), - ); - }).whenComplete(() { - throttledUpdateAttachment.cancel(); - _cancelableAttachmentUploadRequest.remove(it.id); - }); - })).whenComplete(() { + client.logger.severe('error uploading the attachment', e, stk); + updateAttachment( + it.copyWith(uploadState: UploadState.failed(error: e.toString())), + ); + }) + .whenComplete(() { + throttledUpdateAttachment.cancel(); + _cancelableAttachmentUploadRequest.remove(it.id); + }); + }), + ).whenComplete(() { if (message!.attachments.every((it) => it.uploadState.isSuccess)) { _messageAttachmentsUploadCompleter.remove(messageId)?.complete(message); } @@ -693,9 +690,7 @@ class Channel { // Cancelling previous completer in case it's called again in the process // Eg. Updating the message while the previous call is in progress. - _messageAttachmentsUploadCompleter - .remove(message.id) - ?.completeError(const StreamChatError('Message cancelled')); + _messageAttachmentsUploadCompleter.remove(message.id)?.completeError(const StreamChatError('Message cancelled')); final quotedMessage = state!.messages.firstWhereOrNull( (m) => m.id == message.quotedMessageId, @@ -719,8 +714,7 @@ class Channel { try { if (message.attachments.any((it) => !it.uploadState.isSuccess)) { final attachmentsUploadCompleter = Completer(); - _messageAttachmentsUploadCompleter[message.id] = - attachmentsUploadCompleter; + _messageAttachmentsUploadCompleter[message.id] = attachmentsUploadCompleter; _uploadAttachments( message.id, @@ -752,7 +746,9 @@ class Channel { ), ); - final sentMessage = response.message.syncWith(message).copyWith( + final sentMessage = response.message + .syncWith(message) + .copyWith( // Update the message state to sent. state: MessageState.sent, ); @@ -794,9 +790,7 @@ class Channel { // Cancelling previous completer in case it's called again in the process // Eg. Updating the message while the previous call is in progress. - _messageAttachmentsUploadCompleter - .remove(message.id) - ?.completeError(const StreamChatError('Message cancelled')); + _messageAttachmentsUploadCompleter.remove(message.id)?.completeError(const StreamChatError('Message cancelled')); // ignore: parameter_assignments message = message.copyWith( @@ -815,8 +809,7 @@ class Channel { try { if (message.attachments.any((it) => !it.uploadState.isSuccess)) { final attachmentsUploadCompleter = Completer(); - _messageAttachmentsUploadCompleter[message.id] = - attachmentsUploadCompleter; + _messageAttachmentsUploadCompleter[message.id] = attachmentsUploadCompleter; _uploadAttachments( message.id, @@ -837,7 +830,9 @@ class Channel { ), ); - final updateMessage = response.message.syncWith(message).copyWith( + final updateMessage = response.message + .syncWith(message) + .copyWith( // Update the message state to updated. state: MessageState.updated, ownReactions: message.ownReactions, @@ -880,9 +875,7 @@ class Channel { // Cancelling previous completer in case it's called again in the process // Eg. Updating the message while the previous call is in progress. - _messageAttachmentsUploadCompleter - .remove(message.id) - ?.completeError(const StreamChatError('Message cancelled')); + _messageAttachmentsUploadCompleter.remove(message.id)?.completeError(const StreamChatError('Message cancelled')); // ignore: parameter_assignments message = message.copyWith( @@ -904,7 +897,9 @@ class Channel { ), ); - final updatedMessage = response.message.syncWith(message).copyWith( + final updatedMessage = response.message + .syncWith(message) + .copyWith( // Update the message state to updated. state: MessageState.updated, ownReactions: message.ownReactions, @@ -1128,9 +1123,7 @@ class Channel { Object? /*num|DateTime*/ timeoutOrExpirationDate, }) { assert(() { - if (timeoutOrExpirationDate is! DateTime && - timeoutOrExpirationDate != null && - timeoutOrExpirationDate is! num) { + if (timeoutOrExpirationDate is! DateTime && timeoutOrExpirationDate != null && timeoutOrExpirationDate is! num) { throw ArgumentError('Invalid timeout or Expiration date'); } return true; @@ -1154,13 +1147,12 @@ class Channel { } /// Unpins provided message. - Future unpinMessage(Message message) => - partialUpdateMessage( - message, - set: { - 'pinned': false, - }, - ); + Future unpinMessage(Message message) => partialUpdateMessage( + message, + set: { + 'pinned': false, + }, + ); /// Creates or updates a new [draft] for this channel. Future createDraft( @@ -1605,8 +1597,7 @@ class Channel { /// ```dart /// channel.updateName('Updated channel name'); /// ``` - Future updateName(String name) => - updatePartial(set: {'name': name}); + Future updateName(String name) => updatePartial(set: {'name': name}); /// Update the channel's [image]. /// @@ -1623,8 +1614,7 @@ class Channel { /// ```dart /// channel.updateImage('https://getstream.io/new-image'); /// ``` - Future updateImage(String image) => - updatePartial(set: {'image': image}); + Future updateImage(String image) => updatePartial(set: {'image': image}); /// Update the channel custom data. This replaces all of the channel data /// with the given [channelData]. @@ -1929,11 +1919,10 @@ class Channel { Future getReactions( String messageId, { PaginationParams? pagination, - }) => - _client.getReactions( - messageId, - pagination: pagination, - ); + }) => _client.getReactions( + messageId, + pagination: pagination, + ); /// Retrieves a list of messages by given [messageIDs]. Future getMessagesById( @@ -1950,11 +1939,10 @@ class Channel { Future translateMessage( String messageId, String language, - ) => - _client.translateMessage( - messageId, - language, - ); + ) => _client.translateMessage( + messageId, + language, + ); /// Creates a new channel. Future create() => query(state: false); @@ -2061,15 +2049,14 @@ class Channel { Filter? filter, SortOrder? sort, PaginationParams? pagination, - }) => - _client.queryMembers( - type, - channelId: id, - filter: filter, - members: state?.members, - sort: sort, - pagination: pagination, - ); + }) => _client.queryMembers( + type, + channelId: id, + filter: filter, + members: state?.members, + sort: sort, + pagination: pagination, + ); /// Query channel banned users. Future queryBannedUsers({ @@ -2237,15 +2224,14 @@ class Channel { String? eventType2, String? eventType3, String? eventType4, - ]) => - _client - .on( - eventType, - eventType2, - eventType3, - eventType4, - ) - .where((e) => e.cid == cid); + ]) => _client + .on( + eventType, + eventType2, + eventType3, + eventType4, + ) + .where((e) => e.cid == cid); late final _keyStrokeHandler = KeyStrokeHandler( onStartTyping: startTyping, @@ -2277,10 +2263,12 @@ class Channel { if (!_canSendTypingEvents) return; client.logger.info('start typing'); - await sendEvent(Event( - type: EventType.typingStart, - parentId: parentId, - )); + await sendEvent( + Event( + type: EventType.typingStart, + parentId: parentId, + ), + ); } /// Sends the [EventType.typingStop] event. @@ -2288,10 +2276,12 @@ class Channel { if (!_canSendTypingEvents) return; client.logger.info('stop typing'); - await sendEvent(Event( - type: EventType.typingStop, - parentId: parentId, - )); + await sendEvent( + Event( + type: EventType.typingStop, + parentId: parentId, + ), + ); } /// Call this method to dispose the channel client. @@ -2411,10 +2401,13 @@ class ChannelClientState { _listenChannelPushPreferenceUpdated(); final persistenceClient = _client.chatPersistenceClient; - persistenceClient?.getChannelThreads(_channel.cid!).then((threads) { - // Load all the threads for the channel from the offline storage. - if (threads.isNotEmpty) _threads = threads; - }).then((_) => retryFailedMessages()); + persistenceClient + ?.getChannelThreads(_channel.cid!) + .then((threads) { + // Load all the threads for the channel from the offline storage. + if (threads.isNotEmpty) _threads = threads; + }) + .then((_) => retryFailedMessages()); } final Channel _channel; @@ -2423,35 +2416,34 @@ class ChannelClientState { void _checkExpiredAttachmentMessages(ChannelState channelState) async { final expiredAttachmentMessagesId = channelState.messages - ?.where((m) => - !_updatedMessagesIds.contains(m.id) && - m.attachments.isNotEmpty && - m.attachments.any((e) { - final url = e.imageUrl ?? e.assetUrl; - if (url == null || !url.contains('')) { - return false; - } - try { - final uri = Uri.parse(url); - if (!uri.host.endsWith('stream-io-cdn.com') || - uri.queryParameters['Expires'] == null) { + ?.where( + (m) => + !_updatedMessagesIds.contains(m.id) && + m.attachments.isNotEmpty && + m.attachments.any((e) { + final url = e.imageUrl ?? e.assetUrl; + if (url == null || !url.contains('')) { return false; } - final secondsFromEpoch = - int.parse(uri.queryParameters['Expires']!); - final expiration = DateTime.fromMillisecondsSinceEpoch( - secondsFromEpoch * 1000, - ); - return expiration.isBefore(DateTime.now()); - } catch (_) { - return false; - } - })) + try { + final uri = Uri.parse(url); + if (!uri.host.endsWith('stream-io-cdn.com') || uri.queryParameters['Expires'] == null) { + return false; + } + final secondsFromEpoch = int.parse(uri.queryParameters['Expires']!); + final expiration = DateTime.fromMillisecondsSinceEpoch( + secondsFromEpoch * 1000, + ); + return expiration.isBefore(DateTime.now()); + } catch (_) { + return false; + } + }), + ) .map((e) => e.id) .toList(); - if (expiredAttachmentMessagesId != null && - expiredAttachmentMessagesId.isNotEmpty) { + if (expiredAttachmentMessagesId != null && expiredAttachmentMessagesId.isNotEmpty) { await _channel.initialized; _updatedMessagesIds.addAll(expiredAttachmentMessagesId); _channel.getMessagesById(expiredAttachmentMessagesId); @@ -2459,141 +2451,156 @@ class ChannelClientState { } void _listenMemberAdded() { - _subscriptions.add(_channel.on(EventType.memberAdded).listen((Event e) { - final member = e.member!; - final existingMembers = channelState.members ?? []; + _subscriptions.add( + _channel.on(EventType.memberAdded).listen((Event e) { + final member = e.member!; + final existingMembers = channelState.members ?? []; - updateChannelState( - channelState.copyWith( - members: [...existingMembers, member], - ), - ); - })); + updateChannelState( + channelState.copyWith( + members: [...existingMembers, member], + ), + ); + }), + ); } void _listenMemberRemoved() { - _subscriptions.add(_channel.on(EventType.memberRemoved).listen((Event e) { - final user = e.user!; - final existingRead = channelState.read ?? []; - final existingMembers = channelState.members ?? []; - - updateChannelState( - channelState.copyWith( - read: [...existingRead.where((r) => r.user.id != user.id)], - members: [...existingMembers.where((m) => m.userId != user.id)], - ), - ); - })); + _subscriptions.add( + _channel.on(EventType.memberRemoved).listen((Event e) { + final user = e.user!; + final existingRead = channelState.read ?? []; + final existingMembers = channelState.members ?? []; + + updateChannelState( + channelState.copyWith( + read: [...existingRead.where((r) => r.user.id != user.id)], + members: [...existingMembers.where((m) => m.userId != user.id)], + ), + ); + }), + ); } void _listenMemberUpdated() { _subscriptions // Listen to events containing member users - ..add(_channel.on().listen( - (event) { - final user = event.user; - if (user == null) return; + ..add( + _channel.on().listen( + (event) { + final user = event.user; + if (user == null) return; - final existingMembers = [...?channelState.members]; - final existingMembership = channelState.membership; + final existingMembers = [...?channelState.members]; + final existingMembership = channelState.membership; - // Return if the user is not a existing member of the channel. - if (!existingMembers.any((m) => m.userId == user.id)) return; + // Return if the user is not a existing member of the channel. + if (!existingMembers.any((m) => m.userId == user.id)) return; - Member? maybeUpdateMemberUser(Member? existingMember) { - if (existingMember == null) return null; - if (existingMember.userId == user.id) { - return existingMember.copyWith(user: user); + Member? maybeUpdateMemberUser(Member? existingMember) { + if (existingMember == null) return null; + if (existingMember.userId == user.id) { + return existingMember.copyWith(user: user); + } + return existingMember; } - return existingMember; - } - - updateChannelState( - channelState.copyWith( - membership: maybeUpdateMemberUser(existingMembership), - members: [...existingMembers.map(maybeUpdateMemberUser).nonNulls], - ), - ); - }, - )) + updateChannelState( + channelState.copyWith( + membership: maybeUpdateMemberUser(existingMembership), + members: [...existingMembers.map(maybeUpdateMemberUser).nonNulls], + ), + ); + }, + ), + ) // Listen to member updated events. - ..add(_channel.on(EventType.memberUpdated).listen( - (Event e) { - final member = e.member!; - final existingMembers = channelState.members ?? []; - final existingMembership = channelState.membership; - - Member? maybeUpdateMember(Member? existingMember) { - if (existingMember == null) return null; - if (existingMember.userId == member.userId) return member; - return existingMember; - } + ..add( + _channel.on(EventType.memberUpdated).listen( + (Event e) { + final member = e.member!; + final existingMembers = channelState.members ?? []; + final existingMembership = channelState.membership; + + Member? maybeUpdateMember(Member? existingMember) { + if (existingMember == null) return null; + if (existingMember.userId == member.userId) return member; + return existingMember; + } - updateChannelState( - channelState.copyWith( - membership: maybeUpdateMember(existingMembership), - members: [...existingMembers.map(maybeUpdateMember).nonNulls], - ), - ); - }, - )); + updateChannelState( + channelState.copyWith( + membership: maybeUpdateMember(existingMembership), + members: [...existingMembers.map(maybeUpdateMember).nonNulls], + ), + ); + }, + ), + ); } void _listenChannelUpdated() { - _subscriptions.add(_channel.on(EventType.channelUpdated).listen((Event e) { - final channel = e.channel!; - updateChannelState(channelState.copyWith( - channel: channelState.channel?.merge(channel), - members: channel.members, - )); - })); + _subscriptions.add( + _channel.on(EventType.channelUpdated).listen((Event e) { + final channel = e.channel!; + updateChannelState( + channelState.copyWith( + channel: channelState.channel?.merge(channel), + members: channel.members, + ), + ); + }), + ); } void _listenChannelMessageCount() { - _subscriptions.add(_channel.on().listen( - (Event e) { - final messageCount = e.channelMessageCount; - if (messageCount == null) return; + _subscriptions.add( + _channel.on().listen( + (Event e) { + final messageCount = e.channelMessageCount; + if (messageCount == null) return; - updateChannelState( - channelState.copyWith( - channel: channelState.channel?.copyWith( - messageCount: messageCount, + updateChannelState( + channelState.copyWith( + channel: channelState.channel?.copyWith( + messageCount: messageCount, + ), ), - ), - ); - }, - )); + ); + }, + ), + ); } void _listenChannelTruncated() { - _subscriptions.add(_channel - .on(EventType.channelTruncated, EventType.notificationChannelTruncated) - .listen((event) async { - final channel = event.channel!; - await _client.chatPersistenceClient?.deleteMessageByCid(channel.cid); - truncate(); - if (event.message != null) { - updateMessage(event.message!); - } - })); + _subscriptions.add( + _channel.on(EventType.channelTruncated, EventType.notificationChannelTruncated).listen((event) async { + final channel = event.channel!; + await _client.chatPersistenceClient?.deleteMessageByCid(channel.cid); + truncate(); + if (event.message != null) { + updateMessage(event.message!); + } + }), + ); } void _listenMemberBanned() { - _subscriptions.add(_channel - .on(EventType.userBanned) - .where((it) => it.cid != null) // filters channel ban from app ban - .listen( - (event) async { - final user = event.user!; - final member = await _channel - .queryMembers(filter: Filter.equal('id', user.id)) - .then((it) => it.members.first); - - _updateMember(member); - }, - )); + _subscriptions.add( + _channel + .on(EventType.userBanned) + .where((it) => it.cid != null) // filters channel ban from app ban + .listen( + (event) async { + final user = event.user!; + final member = await _channel + .queryMembers(filter: Filter.equal('id', user.id)) + .then((it) => it.members.first); + + _updateMember(member); + }, + ), + ); } void _listenUserStartWatching() { @@ -2602,12 +2609,14 @@ class ChannelClientState { final watcher = event.user; if (watcher != null) { final existingWatchers = channelState.watchers; - updateChannelState(channelState.copyWith( - watchers: [ - watcher, - ...?existingWatchers?.where((user) => user.id != watcher.id), - ], - )); + updateChannelState( + channelState.copyWith( + watchers: [ + watcher, + ...?existingWatchers?.where((user) => user.id != watcher.id), + ], + ), + ); } }), ); @@ -2619,30 +2628,32 @@ class ChannelClientState { final watcher = event.user; if (watcher != null) { final existingWatchers = channelState.watchers; - updateChannelState(channelState.copyWith( - watchers: [ - ...?existingWatchers?.where((user) => user.id != watcher.id) - ], - )); + updateChannelState( + channelState.copyWith( + watchers: [...?existingWatchers?.where((user) => user.id != watcher.id)], + ), + ); } }), ); } void _listenMemberUnbanned() { - _subscriptions.add(_channel - .on(EventType.userUnbanned) - .where((it) => it.cid != null) // filters channel ban from app ban - .listen( - (event) async { - final user = event.user!; - final member = await _channel - .queryMembers(filter: Filter.equal('id', user.id)) - .then((it) => it.members.first); - - _updateMember(member); - }, - )); + _subscriptions.add( + _channel + .on(EventType.userUnbanned) + .where((it) => it.cid != null) // filters channel ban from app ban + .listen( + (event) async { + final user = event.user!; + final member = await _channel + .queryMembers(filter: Filter.equal('id', user.id)) + .then((it) => it.members.first); + + _updateMember(member); + }, + ), + ); } void _updateMember(Member member) { @@ -2710,184 +2721,194 @@ class ChannelClientState { } void _listenPollUpdated() { - _subscriptions.add(_channel.on(EventType.pollUpdated).listen((event) { - final eventPoll = event.poll; - if (eventPoll == null) return; + _subscriptions.add( + _channel.on(EventType.pollUpdated).listen((event) { + final eventPoll = event.poll; + if (eventPoll == null) return; - final pollMessage = _findPollMessage(eventPoll.id); - if (pollMessage == null) return; + final pollMessage = _findPollMessage(eventPoll.id); + if (pollMessage == null) return; - final oldPoll = pollMessage.poll; + final oldPoll = pollMessage.poll; - final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers; - final ownVotesAndAnswers = - oldPoll?.ownVotesAndAnswers ?? eventPoll.ownVotesAndAnswers; + final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers; + final ownVotesAndAnswers = oldPoll?.ownVotesAndAnswers ?? eventPoll.ownVotesAndAnswers; - final poll = eventPoll.copyWith( - latestAnswers: latestAnswers, - ownVotesAndAnswers: ownVotesAndAnswers, - ); + final poll = eventPoll.copyWith( + latestAnswers: latestAnswers, + ownVotesAndAnswers: ownVotesAndAnswers, + ); - final message = pollMessage.copyWith(poll: poll); - updateMessage(message); - })); + final message = pollMessage.copyWith(poll: poll); + updateMessage(message); + }), + ); } void _listenPollClosed() { - _subscriptions.add(_channel.on(EventType.pollClosed).listen((event) { - final eventPoll = event.poll; - if (eventPoll == null) return; + _subscriptions.add( + _channel.on(EventType.pollClosed).listen((event) { + final eventPoll = event.poll; + if (eventPoll == null) return; - final pollMessage = _findPollMessage(eventPoll.id); - if (pollMessage == null) return; + final pollMessage = _findPollMessage(eventPoll.id); + if (pollMessage == null) return; - final oldPoll = pollMessage.poll; - final poll = oldPoll?.copyWith(isClosed: true) ?? eventPoll; + final oldPoll = pollMessage.poll; + final poll = oldPoll?.copyWith(isClosed: true) ?? eventPoll; - final message = pollMessage.copyWith(poll: poll); - updateMessage(message); - })); + final message = pollMessage.copyWith(poll: poll); + updateMessage(message); + }), + ); } void _listenPollAnswerCasted() { - _subscriptions.add(_channel.on(EventType.pollAnswerCasted).listen((event) { - final (eventPoll, eventPollVote) = (event.poll, event.pollVote); - if (eventPoll == null || eventPollVote == null) return; - - final pollMessage = _findPollMessage(eventPoll.id); - if (pollMessage == null) return; + _subscriptions.add( + _channel.on(EventType.pollAnswerCasted).listen((event) { + final (eventPoll, eventPollVote) = (event.poll, event.pollVote); + if (eventPoll == null || eventPollVote == null) return; - final oldPoll = pollMessage.poll; + final pollMessage = _findPollMessage(eventPoll.id); + if (pollMessage == null) return; - final latestAnswers = { - for (final ans in oldPoll?.latestAnswers ?? []) ans.id: ans, - eventPollVote.id!: eventPollVote, - }; + final oldPoll = pollMessage.poll; - final currentUserId = _client.state.currentUser?.id; - final ownVotesAndAnswers = { - for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, - if (eventPollVote.userId == currentUserId) + final latestAnswers = { + for (final ans in oldPoll?.latestAnswers ?? []) ans.id: ans, eventPollVote.id!: eventPollVote, - }; + }; - final poll = eventPoll.copyWith( - latestAnswers: [...latestAnswers.values], - ownVotesAndAnswers: [...ownVotesAndAnswers.values], - ); + final currentUserId = _client.state.currentUser?.id; + final ownVotesAndAnswers = { + for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, + if (eventPollVote.userId == currentUserId) eventPollVote.id!: eventPollVote, + }; - final message = pollMessage.copyWith(poll: poll); - updateMessage(message); - })); + final poll = eventPoll.copyWith( + latestAnswers: [...latestAnswers.values], + ownVotesAndAnswers: [...ownVotesAndAnswers.values], + ); + + final message = pollMessage.copyWith(poll: poll); + updateMessage(message); + }), + ); } void _listenPollVoteCasted() { - _subscriptions.add(_channel.on(EventType.pollVoteCasted).listen((event) { - final (eventPoll, eventPollVote) = (event.poll, event.pollVote); - if (eventPoll == null || eventPollVote == null) return; + _subscriptions.add( + _channel.on(EventType.pollVoteCasted).listen((event) { + final (eventPoll, eventPollVote) = (event.poll, event.pollVote); + if (eventPoll == null || eventPollVote == null) return; - final pollMessage = _findPollMessage(eventPoll.id); - if (pollMessage == null) return; + final pollMessage = _findPollMessage(eventPoll.id); + if (pollMessage == null) return; - final oldPoll = pollMessage.poll; + final oldPoll = pollMessage.poll; - final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers; - final currentUserId = _client.state.currentUser?.id; - final ownVotesAndAnswers = { - for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, - if (eventPollVote.userId == currentUserId) - eventPollVote.id!: eventPollVote, - }; + final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers; + final currentUserId = _client.state.currentUser?.id; + final ownVotesAndAnswers = { + for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, + if (eventPollVote.userId == currentUserId) eventPollVote.id!: eventPollVote, + }; - final poll = eventPoll.copyWith( - latestAnswers: latestAnswers, - ownVotesAndAnswers: [...ownVotesAndAnswers.values], - ); + final poll = eventPoll.copyWith( + latestAnswers: latestAnswers, + ownVotesAndAnswers: [...ownVotesAndAnswers.values], + ); - final message = pollMessage.copyWith(poll: poll); - updateMessage(message); - })); + final message = pollMessage.copyWith(poll: poll); + updateMessage(message); + }), + ); } void _listenPollAnswerRemoved() { - _subscriptions.add(_channel.on(EventType.pollAnswerRemoved).listen((event) { - final (eventPoll, eventPollVote) = (event.poll, event.pollVote); - if (eventPoll == null || eventPollVote == null) return; + _subscriptions.add( + _channel.on(EventType.pollAnswerRemoved).listen((event) { + final (eventPoll, eventPollVote) = (event.poll, event.pollVote); + if (eventPoll == null || eventPollVote == null) return; - final pollMessage = _findPollMessage(eventPoll.id); - if (pollMessage == null) return; + final pollMessage = _findPollMessage(eventPoll.id); + if (pollMessage == null) return; - final oldPoll = pollMessage.poll; + final oldPoll = pollMessage.poll; - final latestAnswers = { - for (final ans in oldPoll?.latestAnswers ?? []) ans.id: ans, - }..remove(eventPollVote.id); + final latestAnswers = { + for (final ans in oldPoll?.latestAnswers ?? []) ans.id: ans, + }..remove(eventPollVote.id); - final ownVotesAndAnswers = { - for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, - }..remove(eventPollVote.id); + final ownVotesAndAnswers = { + for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, + }..remove(eventPollVote.id); - final poll = eventPoll.copyWith( - latestAnswers: [...latestAnswers.values], - ownVotesAndAnswers: [...ownVotesAndAnswers.values], - ); + final poll = eventPoll.copyWith( + latestAnswers: [...latestAnswers.values], + ownVotesAndAnswers: [...ownVotesAndAnswers.values], + ); - final message = pollMessage.copyWith(poll: poll); - updateMessage(message); - })); + final message = pollMessage.copyWith(poll: poll); + updateMessage(message); + }), + ); } void _listenPollVoteRemoved() { - _subscriptions.add(_channel.on(EventType.pollVoteRemoved).listen((event) { - final (eventPoll, eventPollVote) = (event.poll, event.pollVote); - if (eventPoll == null || eventPollVote == null) return; + _subscriptions.add( + _channel.on(EventType.pollVoteRemoved).listen((event) { + final (eventPoll, eventPollVote) = (event.poll, event.pollVote); + if (eventPoll == null || eventPollVote == null) return; - final pollMessage = _findPollMessage(eventPoll.id); - if (pollMessage == null) return; + final pollMessage = _findPollMessage(eventPoll.id); + if (pollMessage == null) return; - final oldPoll = pollMessage.poll; + final oldPoll = pollMessage.poll; - final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers; - final ownVotesAndAnswers = { - for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, - }..remove(eventPollVote.id); + final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers; + final ownVotesAndAnswers = { + for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, + }..remove(eventPollVote.id); - final poll = eventPoll.copyWith( - latestAnswers: latestAnswers, - ownVotesAndAnswers: [...ownVotesAndAnswers.values], - ); + final poll = eventPoll.copyWith( + latestAnswers: latestAnswers, + ownVotesAndAnswers: [...ownVotesAndAnswers.values], + ); - final message = pollMessage.copyWith(poll: poll); - updateMessage(message); - })); + final message = pollMessage.copyWith(poll: poll); + updateMessage(message); + }), + ); } void _listenPollVoteChanged() { - _subscriptions.add(_channel.on(EventType.pollVoteChanged).listen((event) { - final (eventPoll, eventPollVote) = (event.poll, event.pollVote); - if (eventPoll == null || eventPollVote == null) return; + _subscriptions.add( + _channel.on(EventType.pollVoteChanged).listen((event) { + final (eventPoll, eventPollVote) = (event.poll, event.pollVote); + if (eventPoll == null || eventPollVote == null) return; - final pollMessage = _findPollMessage(eventPoll.id); - if (pollMessage == null) return; + final pollMessage = _findPollMessage(eventPoll.id); + if (pollMessage == null) return; - final oldPoll = pollMessage.poll; + final oldPoll = pollMessage.poll; - final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers; - final currentUserId = _client.state.currentUser?.id; - final ownVotesAndAnswers = { - for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, - if (eventPollVote.userId == currentUserId) - eventPollVote.id!: eventPollVote, - }; + final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers; + final currentUserId = _client.state.currentUser?.id; + final ownVotesAndAnswers = { + for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, + if (eventPollVote.userId == currentUserId) eventPollVote.id!: eventPollVote, + }; - final poll = eventPoll.copyWith( - latestAnswers: latestAnswers, - ownVotesAndAnswers: [...ownVotesAndAnswers.values], - ); + final poll = eventPoll.copyWith( + latestAnswers: latestAnswers, + ownVotesAndAnswers: [...ownVotesAndAnswers.values], + ); - final message = pollMessage.copyWith(poll: poll); - updateMessage(message); - })); + final message = pollMessage.copyWith(poll: poll); + updateMessage(message); + }), + ); } void _listenDraftUpdated() { @@ -3046,8 +3067,9 @@ class ChannelClientState { final currentUserId = _channel.client.state.currentUser?.id; final currentMessage = switch (currentUserId) { - final userId? when userId == eventReaction.userId => - message.deleteMyReaction(reactionType: eventReaction.type), + final userId? when userId == eventReaction.userId => message.deleteMyReaction( + reactionType: eventReaction.type, + ), _ => message, }; @@ -3063,31 +3085,32 @@ class ChannelClientState { } void _listenReactionNew() { - _subscriptions.add(_channel.on(EventType.reactionNew).listen((event) { - final (eventReaction, eventMessage) = (event.reaction, event.message); - if (eventReaction == null || eventMessage == null) return; + _subscriptions.add( + _channel.on(EventType.reactionNew).listen((event) { + final (eventReaction, eventMessage) = (event.reaction, event.message); + if (eventReaction == null || eventMessage == null) return; - final messageId = eventMessage.id; - final parentId = eventMessage.parentId; + final messageId = eventMessage.id; + final parentId = eventMessage.parentId; - for (final message in [...messages, ...?threads[parentId]]) { - if (message.id == messageId) { - final currentUserId = _channel.client.state.currentUser?.id; + for (final message in [...messages, ...?threads[parentId]]) { + if (message.id == messageId) { + final currentUserId = _channel.client.state.currentUser?.id; - final currentMessage = switch (currentUserId) { - final userId? when userId == eventReaction.userId => - message.addMyReaction(eventReaction), - _ => message, - }; + final currentMessage = switch (currentUserId) { + final userId? when userId == eventReaction.userId => message.addMyReaction(eventReaction), + _ => message, + }; - return updateMessage( - eventMessage.copyWith( - ownReactions: currentMessage.ownReactions, - ), - ); + return updateMessage( + eventMessage.copyWith( + ownReactions: currentMessage.ownReactions, + ), + ); + } } - } - })); + }), + ); } void _listenReactionUpdated() { @@ -3122,39 +3145,45 @@ class ChannelClientState { } void _listenMessageUpdated() { - _subscriptions.add(_channel.on(EventType.messageUpdated).listen((event) { - final message = event.message; - if (message == null) return; + _subscriptions.add( + _channel.on(EventType.messageUpdated).listen((event) { + final message = event.message; + if (message == null) return; - return updateMessage(message); - })); + return updateMessage(message); + }), + ); } void _listenMessageDeleted() { - _subscriptions.add(_channel.on(EventType.messageDeleted).listen((event) { - final hardDelete = event.hardDelete ?? false; + _subscriptions.add( + _channel.on(EventType.messageDeleted).listen((event) { + final hardDelete = event.hardDelete ?? false; - final message = event.message!.copyWith( - // TODO: Remove once deletedForMe is properly enriched on the backend. - deletedForMe: event.deletedForMe, - ); + final message = event.message!.copyWith( + // TODO: Remove once deletedForMe is properly enriched on the backend. + deletedForMe: event.deletedForMe, + ); - return deleteMessage(message, hardDelete: hardDelete); - })); + return deleteMessage(message, hardDelete: hardDelete); + }), + ); } void _listenMessageNew() { - _subscriptions.add(_channel - .on( - EventType.messageNew, - EventType.notificationMessageNew, - ) - .listen((event) { - final message = event.message; - if (message == null) return; + _subscriptions.add( + _channel + .on( + EventType.messageNew, + EventType.notificationMessageNew, + ) + .listen((event) { + final message = event.message; + if (message == null) return; - return addNewMessage(message); - })); + return addNewMessage(message); + }), + ); } /// Adds a new message to the channel state and updates the unread count. @@ -3345,27 +3374,22 @@ class ChannelClientState { List get messages => _channelState.messages ?? []; /// Channel message list as a stream. - Stream> get messagesStream => channelStateStream - .map((cs) => cs.messages ?? []) - .distinct(const ListEquality().equals); + Stream> get messagesStream => + channelStateStream.map((cs) => cs.messages ?? []).distinct(const ListEquality().equals); /// Channel pinned message list. - List get pinnedMessages => - _channelState.pinnedMessages ?? []; + List get pinnedMessages => _channelState.pinnedMessages ?? []; /// Channel pinned message list as a stream. - Stream> get pinnedMessagesStream => channelStateStream - .map((cs) => cs.pinnedMessages ?? []) - .distinct(const ListEquality().equals); + Stream> get pinnedMessagesStream => + channelStateStream.map((cs) => cs.pinnedMessages ?? []).distinct(const ListEquality().equals); /// Channel pending message list. - List get pendingMessages => - _channelState.pendingMessages ?? []; + List get pendingMessages => _channelState.pendingMessages ?? []; /// Channel pending message list as a stream. - Stream> get pendingMessagesStream => channelStateStream - .map((cs) => cs.pendingMessages ?? []) - .distinct(const ListEquality().equals); + Stream> get pendingMessagesStream => + channelStateStream.map((cs) => cs.pendingMessages ?? []).distinct(const ListEquality().equals); /// Get channel last message. Message? get lastMessage => messages.lastOrNull; @@ -3376,38 +3400,32 @@ class ChannelClientState { } /// Channel members list. - List get members => (_channelState.members ?? []) - .map((e) => e.copyWith(user: _client.state.users[e.user!.id])) - .toList(); + List get members => + (_channelState.members ?? []).map((e) => e.copyWith(user: _client.state.users[e.user!.id])).toList(); /// Channel members list as a stream. - Stream> get membersStream => CombineLatestStream.combine2< - List?, Map, List>( + Stream> get membersStream => + CombineLatestStream.combine2?, Map, List>( channelStateStream.map((cs) => cs.members), _client.state.usersStream, - (members, users) => - [...?members?.map((e) => e!.copyWith(user: users[e.user!.id]))], + (members, users) => [...?members?.map((e) => e!.copyWith(user: users[e.user!.id]))], ).distinct(const ListEquality().equals); /// Channel watcher count. int? get watcherCount => _channelState.watcherCount; /// Channel watcher count as a stream. - Stream get watcherCountStream => - channelStateStream.map((cs) => cs.watcherCount); + Stream get watcherCountStream => channelStateStream.map((cs) => cs.watcherCount); /// Channel watchers list. - List get watchers => (_channelState.watchers ?? []) - .map((e) => _client.state.users[e.id] ?? e) - .toList(); + List get watchers => (_channelState.watchers ?? []).map((e) => _client.state.users[e.id] ?? e).toList(); /// Channel watchers list as a stream. - Stream> get watchersStream => CombineLatestStream.combine2< - List?, Map, List>( - channelStateStream.map((cs) => cs.watchers), - _client.state.usersStream, - (watchers, users) => [...?watchers?.map((e) => users[e.id] ?? e)], - ).distinct(const ListEquality().equals); + Stream> get watchersStream => CombineLatestStream.combine2?, Map, List>( + channelStateStream.map((cs) => cs.watchers), + _client.state.usersStream, + (watchers, users) => [...?watchers?.map((e) => users[e.id] ?? e)], + ).distinct(const ListEquality().equals); /// Channel active live locations. List get activeLiveLocations { @@ -3415,9 +3433,8 @@ class ChannelClientState { } /// Channel active live locations as a stream. - Stream> get activeLiveLocationsStream => channelStateStream - .map((cs) => cs.activeLiveLocations ?? []) - .distinct(const ListEquality().equals); + Stream> get activeLiveLocationsStream => + channelStateStream.map((cs) => cs.activeLiveLocations ?? []).distinct(const ListEquality().equals); /// Channel draft. Draft? get draft => _channelState.draft; @@ -3429,8 +3446,8 @@ class ChannelClientState { /// Channel member for the current user. Member? get currentUserMember => members.firstWhereOrNull( - (m) => m.user?.id == _client.state.currentUser?.id, - ); + (m) => m.user?.id == _client.state.currentUser?.id, + ); /// Channel role for the current user String? get currentUserChannelRole => currentUserMember?.channelRole; @@ -3554,8 +3571,7 @@ class ChannelClientState { ); } - int _sortByCreatedAt(Message a, Message b) => - a.createdAt.compareTo(b.createdAt); + int _sortByCreatedAt(Message a, Message b) => a.createdAt.compareTo(b.createdAt); /// The channel state related to this client. ChannelState get _channelState => _channelStateController.value; @@ -3639,13 +3655,11 @@ class ChannelClientState { /// /// This stream emits a new value whenever the draft associated with the /// specified thread is updated or removed. - Stream threadDraftStream(String parentId) => channelStateStream - .map((cs) => _getThreadDraft(parentId, cs.messages)) - .distinct(); + Stream threadDraftStream(String parentId) => + channelStateStream.map((cs) => _getThreadDraft(parentId, cs.messages)).distinct(); /// Channel related typing users stream. - Stream> get typingEventsStream => - _typingEventsController.stream; + Stream> get typingEventsStream => _typingEventsController.stream; /// Channel related typing users last value. Map get typingEvents => _typingEventsController.value; @@ -3694,8 +3708,7 @@ class ChannelClientState { (_) { final now = DateTime.now(); typingEvents.forEach((user, event) { - if (now.difference(event.createdAt).inSeconds > - incomingTypingStartEventTimeout) { + if (now.difference(event.createdAt).inSeconds > incomingTypingStartEventTimeout) { _client.handleEvent( Event( type: EventType.typingStop, @@ -3718,21 +3731,23 @@ class ChannelClientState { const Duration(seconds: 30), (_) { final now = DateTime.now(); - var expiredMessages = channelState.pinnedMessages - ?.where((m) => m.pinExpires?.isBefore(now) == true) - .toList(); + var expiredMessages = channelState.pinnedMessages?.where((m) => m.pinExpires?.isBefore(now) == true).toList(); if (expiredMessages != null && expiredMessages.isNotEmpty) { expiredMessages = expiredMessages - .map((m) => m.copyWith( - pinExpires: null, - pinned: false, - )) + .map( + (m) => m.copyWith( + pinExpires: null, + pinned: false, + ), + ) .toList(); - updateChannelState(_channelState.copyWith( - pinnedMessages: pinnedMessages.where(_pinIsValid).toList(), - messages: expiredMessages, - )); + updateChannelState( + _channelState.copyWith( + pinnedMessages: pinnedMessages.where(_pinIsValid).toList(), + messages: expiredMessages, + ), + ); } }, ); @@ -4140,16 +4155,18 @@ class ChannelClientState { if (toRemove.isEmpty) return existing; final toRemoveIds = toRemove.map((m) => m.id).toSet(); - final updatedMessages = existing.where((it) { - // Remove the message if it's in the toRemove list. - return !toRemoveIds.contains(it.id); - }).map((it) { - // Continue if the message doesn't quote any of the deleted messages. - if (!toRemoveIds.contains(it.quotedMessageId)) return it; - - // Setting it to null will remove the quoted message from the message. - return it.copyWith(quotedMessageId: null, quotedMessage: null); - }); + final updatedMessages = existing + .where((it) { + // Remove the message if it's in the toRemove list. + return !toRemoveIds.contains(it.id); + }) + .map((it) { + // Continue if the message doesn't quote any of the deleted messages. + if (!toRemoveIds.contains(it.quotedMessageId)) return it; + + // Setting it to null will remove the quoted message from the message. + return it.copyWith(quotedMessageId: null, quotedMessage: null); + }); return updatedMessages; } diff --git a/packages/stream_chat/lib/src/client/channel_delivery_reporter.dart b/packages/stream_chat/lib/src/client/channel_delivery_reporter.dart index 001f19625a..1342d397a3 100644 --- a/packages/stream_chat/lib/src/client/channel_delivery_reporter.dart +++ b/packages/stream_chat/lib/src/client/channel_delivery_reporter.dart @@ -10,9 +10,10 @@ import 'package:synchronized/synchronized.dart'; /// /// Each [MessageDeliveryInfo] represents an acknowledgment that the current /// user has received a message. -typedef MarkChannelsDelivered = Future Function( - Iterable deliveries, -); +typedef MarkChannelsDelivered = + Future Function( + Iterable deliveries, + ); /// Manages the delivery reporting for channel messages. /// @@ -31,8 +32,8 @@ class ChannelDeliveryReporter { Logger? logger, required this.onMarkChannelsDelivered, Duration throttleDuration = const Duration(seconds: 1), - }) : _logger = logger, - _markAsDeliveredThrottleDuration = throttleDuration; + }) : _logger = logger, + _markAsDeliveredThrottleDuration = throttleDuration; final Logger? _logger; final Duration _markAsDeliveredThrottleDuration; @@ -43,7 +44,7 @@ class ChannelDeliveryReporter { final MarkChannelsDelivered onMarkChannelsDelivered; final _deliveryCandidatesLock = Lock(); - final _deliveryCandidates = {}; + final _deliveryCandidates = {}; /// Submits [channels] for delivery reporting. /// diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 6fb7aac5bf..f7b529d583 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -90,8 +90,7 @@ class StreamChatClient { Duration receiveTimeout = const Duration(seconds: 6), StreamChatApi? chatApi, WebSocket? ws, - AttachmentFileUploaderProvider attachmentFileUploaderProvider = - StreamAttachmentFileUploader.new, + AttachmentFileUploaderProvider attachmentFileUploaderProvider = StreamAttachmentFileUploader.new, Iterable? chatApiInterceptors, HttpClientAdapter? httpClientAdapter, }) { @@ -103,7 +102,8 @@ class StreamChatClient { receiveTimeout: receiveTimeout, ); - _chatApi = chatApi ?? + _chatApi = + chatApi ?? StreamChatApi( apiKey, options: options, @@ -116,7 +116,8 @@ class StreamChatClient { httpClientAdapter: httpClientAdapter, ); - _ws = ws ?? + _ws = + ws ?? WebSocket( apiKey: apiKey, baseUrl: baseWsUrl ?? options.baseUrl, @@ -126,7 +127,8 @@ class StreamChatClient { logger: detachedLogger('🔌'), ); - _retryPolicy = retryPolicy ?? + _retryPolicy = + retryPolicy ?? RetryPolicy( shouldRetry: (_, __, error) { return error is StreamChatNetworkError && error.isRetriable; @@ -292,12 +294,11 @@ class StreamChatClient { User user, String token, { bool connectWebSocket = true, - }) => - _connectUser( - user, - token: Token.fromRawValue(token), - connectWebSocket: connectWebSocket, - ); + }) => _connectUser( + user, + token: Token.fromRawValue(token), + connectWebSocket: connectWebSocket, + ); /// Connects the current user using the [tokenProvider] to fetch the token. /// It returns a [Future] that resolves when the connection is setup. @@ -305,12 +306,11 @@ class StreamChatClient { User user, TokenProvider tokenProvider, { bool connectWebSocket = true, - }) => - _connectUser( - user, - provider: tokenProvider, - connectWebSocket: connectWebSocket, - ); + }) => _connectUser( + user, + provider: tokenProvider, + connectWebSocket: connectWebSocket, + ); /// Connects the current user with an anonymous id, this triggers a connection /// to the API. It returns a [Future] that resolves when the connection is @@ -523,10 +523,12 @@ class StreamChatClient { final isConnected = currStatus == ConnectionStatus.connected; // Notify the connection status change event - handleEvent(Event( - type: EventType.connectionChanged, - online: isConnected, - )); + handleEvent( + Event( + type: EventType.connectionChanged, + online: isConnected, + ), + ); final connectionRecovered = !wasConnected && isConnected; @@ -543,10 +545,12 @@ class StreamChatClient { if (persistenceEnabled) await sync(cids: cids); } - handleEvent(Event( - type: EventType.connectionRecovered, - online: true, - )); + handleEvent( + Event( + type: EventType.connectionRecovered, + online: true, + ), + ); } } @@ -559,11 +563,10 @@ class StreamChatClient { String? eventType4, ]) { if (eventType == null || eventType == EventType.any) return eventStream; - return eventStream.where((event) => - event.type == eventType || - event.type == eventType2 || - event.type == eventType3 || - event.type == eventType4); + return eventStream.where( + (event) => + event.type == eventType || event.type == eventType2 || event.type == eventType3 || event.type == eventType4, + ); } // Lock to make sure only one sync process is running at a time. @@ -676,26 +679,29 @@ class StreamChatClient { } try { - final newQueryChannelsFuture = queryChannelsOnline( - filter: filter, - sort: channelStateSort, - state: state, - watch: watch, - presence: presence, - memberLimit: memberLimit, - messageLimit: messageLimit, - paginationParams: paginationParams, - waitForConnect: waitForConnect, - ).timeout( - const Duration(seconds: 30), - onTimeout: () { - logger.warning('Online channel query timed out'); - throw TimeoutException('Channel query timed out'); - }, - ).whenComplete(() { - // Always clean up cache reference when done - _queryChannelsStreams.remove(hash); - }); + final newQueryChannelsFuture = + queryChannelsOnline( + filter: filter, + sort: channelStateSort, + state: state, + watch: watch, + presence: presence, + memberLimit: memberLimit, + messageLimit: messageLimit, + paginationParams: paginationParams, + waitForConnect: waitForConnect, + ) + .timeout( + const Duration(seconds: 30), + onTimeout: () { + logger.warning('Online channel query timed out'); + throw TimeoutException('Channel query timed out'); + }, + ) + .whenComplete(() { + // Always clean up cache reference when done + _queryChannelsStreams.remove(hash); + }); // Store the future in cache _queryChannelsStreams[hash] = newQueryChannelsFuture; @@ -762,10 +768,7 @@ class StreamChatClient { final channels = res.channels; - final users = channels - .expand((it) => it.members ?? []) - .map((it) => it.user) - .toList(growable: false); + final users = channels.expand((it) => it.members ?? []).map((it) => it.user).toList(growable: false); this.state.updateUsers(users); @@ -854,12 +857,11 @@ class StreamChatClient { required Filter filter, SortOrder? sort, PaginationParams? pagination, - }) => - _chatApi.moderation.queryBannedUsers( - filter: filter, - sort: sort, - pagination: pagination, - ); + }) => _chatApi.moderation.queryBannedUsers( + filter: filter, + sort: sort, + pagination: pagination, + ); /// A message search. Future search( @@ -868,14 +870,13 @@ class StreamChatClient { SortOrder? sort, PaginationParams? paginationParams, Filter? messageFilters, - }) => - _chatApi.general.searchMessages( - filter, - query: query, - sort: sort, - pagination: paginationParams, - messageFilters: messageFilters, - ); + }) => _chatApi.general.searchMessages( + filter, + query: query, + sort: sort, + pagination: paginationParams, + messageFilters: messageFilters, + ); /// Send a [file] to the [channelId] of type [channelType] Future sendFile( @@ -885,15 +886,14 @@ class StreamChatClient { ProgressCallback? onSendProgress, CancelToken? cancelToken, Map? extraData, - }) => - _chatApi.fileUploader.sendFile( - file, - channelId, - channelType, - onSendProgress: onSendProgress, - cancelToken: cancelToken, - extraData: extraData, - ); + }) => _chatApi.fileUploader.sendFile( + file, + channelId, + channelType, + onSendProgress: onSendProgress, + cancelToken: cancelToken, + extraData: extraData, + ); /// Send a [image] to the [channelId] of type [channelType] Future sendImage( @@ -903,15 +903,14 @@ class StreamChatClient { ProgressCallback? onSendProgress, CancelToken? cancelToken, Map? extraData, - }) => - _chatApi.fileUploader.sendImage( - image, - channelId, - channelType, - onSendProgress: onSendProgress, - cancelToken: cancelToken, - extraData: extraData, - ); + }) => _chatApi.fileUploader.sendImage( + image, + channelId, + channelType, + onSendProgress: onSendProgress, + cancelToken: cancelToken, + extraData: extraData, + ); /// Delete a file from this channel Future deleteFile( @@ -920,14 +919,13 @@ class StreamChatClient { String channelType, { CancelToken? cancelToken, Map? extraData, - }) => - _chatApi.fileUploader.deleteFile( - url, - channelId, - channelType, - cancelToken: cancelToken, - extraData: extraData, - ); + }) => _chatApi.fileUploader.deleteFile( + url, + channelId, + channelType, + cancelToken: cancelToken, + extraData: extraData, + ); /// Delete an image from this channel Future deleteImage( @@ -936,14 +934,13 @@ class StreamChatClient { String channelType, { CancelToken? cancelToken, Map? extraData, - }) => - _chatApi.fileUploader.deleteImage( - url, - channelId, - channelType, - cancelToken: cancelToken, - extraData: extraData, - ); + }) => _chatApi.fileUploader.deleteImage( + url, + channelId, + channelType, + cancelToken: cancelToken, + extraData: extraData, + ); /// Upload an image to the Stream CDN /// @@ -955,12 +952,11 @@ class StreamChatClient { AttachmentFile image, { ProgressCallback? onUploadProgress, CancelToken? cancelToken, - }) => - _chatApi.fileUploader.uploadImage( - image, - onSendProgress: onUploadProgress, - cancelToken: cancelToken, - ); + }) => _chatApi.fileUploader.uploadImage( + image, + onSendProgress: onUploadProgress, + cancelToken: cancelToken, + ); /// Upload a file to the Stream CDN /// @@ -972,12 +968,11 @@ class StreamChatClient { AttachmentFile file, { ProgressCallback? onUploadProgress, CancelToken? cancelToken, - }) => - _chatApi.fileUploader.uploadFile( - file, - onSendProgress: onUploadProgress, - cancelToken: cancelToken, - ); + }) => _chatApi.fileUploader.uploadFile( + file, + onSendProgress: onUploadProgress, + cancelToken: cancelToken, + ); /// Remove an image from the Stream CDN using its [url]. /// @@ -987,11 +982,10 @@ class StreamChatClient { Future removeImage( String url, { CancelToken? cancelToken, - }) => - _chatApi.fileUploader.removeImage( - url, - cancelToken: cancelToken, - ); + }) => _chatApi.fileUploader.removeImage( + url, + cancelToken: cancelToken, + ); /// Remove a file from the Stream CDN using its [url]. /// @@ -1001,11 +995,10 @@ class StreamChatClient { Future removeFile( String url, { CancelToken? cancelToken, - }) => - _chatApi.fileUploader.removeFile( - url, - cancelToken: cancelToken, - ); + }) => _chatApi.fileUploader.removeFile( + url, + cancelToken: cancelToken, + ); /// Replaces the [channelId] of type [ChannelType] data with [data]. /// @@ -1015,13 +1008,12 @@ class StreamChatClient { String channelType, Map data, { Message? message, - }) => - _chatApi.channel.updateChannel( - channelId, - channelType, - data, - message: message, - ); + }) => _chatApi.channel.updateChannel( + channelId, + channelType, + data, + message: message, + ); /// Partial update for the [channelId] of type [ChannelType]. Sets the /// data provided in [set], and removes the attributes given in [unset]. @@ -1032,32 +1024,29 @@ class StreamChatClient { String channelType, { Map? set, List? unset, - }) => - _chatApi.channel.updateChannelPartial( - channelId, - channelType, - set: set, - unset: unset, - ); + }) => _chatApi.channel.updateChannelPartial( + channelId, + channelType, + set: set, + unset: unset, + ); /// Add a device for Push Notifications. Future addDevice( String id, PushProvider pushProvider, { String? pushProviderName, - }) => - _chatApi.device.addDevice( - id, - pushProvider, - pushProviderName: pushProviderName, - ); + }) => _chatApi.device.addDevice( + id, + pushProvider, + pushProviderName: pushProviderName, + ); /// Gets a list of user devices. Future getDevices() => _chatApi.device.getDevices(); /// Remove a user's device. - Future removeDevice(String id) => - _chatApi.device.removeDevice(id); + Future removeDevice(String id) => _chatApi.device.removeDevice(id); /// Set push preferences for the current user. /// @@ -1158,13 +1147,12 @@ class StreamChatClient { String channelType, { String? channelId, Map? channelData, - }) => - queryChannel( - channelType, - channelId: channelId, - state: false, - channelData: channelData, - ); + }) => queryChannel( + channelType, + channelId: channelId, + state: false, + channelData: channelData, + ); /// watches the provided channel /// Creates first if not yet created @@ -1172,13 +1160,12 @@ class StreamChatClient { String channelType, { String? channelId, Map? channelData, - }) => - queryChannel( - channelType, - channelId: channelId, - watch: true, - channelData: channelData, - ); + }) => queryChannel( + channelType, + channelId: channelId, + watch: true, + channelData: channelData, + ); /// Query the API, get messages, members or other channel fields /// Creates the channel first if not yet created @@ -1192,18 +1179,17 @@ class StreamChatClient { PaginationParams? messagesPagination, PaginationParams? membersPagination, PaginationParams? watchersPagination, - }) => - _chatApi.channel.queryChannel( - channelType, - channelId: channelId, - channelData: channelData, - state: state, - watch: watch, - presence: presence, - messagesPagination: messagesPagination, - membersPagination: membersPagination, - watchersPagination: watchersPagination, - ); + }) => _chatApi.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: state, + watch: watch, + presence: presence, + messagesPagination: messagesPagination, + membersPagination: membersPagination, + watchersPagination: watchersPagination, + ); /// Query channel members Future queryMembers( @@ -1213,15 +1199,14 @@ class StreamChatClient { List? members, SortOrder? sort, PaginationParams? pagination, - }) => - _chatApi.general.queryMembers( - channelType, - channelId: channelId, - filter: filter, - members: members, - sort: sort, - pagination: pagination, - ); + }) => _chatApi.general.queryMembers( + channelType, + channelId: channelId, + filter: filter, + members: members, + sort: sort, + pagination: pagination, + ); /// Hides the channel from [queryChannels] for the user /// until a message is added If [clearHistory] is set to true - all messages @@ -1230,32 +1215,29 @@ class StreamChatClient { String channelId, String channelType, { bool clearHistory = false, - }) => - _chatApi.channel.hideChannel( - channelId, - channelType, - clearHistory: clearHistory, - ); + }) => _chatApi.channel.hideChannel( + channelId, + channelType, + clearHistory: clearHistory, + ); /// Removes the hidden status for the channel Future showChannel( String channelId, String channelType, - ) => - _chatApi.channel.showChannel( - channelId, - channelType, - ); + ) => _chatApi.channel.showChannel( + channelId, + channelType, + ); /// Delete this channel. Messages are permanently removed. Future deleteChannel( String channelId, String channelType, - ) => - _chatApi.channel.deleteChannel( - channelId, - channelType, - ); + ) => _chatApi.channel.deleteChannel( + channelId, + channelType, + ); /// Removes all messages from the channel up to [truncatedAt] or now if /// [truncatedAt] is not provided. @@ -1267,52 +1249,47 @@ class StreamChatClient { Message? message, bool? skipPush, DateTime? truncatedAt, - }) => - _chatApi.channel.truncateChannel( - channelId, - channelType, - message: message, - skipPush: skipPush, - truncatedAt: truncatedAt, - ); + }) => _chatApi.channel.truncateChannel( + channelId, + channelType, + message: message, + skipPush: skipPush, + truncatedAt: truncatedAt, + ); /// Mutes the channel Future muteChannel( String channelCid, { Duration? expiration, - }) => - _chatApi.moderation.muteChannel( - channelCid, - expiration: expiration, - ); + }) => _chatApi.moderation.muteChannel( + channelCid, + expiration: expiration, + ); /// Unmutes the channel - Future unmuteChannel(String channelCid) => - _chatApi.moderation.unmuteChannel(channelCid); + Future unmuteChannel(String channelCid) => _chatApi.moderation.unmuteChannel(channelCid); /// Accept invitation to the channel Future acceptChannelInvite( String channelId, String channelType, { Message? message, - }) => - _chatApi.channel.acceptChannelInvite( - channelId, - channelType, - message: message, - ); + }) => _chatApi.channel.acceptChannelInvite( + channelId, + channelType, + message: message, + ); /// Reject invitation to the channel Future rejectChannelInvite( String channelId, String channelType, { Message? message, - }) => - _chatApi.channel.rejectChannelInvite( - channelId, - channelType, - message: message, - ); + }) => _chatApi.channel.rejectChannelInvite( + channelId, + channelType, + message: message, + ); /// Add members to the channel Future addChannelMembers( @@ -1322,15 +1299,14 @@ class StreamChatClient { Message? message, bool hideHistory = false, DateTime? hideHistoryBefore, - }) => - _chatApi.channel.addMembers( - channelId, - channelType, - memberIds, - message: message, - hideHistory: hideHistory, - hideHistoryBefore: hideHistoryBefore, - ); + }) => _chatApi.channel.addMembers( + channelId, + channelType, + memberIds, + message: message, + hideHistory: hideHistory, + hideHistoryBefore: hideHistoryBefore, + ); /// Remove members from the channel Future removeChannelMembers( @@ -1338,13 +1314,12 @@ class StreamChatClient { String channelType, List memberIds, { Message? message, - }) => - _chatApi.channel.removeMembers( - channelId, - channelType, - memberIds, - message: message, - ); + }) => _chatApi.channel.removeMembers( + channelId, + channelType, + memberIds, + message: message, + ); /// Invite members to the channel Future inviteChannelMembers( @@ -1352,23 +1327,21 @@ class StreamChatClient { String channelType, List memberIds, { Message? message, - }) => - _chatApi.channel.inviteChannelMembers( - channelId, - channelType, - memberIds, - message: message, - ); + }) => _chatApi.channel.inviteChannelMembers( + channelId, + channelType, + memberIds, + message: message, + ); /// Stop watching the channel Future stopChannelWatching( String channelId, String channelType, - ) => - _chatApi.channel.stopWatching( - channelId, - channelType, - ); + ) => _chatApi.channel.stopWatching( + channelId, + channelType, + ); /// Send action for a specific message of this channel Future sendAction( @@ -1376,13 +1349,12 @@ class StreamChatClient { String channelType, String messageId, Map formData, - ) => - _chatApi.message.sendAction( - channelId, - channelType, - messageId, - formData, - ); + ) => _chatApi.message.sendAction( + channelId, + channelType, + messageId, + formData, + ); /// Mark [channelId] of type [channelType] all messages as read /// Optionally provide a [messageId] if you want to mark a @@ -1391,12 +1363,11 @@ class StreamChatClient { String channelId, String channelType, { String? messageId, - }) => - _chatApi.channel.markRead( - channelId, - channelType, - messageId: messageId, - ); + }) => _chatApi.channel.markRead( + channelId, + channelType, + messageId: messageId, + ); /// Marks the [channelId] of type [channelType] as unread /// by a given [messageId]. @@ -1406,12 +1377,11 @@ class StreamChatClient { String channelId, String channelType, String messageId, - ) => - _chatApi.channel.markUnread( - channelId, - channelType, - messageId, - ); + ) => _chatApi.channel.markUnread( + channelId, + channelType, + messageId, + ); /// Marks the [channelId] of type [channelType] as unread /// by a given [timestamp]. @@ -1421,12 +1391,11 @@ class StreamChatClient { String channelId, String channelType, DateTime timestamp, - ) => - _chatApi.channel.markUnreadByTimestamp( - channelId, - channelType, - timestamp, - ); + ) => _chatApi.channel.markUnreadByTimestamp( + channelId, + channelType, + timestamp, + ); /// Mark the thread with [threadId] in the channel with [channelId] of type /// [channelType] as read. @@ -1434,12 +1403,11 @@ class StreamChatClient { String channelId, String channelType, String threadId, - ) => - _chatApi.channel.markThreadRead( - channelId, - channelType, - threadId, - ); + ) => _chatApi.channel.markThreadRead( + channelId, + channelType, + threadId, + ); /// Mark the thread with [threadId] in the channel with [channelId] of type /// [channelType] as unread. @@ -1447,24 +1415,20 @@ class StreamChatClient { String channelId, String channelType, String threadId, - ) => - _chatApi.channel.markThreadUnread( - channelId, - channelType, - threadId, - ); + ) => _chatApi.channel.markThreadUnread( + channelId, + channelType, + threadId, + ); /// Creates a new Poll - Future createPoll(Poll poll) => - _chatApi.polls.createPoll(poll); + Future createPoll(Poll poll) => _chatApi.polls.createPoll(poll); /// Retrieves a Poll by [pollId] - Future getPoll(String pollId) => - _chatApi.polls.getPoll(pollId); + Future getPoll(String pollId) => _chatApi.polls.getPoll(pollId); /// Updates a Poll - Future updatePoll(Poll poll) => - _chatApi.polls.updatePoll(poll); + Future updatePoll(Poll poll) => _chatApi.polls.updatePoll(poll); /// Partially updates a Poll by [pollId]. /// @@ -1474,50 +1438,46 @@ class StreamChatClient { String pollId, { Map? set, List? unset, - }) => - _chatApi.polls.partialUpdatePoll( - pollId, - set: set, - unset: unset, - ); + }) => _chatApi.polls.partialUpdatePoll( + pollId, + set: set, + unset: unset, + ); /// Deletes the Poll by [pollId]. - Future deletePoll(String pollId) => - _chatApi.polls.deletePoll(pollId); + Future deletePoll(String pollId) => _chatApi.polls.deletePoll(pollId); /// Marks the Poll [pollId] as closed. - Future closePoll(String pollId) => - partialUpdatePoll(pollId, set: { - 'is_closed': true, - }); + Future closePoll(String pollId) => partialUpdatePoll( + pollId, + set: { + 'is_closed': true, + }, + ); /// Creates a new Poll Option for the Poll [pollId]. Future createPollOption( String pollId, PollOption option, - ) => - _chatApi.polls.createPollOption(pollId, option); + ) => _chatApi.polls.createPollOption(pollId, option); /// Retrieves a Poll Option by [optionId] for the Poll [pollId]. Future getPollOption( String pollId, String optionId, - ) => - _chatApi.polls.getPollOption(pollId, optionId); + ) => _chatApi.polls.getPollOption(pollId, optionId); /// Updates a Poll Option for the Poll [pollId]. Future updatePollOption( String pollId, PollOption option, - ) => - _chatApi.polls.updatePollOption(pollId, option); + ) => _chatApi.polls.updatePollOption(pollId, option); /// Deletes a Poll Option by [optionId] for the Poll [pollId]. Future deletePollOption( String pollId, String optionId, - ) => - _chatApi.polls.deletePollOption(pollId, optionId); + ) => _chatApi.polls.deletePollOption(pollId, optionId); /// Cast a [vote] for the Poll [pollId]. Future castPollVote( @@ -1544,20 +1504,18 @@ class StreamChatClient { String messageId, String pollId, String voteId, - ) => - _chatApi.polls.removePollVote(messageId, pollId, voteId); + ) => _chatApi.polls.removePollVote(messageId, pollId, voteId); /// Queries Polls with the given [filter] and [sort] options. Future queryPolls({ Filter? filter, SortOrder? sort, PaginationParams pagination = const PaginationParams(), - }) => - _chatApi.polls.queryPolls( - filter: filter, - sort: sort, - pagination: pagination, - ); + }) => _chatApi.polls.queryPolls( + filter: filter, + sort: sort, + pagination: pagination, + ); /// Queries Poll Votes for the Poll [pollId] with the given [filter] /// and [sort] options. @@ -1566,20 +1524,18 @@ class StreamChatClient { Filter? filter, SortOrder? sort, PaginationParams pagination = const PaginationParams(), - }) => - _chatApi.polls.queryPollVotes( - pollId, - filter: filter, - sort: sort, - pagination: pagination, - ); + }) => _chatApi.polls.queryPollVotes( + pollId, + filter: filter, + sort: sort, + pagination: pagination, + ); /// Update or Create the given user object. Future updateUser(User user) => updateUsers([user]); /// Batch update a list of users - Future updateUsers(List users) => - _chatApi.user.updateUsers(users); + Future updateUsers(List users) => _chatApi.user.updateUsers(users); /// Partially update the given user with [id]. /// Use [set] to define values to be set. @@ -1600,48 +1556,43 @@ class StreamChatClient { /// Batch partial updates the [users]. Future partialUpdateUsers( List users, - ) => - _chatApi.user.partialUpdateUsers(users); + ) => _chatApi.user.partialUpdateUsers(users); /// Bans a user from all channels Future banUser( String targetUserId, [ Map options = const {}, - ]) => - _chatApi.moderation.banUser( - targetUserId, - options: options, - ); + ]) => _chatApi.moderation.banUser( + targetUserId, + options: options, + ); /// Remove global ban for a user Future unbanUser( String targetUserId, [ Map options = const {}, - ]) => - _chatApi.moderation.unbanUser( - targetUserId, - options: options, - ); + ]) => _chatApi.moderation.unbanUser( + targetUserId, + options: options, + ); /// Shadow bans a user Future shadowBan( String targetID, [ Map options = const {}, - ]) => - banUser(targetID, { - 'shadow': true, - ...options, - }); + ]) => banUser(targetID, { + 'shadow': true, + ...options, + }); /// Removes shadow ban from a user Future removeShadowBan( String targetID, [ Map options = const {}, - ]) => - unbanUser(targetID, { - 'shadow': true, - ...options, - }); + ]) => unbanUser(targetID, { + 'shadow': true, + ...options, + }); final _userBlockLock = Lock(); @@ -1711,38 +1662,34 @@ class StreamChatClient { // Emit an local event with the unread count information as a side effect // in order to update the current user state. - handleEvent(Event( - totalUnreadCount: response.totalUnreadCount, - unreadChannels: response.channels.length, - unreadThreads: response.threads.length, - )); + handleEvent( + Event( + totalUnreadCount: response.totalUnreadCount, + unreadChannels: response.channels.length, + unreadThreads: response.threads.length, + ), + ); return response; } /// Mutes a user - Future muteUser(String userId) => - _chatApi.moderation.muteUser(userId); + Future muteUser(String userId) => _chatApi.moderation.muteUser(userId); /// Unmutes a user - Future unmuteUser(String userId) => - _chatApi.moderation.unmuteUser(userId); + Future unmuteUser(String userId) => _chatApi.moderation.unmuteUser(userId); /// Flag a message - Future flagMessage(String messageId) => - _chatApi.moderation.flagMessage(messageId); + Future flagMessage(String messageId) => _chatApi.moderation.flagMessage(messageId); /// Unflag a message - Future unflagMessage(String messageId) => - _chatApi.moderation.unflagMessage(messageId); + Future unflagMessage(String messageId) => _chatApi.moderation.unflagMessage(messageId); /// Flag a user - Future flagUser(String userId) => - _chatApi.moderation.flagUser(userId); + Future flagUser(String userId) => _chatApi.moderation.flagUser(userId); /// Unflag a message - Future unflagUser(String userId) => - _chatApi.moderation.unflagUser(userId); + Future unflagUser(String userId) => _chatApi.moderation.unflagUser(userId); /// Mark all channels for this user as read Future markAllRead() => _chatApi.channel.markAllRead(); @@ -1775,12 +1722,11 @@ class StreamChatClient { String channelId, String channelType, Event event, - ) => - _chatApi.channel.sendEvent( - channelId, - channelType, - event, - ); + ) => _chatApi.channel.sendEvent( + channelId, + channelType, + event, + ); /// Send a [reactionType] for this [messageId] /// Set [enforceUnique] to true to remove the existing user reaction @@ -1789,23 +1735,21 @@ class StreamChatClient { Reaction reaction, { bool skipPush = false, bool enforceUnique = false, - }) => - _chatApi.message.sendReaction( - messageId, - reaction, - skipPush: skipPush, - enforceUnique: enforceUnique, - ); + }) => _chatApi.message.sendReaction( + messageId, + reaction, + skipPush: skipPush, + enforceUnique: enforceUnique, + ); /// Delete a [reactionType] from this [messageId] Future deleteReaction( String messageId, String reactionType, - ) => - _chatApi.message.deleteReaction( - messageId, - reactionType, - ); + ) => _chatApi.message.deleteReaction( + messageId, + reactionType, + ); /// Sends the message to the given channel Future sendMessage( @@ -1814,46 +1758,42 @@ class StreamChatClient { String channelType, { bool skipPush = false, bool skipEnrichUrl = false, - }) => - _chatApi.message.sendMessage( - channelId, - channelType, - message, - skipPush: skipPush, - skipEnrichUrl: skipEnrichUrl, - ); + }) => _chatApi.message.sendMessage( + channelId, + channelType, + message, + skipPush: skipPush, + skipEnrichUrl: skipEnrichUrl, + ); /// Lists all the message replies for the [parentId] Future getReplies( String parentId, { PaginationParams? options, - }) => - _chatApi.message.getReplies( - parentId, - options: options, - ); + }) => _chatApi.message.getReplies( + parentId, + options: options, + ); /// Get all the reactions for a [messageId] Future getReactions( String messageId, { PaginationParams? pagination, - }) => - _chatApi.message.getReactions( - messageId, - pagination: pagination, - ); + }) => _chatApi.message.getReactions( + messageId, + pagination: pagination, + ); /// Update the given message Future updateMessage( Message message, { bool skipPush = false, bool skipEnrichUrl = false, - }) => - _chatApi.message.updateMessage( - message, - skipPush: skipPush, - skipEnrichUrl: skipEnrichUrl, - ); + }) => _chatApi.message.updateMessage( + message, + skipPush: skipPush, + skipEnrichUrl: skipEnrichUrl, + ); /// Partially update the given [messageId] /// Use [set] to define values to be set @@ -1863,13 +1803,12 @@ class StreamChatClient { Map? set, List? unset, bool skipEnrichUrl = false, - }) => - _chatApi.message.partialUpdateMessage( - messageId, - set: set, - unset: unset, - skipEnrichUrl: skipEnrichUrl, - ); + }) => _chatApi.message.partialUpdateMessage( + messageId, + set: set, + unset: unset, + skipEnrichUrl: skipEnrichUrl, + ); /// Deletes the given message. /// @@ -1897,8 +1836,7 @@ class StreamChatClient { } /// Get a message by [messageId] - Future getMessage(String messageId) => - _chatApi.message.getMessage(messageId); + Future getMessage(String messageId) => _chatApi.message.getMessage(messageId); /// Retrieves a list of messages by [messageIDs] /// from the given [channelId] of type [channelType] @@ -1906,34 +1844,31 @@ class StreamChatClient { String channelId, String channelType, List messageIDs, - ) => - _chatApi.message.getMessagesById( - channelId, - channelType, - messageIDs, - ); + ) => _chatApi.message.getMessagesById( + channelId, + channelType, + messageIDs, + ); /// Translates the [messageId] in provided [language] Future translateMessage( String messageId, String language, - ) => - _chatApi.message.translateMessage( - messageId, - language, - ); + ) => _chatApi.message.translateMessage( + messageId, + language, + ); /// Creates a draft for the given [channelId] of type [channelType]. Future createDraft( DraftMessage draft, String channelId, String channelType, - ) => - _chatApi.message.createDraft( - channelId, - channelType, - draft, - ); + ) => _chatApi.message.createDraft( + channelId, + channelType, + draft, + ); /// Retrieves a draft for the given [channelId] of type [channelType]. /// @@ -1942,12 +1877,11 @@ class StreamChatClient { String channelId, String channelType, { String? parentId, - }) => - _chatApi.message.getDraft( - channelId, - channelType, - parentId: parentId, - ); + }) => _chatApi.message.getDraft( + channelId, + channelType, + parentId: parentId, + ); /// Deletes a draft for the given [channelId] of type [channelType]. /// @@ -1956,23 +1890,21 @@ class StreamChatClient { String channelId, String channelType, { String? parentId, - }) => - _chatApi.message.deleteDraft( - channelId, - channelType, - parentId: parentId, - ); + }) => _chatApi.message.deleteDraft( + channelId, + channelType, + parentId: parentId, + ); /// Queries drafts for the current user. Future queryDrafts({ Filter? filter, SortOrder? sort, PaginationParams? pagination, - }) => - _chatApi.message.queryDrafts( - sort: sort, - pagination: pagination, - ); + }) => _chatApi.message.queryDrafts( + sort: sort, + pagination: pagination, + ); /// Retrieves all the active live locations of the current user. Future getActiveLiveLocations() async { @@ -2024,22 +1956,20 @@ class StreamChatClient { String channelId, String channelType, int cooldown, - ) async => - _chatApi.channel.enableSlowdown( - channelId, - channelType, - cooldown, - ); + ) async => _chatApi.channel.enableSlowdown( + channelId, + channelType, + cooldown, + ); /// Disables slow mode Future disableSlowdown( String channelId, String channelType, - ) async => - _chatApi.channel.disableSlowdown( - channelId, - channelType, - ); + ) async => _chatApi.channel.disableSlowdown( + channelId, + channelType, + ); /// Pins provided message /// [timeoutOrExpirationDate] can either be a [DateTime] or a value in seconds @@ -2049,9 +1979,7 @@ class StreamChatClient { Object? /*num|DateTime*/ timeoutOrExpirationDate, }) { assert(() { - if (timeoutOrExpirationDate is! DateTime && - timeoutOrExpirationDate != null && - timeoutOrExpirationDate is! num) { + if (timeoutOrExpirationDate is! DateTime && timeoutOrExpirationDate != null && timeoutOrExpirationDate is! num) { throw ArgumentError('Invalid timeout or Expiration date'); } return true; @@ -2075,17 +2003,15 @@ class StreamChatClient { } /// Unpins provided message - Future unpinMessage(String messageId) => - partialUpdateMessage( - messageId, - set: { - 'pinned': false, - }, - ); + Future unpinMessage(String messageId) => partialUpdateMessage( + messageId, + set: { + 'pinned': false, + }, + ); /// Get OpenGraph data of the given [url]. - Future enrichUrl(String url) => - _chatApi.general.enrichUrl(url); + Future enrichUrl(String url) => _chatApi.general.enrichUrl(url); /// Queries threads with the given [options] and [pagination] params. /// @@ -2095,13 +2021,12 @@ class StreamChatClient { SortOrder? sort, ThreadOptions options = const ThreadOptions(), PaginationParams pagination = const PaginationParams(), - }) => - _chatApi.threads.queryThreads( - filter: filter, - sort: sort, - options: options, - pagination: pagination, - ); + }) => _chatApi.threads.queryThreads( + filter: filter, + sort: sort, + options: options, + pagination: pagination, + ); /// Retrieves a thread with the given [messageId]. /// @@ -2109,11 +2034,10 @@ class StreamChatClient { Future getThread( String messageId, { ThreadOptions options = const ThreadOptions(), - }) => - _chatApi.threads.getThread( - messageId, - options: options, - ); + }) => _chatApi.threads.getThread( + messageId, + options: options, + ); /// Partially updates the thread with the given [messageId]. /// @@ -2123,12 +2047,11 @@ class StreamChatClient { String messageId, { Map? set, List? unset, - }) => - _chatApi.threads.partialUpdateThread( - messageId, - set: set, - unset: unset, - ); + }) => _chatApi.threads.partialUpdateThread( + messageId, + set: set, + unset: unset, + ); /// Pins the channel for the current user. Future pinChannel({ @@ -2421,18 +2344,17 @@ class ClientState { _eventsSubscription?.add( _client .on( - EventType.memberRemoved, - EventType.notificationRemovedFromChannel, - ) + EventType.memberRemoved, + EventType.notificationRemovedFromChannel, + ) .listen((event) async { - final isCurrentUser = event.user!.id == currentUser!.id; - if (isCurrentUser && event.channel != null) { - final eventChannel = event.channel!; - await _client.chatPersistenceClient - ?.deleteChannels([eventChannel.cid]); - channels.remove(eventChannel.cid)?.dispose(); - } - }), + final isCurrentUser = event.user!.id == currentUser!.id; + if (isCurrentUser && event.channel != null) { + final eventChannel = event.channel!; + await _client.chatPersistenceClient?.deleteChannels([eventChannel.cid]); + channels.remove(eventChannel.cid)?.dispose(); + } + }), ); } @@ -2440,14 +2362,14 @@ class ClientState { _eventsSubscription?.add( _client .on( - EventType.channelDeleted, - EventType.notificationChannelDeleted, - ) + EventType.channelDeleted, + EventType.notificationChannelDeleted, + ) .listen((Event event) async { - final eventChannel = event.channel!; - await _client.chatPersistenceClient?.deleteChannels([eventChannel.cid]); - channels.remove(eventChannel.cid)?.dispose(); - }), + final eventChannel = event.channel!; + await _client.chatPersistenceClient?.deleteChannels([eventChannel.cid]); + channels.remove(eventChannel.cid)?.dispose(); + }), ); } @@ -2528,7 +2450,7 @@ class ClientState { final newActiveLiveLocations = [ ...activeLiveLocations.where( (it) => it.messageId != location.messageId, - ) + ), ]; activeLiveLocations = newActiveLiveLocations; diff --git a/packages/stream_chat/lib/src/client/key_stroke_handler.dart b/packages/stream_chat/lib/src/client/key_stroke_handler.dart index 52878182fd..501c1f944d 100644 --- a/packages/stream_chat/lib/src/client/key_stroke_handler.dart +++ b/packages/stream_chat/lib/src/client/key_stroke_handler.dart @@ -87,13 +87,15 @@ class KeyStrokeHandler { _cancelKeyStrokeTimer(); _keyStrokeTimer = Timer(Duration(seconds: startTypingEventTimeout), () { - _stopTyping(parentId).then((_) { - if (completer.isCompleted) return; - completer.complete(); - }).onError((error, stackTrace) { - if (completer.isCompleted) return; - completer.completeError(error!, stackTrace); - }); + _stopTyping(parentId) + .then((_) { + if (completer.isCompleted) return; + completer.complete(); + }) + .onError((error, stackTrace) { + if (completer.isCompleted) return; + completer.completeError(error!, stackTrace); + }); }); // If the user is typing too long, it should call [onStartTyping] again. diff --git a/packages/stream_chat/lib/src/client/retry_policy.dart b/packages/stream_chat/lib/src/client/retry_policy.dart index b5d0804368..4c7d249e84 100644 --- a/packages/stream_chat/lib/src/client/retry_policy.dart +++ b/packages/stream_chat/lib/src/client/retry_policy.dart @@ -51,5 +51,6 @@ class RetryPolicy { StreamChatClient client, int attempt, StreamChatError? error, - ) shouldRetry; + ) + shouldRetry; } diff --git a/packages/stream_chat/lib/src/client/retry_queue.dart b/packages/stream_chat/lib/src/client/retry_queue.dart index c1f8841406..feb6b623f6 100644 --- a/packages/stream_chat/lib/src/client/retry_queue.dart +++ b/packages/stream_chat/lib/src/client/retry_queue.dart @@ -31,12 +31,16 @@ class RetryQueue { final _messageQueue = HeapPriorityQueue(_byDate); void _listenConnectionRecovered() { - client.on(EventType.connectionRecovered).distinct().listen((event) { - if (event.online == true) { - logger?.info('Connection recovered, retrying failed messages'); - channel.state?.retryFailedMessages(); - } - }).addTo(_compositeSubscription); + client + .on(EventType.connectionRecovered) + .distinct() + .listen((event) { + if (event.online == true) { + logger?.info('Connection recovered, retrying failed messages'); + channel.state?.retryFailedMessages(); + } + }) + .addTo(_compositeSubscription); } /// Add a list of messages. diff --git a/packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart b/packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart index 09fc25a081..9575dfa55d 100644 --- a/packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart +++ b/packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart @@ -4,9 +4,10 @@ import 'package:stream_chat/src/core/http/stream_http_client.dart'; import 'package:stream_chat/src/core/models/attachment_file.dart'; /// Signature for a function which provides instance of [AttachmentFileUploader] -typedef AttachmentFileUploaderProvider = AttachmentFileUploader Function( - StreamHttpClient httpClient, -); +typedef AttachmentFileUploaderProvider = + AttachmentFileUploader Function( + StreamHttpClient httpClient, + ); /// Class responsible for uploading images and files to a given channel abstract class AttachmentFileUploader { diff --git a/packages/stream_chat/lib/src/core/api/channel_api.dart b/packages/stream_chat/lib/src/core/api/channel_api.dart index 77c2425a55..b2addba7d6 100644 --- a/packages/stream_chat/lib/src/core/api/channel_api.dart +++ b/packages/stream_chat/lib/src/core/api/channel_api.dart @@ -17,8 +17,7 @@ class ChannelApi { final StreamHttpClient _client; - String _getChannelUrl(String channelId, String channelType) => - '/channels/$channelType/$channelId'; + String _getChannelUrl(String channelId, String channelType) => '/channels/$channelType/$channelId'; /// Query the API, get messages, members or other channel fields Future queryChannel( @@ -100,8 +99,7 @@ class ChannelApi { _getChannelUrl(channelId, channelType), data: { 'data': data, - if (message != null) - 'message': message.copyWith(updatedAt: DateTime.now()), + if (message != null) 'message': message.copyWith(updatedAt: DateTime.now()), }, ); return UpdateChannelResponse.fromJson(response.data); diff --git a/packages/stream_chat/lib/src/core/api/device_api.dart b/packages/stream_chat/lib/src/core/api/device_api.dart index b450ddc18e..1d75994036 100644 --- a/packages/stream_chat/lib/src/core/api/device_api.dart +++ b/packages/stream_chat/lib/src/core/api/device_api.dart @@ -37,8 +37,7 @@ class DeviceApi { data: { 'id': deviceId, 'push_provider': pushProvider.name, - if (pushProviderName != null && pushProviderName.isNotEmpty) - 'push_provider_name': pushProviderName, + if (pushProviderName != null && pushProviderName.isNotEmpty) 'push_provider_name': pushProviderName, }, ); return EmptyResponse.fromJson(response.data); diff --git a/packages/stream_chat/lib/src/core/api/general_api.dart b/packages/stream_chat/lib/src/core/api/general_api.dart index c364674463..16b4bdb9f8 100644 --- a/packages/stream_chat/lib/src/core/api/general_api.dart +++ b/packages/stream_chat/lib/src/core/api/general_api.dart @@ -60,8 +60,7 @@ class GeneralApi { 'filter_conditions': filter, if (sort != null) 'sort': sort, if (query != null) 'query': query, - if (messageFilters != null) - 'message_filter_conditions': messageFilters, + if (messageFilters != null) 'message_filter_conditions': messageFilters, if (pagination != null) ...pagination.toJson(), }), }, @@ -85,10 +84,7 @@ class GeneralApi { 'payload': jsonEncode({ 'type': channelType, 'filter_conditions': filter ?? {}, - if (channelId != null) - 'id': channelId - else if (members != null) - 'members': members, + if (channelId != null) 'id': channelId else if (members != null) 'members': members, if (sort != null) 'sort': sort, if (pagination != null) ...pagination.toJson(), }), diff --git a/packages/stream_chat/lib/src/core/api/requests.dart b/packages/stream_chat/lib/src/core/api/requests.dart index 0b5a704608..c3953e3233 100644 --- a/packages/stream_chat/lib/src/core/api/requests.dart +++ b/packages/stream_chat/lib/src/core/api/requests.dart @@ -31,13 +31,12 @@ class PaginationParams extends Equatable { this.createdAtBefore, this.createdAtAround, }) : assert( - offset == null || offset == 0 || next == null, - 'Cannot specify non-zero `offset` with `next` parameter', - ); + offset == null || offset == 0 || next == null, + 'Cannot specify non-zero `offset` with `next` parameter', + ); /// Create a new instance from a json - factory PaginationParams.fromJson(Map json) => - _$PaginationParamsFromJson(json); + factory PaginationParams.fromJson(Map json) => _$PaginationParamsFromJson(json); /// The amount of items requested from the APIs. final int limit; @@ -108,41 +107,38 @@ class PaginationParams extends Equatable { DateTime? createdAtBeforeOrEqual, DateTime? createdAtBefore, DateTime? createdAtAround, - }) => - PaginationParams( - limit: limit ?? this.limit, - offset: offset ?? this.offset, - idAround: idAround ?? this.idAround, - next: next ?? this.next, - greaterThan: greaterThan ?? this.greaterThan, - greaterThanOrEqual: greaterThanOrEqual ?? this.greaterThanOrEqual, - lessThan: lessThan ?? this.lessThan, - lessThanOrEqual: lessThanOrEqual ?? this.lessThanOrEqual, - createdAtAfterOrEqual: - createdAtAfterOrEqual ?? this.createdAtAfterOrEqual, - createdAtAfter: createdAtAfter ?? this.createdAtAfter, - createdAtBeforeOrEqual: - createdAtBeforeOrEqual ?? this.createdAtBeforeOrEqual, - createdAtBefore: createdAtBefore ?? this.createdAtBefore, - createdAtAround: createdAtAround ?? this.createdAtAround, - ); + }) => PaginationParams( + limit: limit ?? this.limit, + offset: offset ?? this.offset, + idAround: idAround ?? this.idAround, + next: next ?? this.next, + greaterThan: greaterThan ?? this.greaterThan, + greaterThanOrEqual: greaterThanOrEqual ?? this.greaterThanOrEqual, + lessThan: lessThan ?? this.lessThan, + lessThanOrEqual: lessThanOrEqual ?? this.lessThanOrEqual, + createdAtAfterOrEqual: createdAtAfterOrEqual ?? this.createdAtAfterOrEqual, + createdAtAfter: createdAtAfter ?? this.createdAtAfter, + createdAtBeforeOrEqual: createdAtBeforeOrEqual ?? this.createdAtBeforeOrEqual, + createdAtBefore: createdAtBefore ?? this.createdAtBefore, + createdAtAround: createdAtAround ?? this.createdAtAround, + ); @override List get props => [ - limit, - offset, - next, - idAround, - greaterThan, - greaterThanOrEqual, - lessThan, - lessThanOrEqual, - createdAtAfterOrEqual, - createdAtAfter, - createdAtBeforeOrEqual, - createdAtBefore, - createdAtAround, - ]; + limit, + offset, + next, + idAround, + greaterThan, + greaterThanOrEqual, + lessThan, + lessThanOrEqual, + createdAtAfterOrEqual, + createdAtAfter, + createdAtBeforeOrEqual, + createdAtBefore, + createdAtAround, + ]; } /// Request model for the [client.partialUpdateUser] api call. diff --git a/packages/stream_chat/lib/src/core/api/requests.g.dart b/packages/stream_chat/lib/src/core/api/requests.g.dart index 7a77503534..b04bb02430 100644 --- a/packages/stream_chat/lib/src/core/api/requests.g.dart +++ b/packages/stream_chat/lib/src/core/api/requests.g.dart @@ -6,80 +6,62 @@ part of 'requests.dart'; // JsonSerializableGenerator // ************************************************************************** -PaginationParams _$PaginationParamsFromJson(Map json) => - PaginationParams( - limit: (json['limit'] as num?)?.toInt() ?? 10, - offset: (json['offset'] as num?)?.toInt(), - next: json['next'] as String?, - idAround: json['id_around'] as String?, - greaterThan: json['id_gt'] as String?, - greaterThanOrEqual: json['id_gte'] as String?, - lessThan: json['id_lt'] as String?, - lessThanOrEqual: json['id_lte'] as String?, - createdAtAfterOrEqual: json['created_at_after_or_equal'] == null - ? null - : DateTime.parse(json['created_at_after_or_equal'] as String), - createdAtAfter: json['created_at_after'] == null - ? null - : DateTime.parse(json['created_at_after'] as String), - createdAtBeforeOrEqual: json['created_at_before_or_equal'] == null - ? null - : DateTime.parse(json['created_at_before_or_equal'] as String), - createdAtBefore: json['created_at_before'] == null - ? null - : DateTime.parse(json['created_at_before'] as String), - createdAtAround: json['created_at_around'] == null - ? null - : DateTime.parse(json['created_at_around'] as String), - ); +PaginationParams _$PaginationParamsFromJson(Map json) => PaginationParams( + limit: (json['limit'] as num?)?.toInt() ?? 10, + offset: (json['offset'] as num?)?.toInt(), + next: json['next'] as String?, + idAround: json['id_around'] as String?, + greaterThan: json['id_gt'] as String?, + greaterThanOrEqual: json['id_gte'] as String?, + lessThan: json['id_lt'] as String?, + lessThanOrEqual: json['id_lte'] as String?, + createdAtAfterOrEqual: json['created_at_after_or_equal'] == null + ? null + : DateTime.parse(json['created_at_after_or_equal'] as String), + createdAtAfter: json['created_at_after'] == null ? null : DateTime.parse(json['created_at_after'] as String), + createdAtBeforeOrEqual: json['created_at_before_or_equal'] == null + ? null + : DateTime.parse(json['created_at_before_or_equal'] as String), + createdAtBefore: json['created_at_before'] == null ? null : DateTime.parse(json['created_at_before'] as String), + createdAtAround: json['created_at_around'] == null ? null : DateTime.parse(json['created_at_around'] as String), +); -Map _$PaginationParamsToJson(PaginationParams instance) => - { - 'limit': instance.limit, - if (instance.offset case final value?) 'offset': value, - if (instance.next case final value?) 'next': value, - if (instance.idAround case final value?) 'id_around': value, - if (instance.greaterThan case final value?) 'id_gt': value, - if (instance.greaterThanOrEqual case final value?) 'id_gte': value, - if (instance.lessThan case final value?) 'id_lt': value, - if (instance.lessThanOrEqual case final value?) 'id_lte': value, - if (instance.createdAtAfterOrEqual?.toIso8601String() case final value?) - 'created_at_after_or_equal': value, - if (instance.createdAtAfter?.toIso8601String() case final value?) - 'created_at_after': value, - if (instance.createdAtBeforeOrEqual?.toIso8601String() case final value?) - 'created_at_before_or_equal': value, - if (instance.createdAtBefore?.toIso8601String() case final value?) - 'created_at_before': value, - if (instance.createdAtAround?.toIso8601String() case final value?) - 'created_at_around': value, - }; +Map _$PaginationParamsToJson(PaginationParams instance) => { + 'limit': instance.limit, + if (instance.offset case final value?) 'offset': value, + if (instance.next case final value?) 'next': value, + if (instance.idAround case final value?) 'id_around': value, + if (instance.greaterThan case final value?) 'id_gt': value, + if (instance.greaterThanOrEqual case final value?) 'id_gte': value, + if (instance.lessThan case final value?) 'id_lt': value, + if (instance.lessThanOrEqual case final value?) 'id_lte': value, + if (instance.createdAtAfterOrEqual?.toIso8601String() case final value?) 'created_at_after_or_equal': value, + if (instance.createdAtAfter?.toIso8601String() case final value?) 'created_at_after': value, + if (instance.createdAtBeforeOrEqual?.toIso8601String() case final value?) 'created_at_before_or_equal': value, + if (instance.createdAtBefore?.toIso8601String() case final value?) 'created_at_before': value, + if (instance.createdAtAround?.toIso8601String() case final value?) 'created_at_around': value, +}; -Map _$PartialUpdateUserRequestToJson( - PartialUpdateUserRequest instance) => - { - 'stringify': instance.stringify, - 'hash_code': instance.hashCode, - 'id': instance.id, - 'set': instance.set, - 'unset': instance.unset, - 'props': instance.props, - }; +Map _$PartialUpdateUserRequestToJson(PartialUpdateUserRequest instance) => { + 'stringify': instance.stringify, + 'hash_code': instance.hashCode, + 'id': instance.id, + 'set': instance.set, + 'unset': instance.unset, + 'props': instance.props, +}; -Map _$ThreadOptionsToJson(ThreadOptions instance) => - { - 'stringify': instance.stringify, - 'hash_code': instance.hashCode, - 'watch': instance.watch, - 'reply_limit': instance.replyLimit, - 'participant_limit': instance.participantLimit, - 'member_limit': instance.memberLimit, - 'props': instance.props, - }; +Map _$ThreadOptionsToJson(ThreadOptions instance) => { + 'stringify': instance.stringify, + 'hash_code': instance.hashCode, + 'watch': instance.watch, + 'reply_limit': instance.replyLimit, + 'participant_limit': instance.participantLimit, + 'member_limit': instance.memberLimit, + 'props': instance.props, +}; -Map _$MemberUpdatePayloadToJson( - MemberUpdatePayload instance) => - { - if (instance.archived case final value?) 'archived': value, - if (instance.pinned case final value?) 'pinned': value, - }; +Map _$MemberUpdatePayloadToJson(MemberUpdatePayload instance) => { + if (instance.archived case final value?) 'archived': value, + if (instance.pinned case final value?) 'pinned': value, +}; diff --git a/packages/stream_chat/lib/src/core/api/responses.dart b/packages/stream_chat/lib/src/core/api/responses.dart index 2a2985d7c5..00694b0a50 100644 --- a/packages/stream_chat/lib/src/core/api/responses.dart +++ b/packages/stream_chat/lib/src/core/api/responses.dart @@ -45,14 +45,14 @@ class ErrorResponse extends _BaseResponse { String? moreInfo; /// Create a new instance from a json - static ErrorResponse fromJson(Map json) => - _$ErrorResponseFromJson(json); + static ErrorResponse fromJson(Map json) => _$ErrorResponseFromJson(json); /// Serialize to json Map toJson() => _$ErrorResponseToJson(this); @override - String toString() => 'ErrorResponse(code: $code, ' + String toString() => + 'ErrorResponse(code: $code, ' 'message: $message, ' 'statusCode: $statusCode, ' 'moreInfo: $moreInfo)'; @@ -66,8 +66,7 @@ class SyncResponse extends _BaseResponse { late List events; /// Create a new instance from a json - static SyncResponse fromJson(Map json) => - _$SyncResponseFromJson(json); + static SyncResponse fromJson(Map json) => _$SyncResponseFromJson(json); } /// Model response for [StreamChatClient.queryChannels] api call @@ -78,16 +77,14 @@ class QueryChannelsResponse extends _BaseResponse { late List channels; /// Create a new instance from a json - static QueryChannelsResponse fromJson(Map json) => - _$QueryChannelsResponseFromJson(json); + static QueryChannelsResponse fromJson(Map json) => _$QueryChannelsResponseFromJson(json); } /// Model response for [StreamChatClient.queryChannels] api call @JsonSerializable(createToJson: false) class TranslateMessageResponse extends MessageResponse { /// Create a new instance from a json - static TranslateMessageResponse fromJson(Map json) => - _$TranslateMessageResponseFromJson(json); + static TranslateMessageResponse fromJson(Map json) => _$TranslateMessageResponseFromJson(json); } /// Model response for [StreamChatClient.queryChannels] api call @@ -98,8 +95,7 @@ class QueryMembersResponse extends _BaseResponse { late List members; /// Create a new instance from a json - static QueryMembersResponse fromJson(Map json) => - _$QueryMembersResponseFromJson(json); + static QueryMembersResponse fromJson(Map json) => _$QueryMembersResponseFromJson(json); } /// Model response for update member API calls, such as @@ -110,8 +106,7 @@ class PartialUpdateMemberResponse extends _BaseResponse { late Member channelMember; /// Create a new instance from a json - static PartialUpdateMemberResponse fromJson(Map json) => - _$PartialUpdateMemberResponseFromJson(json); + static PartialUpdateMemberResponse fromJson(Map json) => _$PartialUpdateMemberResponseFromJson(json); } /// Model response for [StreamChatClient.queryUsers] api call @@ -122,8 +117,7 @@ class QueryUsersResponse extends _BaseResponse { late List users; /// Create a new instance from a json - static QueryUsersResponse fromJson(Map json) => - _$QueryUsersResponseFromJson(json); + static QueryUsersResponse fromJson(Map json) => _$QueryUsersResponseFromJson(json); } /// Model response for [StreamChatClient.queryBannedUsers] api call @@ -134,8 +128,7 @@ class QueryBannedUsersResponse extends _BaseResponse { late List bans; /// Create a new instance from a json - static QueryBannedUsersResponse fromJson(Map json) => - _$QueryBannedUsersResponseFromJson(json); + static QueryBannedUsersResponse fromJson(Map json) => _$QueryBannedUsersResponseFromJson(json); } /// Model response for [channel.getReactions] api call @@ -146,8 +139,7 @@ class QueryReactionsResponse extends _BaseResponse { late List reactions; /// Create a new instance from a json - static QueryReactionsResponse fromJson(Map json) => - _$QueryReactionsResponseFromJson(json); + static QueryReactionsResponse fromJson(Map json) => _$QueryReactionsResponseFromJson(json); } /// Model response for [Channel.getReplies] api call @@ -158,8 +150,7 @@ class QueryRepliesResponse extends _BaseResponse { late List messages; /// Create a new instance from a json - static QueryRepliesResponse fromJson(Map json) => - _$QueryRepliesResponseFromJson(json); + static QueryRepliesResponse fromJson(Map json) => _$QueryRepliesResponseFromJson(json); } /// Model response for [StreamChatClient.getDevices] api call @@ -170,8 +161,7 @@ class ListDevicesResponse extends _BaseResponse { late List devices; /// Create a new instance from a json - static ListDevicesResponse fromJson(Map json) => - _$ListDevicesResponseFromJson(json); + static ListDevicesResponse fromJson(Map json) => _$ListDevicesResponseFromJson(json); } /// Base Model response for [Channel.sendImage] and [Channel.sendFile] api call. @@ -181,8 +171,7 @@ class SendAttachmentResponse extends _BaseResponse { late String? file; /// Create a new instance from a json - static SendAttachmentResponse fromJson(Map json) => - _$SendAttachmentResponseFromJson(json); + static SendAttachmentResponse fromJson(Map json) => _$SendAttachmentResponseFromJson(json); } /// Model response for [Channel.sendFile] api call @@ -194,8 +183,7 @@ class SendFileResponse extends SendAttachmentResponse { String? thumbUrl; /// Create a new instance from a json - static SendFileResponse fromJson(Map json) => - _$SendFileResponseFromJson(json); + static SendFileResponse fromJson(Map json) => _$SendFileResponseFromJson(json); } /// Model response for [Channel.sendImage] api call @@ -214,8 +202,7 @@ class SendReactionResponse extends MessageResponse { late Reaction reaction; /// Create a new instance from a json - static SendReactionResponse fromJson(Map json) => - _$SendReactionResponseFromJson(json); + static SendReactionResponse fromJson(Map json) => _$SendReactionResponseFromJson(json); } /// Model response for [StreamChatClient.connectGuestUser] api call @@ -228,8 +215,7 @@ class ConnectGuestUserResponse extends _BaseResponse { late User user; /// Create a new instance from a json - static ConnectGuestUserResponse fromJson(Map json) => - _$ConnectGuestUserResponseFromJson(json); + static ConnectGuestUserResponse fromJson(Map json) => _$ConnectGuestUserResponseFromJson(json); } /// Model response for [StreamChatClient.updateUser] api call @@ -240,8 +226,7 @@ class UpdateUsersResponse extends _BaseResponse { late Map users; /// Create a new instance from a json - static UpdateUsersResponse fromJson(Map json) => - _$UpdateUsersResponseFromJson(json); + static UpdateUsersResponse fromJson(Map json) => _$UpdateUsersResponseFromJson(json); } /// Base Model response for message based api calls. @@ -254,16 +239,14 @@ class MessageResponse extends _BaseResponse { @JsonSerializable(createToJson: false) class UpdateMessageResponse extends MessageResponse { /// Create a new instance from a json - static UpdateMessageResponse fromJson(Map json) => - _$UpdateMessageResponseFromJson(json); + static UpdateMessageResponse fromJson(Map json) => _$UpdateMessageResponseFromJson(json); } /// Model response for [Channel.sendMessage] api call @JsonSerializable(createToJson: false) class SendMessageResponse extends MessageResponse { /// Create a new instance from a json - static SendMessageResponse fromJson(Map json) => - _$SendMessageResponseFromJson(json); + static SendMessageResponse fromJson(Map json) => _$SendMessageResponseFromJson(json); } /// Model response for [StreamChatClient.getMessage] api call @@ -297,8 +280,7 @@ class SearchMessagesResponse extends _BaseResponse { late String? previous; /// Create a new instance from a json - static SearchMessagesResponse fromJson(Map json) => - _$SearchMessagesResponseFromJson(json); + static SearchMessagesResponse fromJson(Map json) => _$SearchMessagesResponseFromJson(json); } /// Model response for [Channel.getMessagesById] api call @@ -309,8 +291,7 @@ class GetMessagesByIdResponse extends _BaseResponse { late List messages; /// Create a new instance from a json - static GetMessagesByIdResponse fromJson(Map json) => - _$GetMessagesByIdResponseFromJson(json); + static GetMessagesByIdResponse fromJson(Map json) => _$GetMessagesByIdResponseFromJson(json); } /// Model response for [Channel.update] api call @@ -326,8 +307,7 @@ class UpdateChannelResponse extends _BaseResponse { Message? message; /// Create a new instance from a json - static UpdateChannelResponse fromJson(Map json) => - _$UpdateChannelResponseFromJson(json); + static UpdateChannelResponse fromJson(Map json) => _$UpdateChannelResponseFromJson(json); } /// Model response for [Channel.updatePartial] api call @@ -358,8 +338,7 @@ class InviteMembersResponse extends _BaseResponse { Message? message; /// Create a new instance from a json - static InviteMembersResponse fromJson(Map json) => - _$InviteMembersResponseFromJson(json); + static InviteMembersResponse fromJson(Map json) => _$InviteMembersResponseFromJson(json); } /// Model response for [Channel.removeMembers] api call @@ -376,8 +355,7 @@ class RemoveMembersResponse extends _BaseResponse { Message? message; /// Create a new instance from a json - static RemoveMembersResponse fromJson(Map json) => - _$RemoveMembersResponseFromJson(json); + static RemoveMembersResponse fromJson(Map json) => _$RemoveMembersResponseFromJson(json); } /// Model response for [Channel.sendAction] api call @@ -387,8 +365,7 @@ class SendActionResponse extends _BaseResponse { Message? message; /// Create a new instance from a json - static SendActionResponse fromJson(Map json) => - _$SendActionResponseFromJson(json); + static SendActionResponse fromJson(Map json) => _$SendActionResponseFromJson(json); } /// Model response for [Channel.addMembers] api call @@ -405,8 +382,7 @@ class AddMembersResponse extends _BaseResponse { Message? message; /// Create a new instance from a json - static AddMembersResponse fromJson(Map json) => - _$AddMembersResponseFromJson(json); + static AddMembersResponse fromJson(Map json) => _$AddMembersResponseFromJson(json); } /// Model response for [Channel.acceptInvite] api call @@ -423,8 +399,7 @@ class AcceptInviteResponse extends _BaseResponse { Message? message; /// Create a new instance from a json - static AcceptInviteResponse fromJson(Map json) => - _$AcceptInviteResponseFromJson(json); + static AcceptInviteResponse fromJson(Map json) => _$AcceptInviteResponseFromJson(json); } /// Model response for [Channel.rejectInvite] api call @@ -441,16 +416,14 @@ class RejectInviteResponse extends _BaseResponse { Message? message; /// Create a new instance from a json - static RejectInviteResponse fromJson(Map json) => - _$RejectInviteResponseFromJson(json); + static RejectInviteResponse fromJson(Map json) => _$RejectInviteResponseFromJson(json); } /// Model response for empty responses @JsonSerializable(createToJson: false) class EmptyResponse extends _BaseResponse { /// Create a new instance from a json - static EmptyResponse fromJson(Map json) => - _$EmptyResponseFromJson(json); + static EmptyResponse fromJson(Map json) => _$EmptyResponseFromJson(json); } /// Model response for [Channel.query] api call @@ -476,8 +449,7 @@ class ChannelStateResponse extends _BaseResponse { late List read; /// Create a new instance from a json - static ChannelStateResponse fromJson(Map json) => - _$ChannelStateResponseFromJson(json); + static ChannelStateResponse fromJson(Map json) => _$ChannelStateResponseFromJson(json); } /// Model response for [Client.enrichUrl] api call. @@ -516,8 +488,7 @@ class OGAttachmentResponse extends _BaseResponse { String? type; /// Create a new instance from a [json]. - static OGAttachmentResponse fromJson(Map json) => - _$OGAttachmentResponseFromJson(json); + static OGAttachmentResponse fromJson(Map json) => _$OGAttachmentResponseFromJson(json); } /// Contains information about a [User] that was banned from a [Channel] or App. @@ -535,8 +506,7 @@ class UserBlockResponse extends _BaseResponse { late DateTime createdAt; /// Create a new instance from a json - static UserBlockResponse fromJson(Map json) => - _$UserBlockResponseFromJson(json); + static UserBlockResponse fromJson(Map json) => _$UserBlockResponseFromJson(json); } /// Model response for [StreamChatClient.queryBlockedUsers] api call @@ -547,8 +517,7 @@ class BlockedUsersResponse extends _BaseResponse { late List blocks; /// Create a new instance from a json - static BlockedUsersResponse fromJson(Map json) => - _$BlockedUsersResponseFromJson(json); + static BlockedUsersResponse fromJson(Map json) => _$BlockedUsersResponseFromJson(json); } /// Model response for [StreamChatClient.createPoll] api call @@ -558,8 +527,7 @@ class CreatePollResponse extends _BaseResponse { late Poll poll; /// Create a new instance from a json - static CreatePollResponse fromJson(Map json) => - _$CreatePollResponseFromJson(json); + static CreatePollResponse fromJson(Map json) => _$CreatePollResponseFromJson(json); } /// Model response for [StreamChatClient.getPoll] api call @@ -569,8 +537,7 @@ class GetPollResponse extends _BaseResponse { late Poll poll; /// Create a new instance from a json - static GetPollResponse fromJson(Map json) => - _$GetPollResponseFromJson(json); + static GetPollResponse fromJson(Map json) => _$GetPollResponseFromJson(json); } /// Model response for [StreamChatClient.updatePoll] api call @@ -580,8 +547,7 @@ class UpdatePollResponse extends _BaseResponse { late Poll poll; /// Create a new instance from a json - static UpdatePollResponse fromJson(Map json) => - _$UpdatePollResponseFromJson(json); + static UpdatePollResponse fromJson(Map json) => _$UpdatePollResponseFromJson(json); } /// Model response for [StreamChatClient.createPollOption] api call @@ -591,8 +557,7 @@ class CreatePollOptionResponse extends _BaseResponse { late PollOption pollOption; /// Create a new instance from a json - static CreatePollOptionResponse fromJson(Map json) => - _$CreatePollOptionResponseFromJson(json); + static CreatePollOptionResponse fromJson(Map json) => _$CreatePollOptionResponseFromJson(json); } /// Model response for [StreamChatClient.getPollOption] api call @@ -602,8 +567,7 @@ class GetPollOptionResponse extends _BaseResponse { late PollOption pollOption; /// Create a new instance from a json - static GetPollOptionResponse fromJson(Map json) => - _$GetPollOptionResponseFromJson(json); + static GetPollOptionResponse fromJson(Map json) => _$GetPollOptionResponseFromJson(json); } /// Model response for [StreamChatClient.updatePollOption] api call @@ -613,8 +577,7 @@ class UpdatePollOptionResponse extends _BaseResponse { late PollOption pollOption; /// Create a new instance from a json - static UpdatePollOptionResponse fromJson(Map json) => - _$UpdatePollOptionResponseFromJson(json); + static UpdatePollOptionResponse fromJson(Map json) => _$UpdatePollOptionResponseFromJson(json); } /// Model response for [StreamChatClient.castPollVote] api call @@ -624,8 +587,7 @@ class CastPollVoteResponse extends _BaseResponse { late PollVote vote; /// Create a new instance from a json - static CastPollVoteResponse fromJson(Map json) => - _$CastPollVoteResponseFromJson(json); + static CastPollVoteResponse fromJson(Map json) => _$CastPollVoteResponseFromJson(json); } /// Model response for [StreamChatClient.removePollVote] api call @@ -635,8 +597,7 @@ class RemovePollVoteResponse extends EmptyResponse { late PollVote vote; /// Create a new instance from a json - static RemovePollVoteResponse fromJson(Map json) => - _$RemovePollVoteResponseFromJson(json); + static RemovePollVoteResponse fromJson(Map json) => _$RemovePollVoteResponseFromJson(json); } /// Model response for [StreamChatClient.queryPolls] api call @@ -650,8 +611,7 @@ class QueryPollsResponse extends _BaseResponse { late String? next; /// Create a new instance from a json - static QueryPollsResponse fromJson(Map json) => - _$QueryPollsResponseFromJson(json); + static QueryPollsResponse fromJson(Map json) => _$QueryPollsResponseFromJson(json); } /// Model response for [StreamChatClient.queryPollVotes] api call @@ -665,8 +625,7 @@ class QueryPollVotesResponse extends _BaseResponse { late String? next; /// Create a new instance from a json - static QueryPollVotesResponse fromJson(Map json) => - _$QueryPollVotesResponseFromJson(json); + static QueryPollVotesResponse fromJson(Map json) => _$QueryPollVotesResponseFromJson(json); } /// Model response for [StreamChatClient.getThread] api call @@ -676,8 +635,7 @@ class GetThreadResponse extends _BaseResponse { late Thread thread; /// Create a new instance from a json - static GetThreadResponse fromJson(Map json) => - _$GetThreadResponseFromJson(json); + static GetThreadResponse fromJson(Map json) => _$GetThreadResponseFromJson(json); } /// Model response for [StreamChatClient.updateThread] api call @@ -687,8 +645,7 @@ class UpdateThreadResponse extends _BaseResponse { late Thread thread; /// Create a new instance from a json - static UpdateThreadResponse fromJson(Map json) => - _$UpdateThreadResponseFromJson(json); + static UpdateThreadResponse fromJson(Map json) => _$UpdateThreadResponseFromJson(json); } /// Model response for [StreamChatClient.queryThreads] api call @@ -702,8 +659,7 @@ class QueryThreadsResponse extends _BaseResponse { late String? next; /// Create a new instance from a json - static QueryThreadsResponse fromJson(Map json) => - _$QueryThreadsResponseFromJson(json); + static QueryThreadsResponse fromJson(Map json) => _$QueryThreadsResponseFromJson(json); } /// Base Model response for draft based api calls. @@ -716,16 +672,14 @@ class DraftResponse extends _BaseResponse { @JsonSerializable(createToJson: false) class CreateDraftResponse extends DraftResponse { /// Create a new instance from a json - static CreateDraftResponse fromJson(Map json) => - _$CreateDraftResponseFromJson(json); + static CreateDraftResponse fromJson(Map json) => _$CreateDraftResponseFromJson(json); } /// Model response for [StreamChatClient.getDraft] api call @JsonSerializable(createToJson: false) class GetDraftResponse extends DraftResponse { /// Create a new instance from a json - static GetDraftResponse fromJson(Map json) => - _$GetDraftResponseFromJson(json); + static GetDraftResponse fromJson(Map json) => _$GetDraftResponseFromJson(json); } /// Model response for [StreamChatClient.queryDrafts] api call @@ -739,8 +693,7 @@ class QueryDraftsResponse extends _BaseResponse { late String? next; /// Create a new instance from a json - static QueryDraftsResponse fromJson(Map json) => - _$QueryDraftsResponseFromJson(json); + static QueryDraftsResponse fromJson(Map json) => _$QueryDraftsResponseFromJson(json); } /// Base Model response for draft based api calls. @@ -753,16 +706,14 @@ class MessageReminderResponse extends _BaseResponse { @JsonSerializable(createToJson: false) class CreateReminderResponse extends MessageReminderResponse { /// Create a new instance from a json - static CreateReminderResponse fromJson(Map json) => - _$CreateReminderResponseFromJson(json); + static CreateReminderResponse fromJson(Map json) => _$CreateReminderResponseFromJson(json); } /// Model response for [StreamChatClient.updateReminder] api call @JsonSerializable(createToJson: false) class UpdateReminderResponse extends MessageReminderResponse { /// Create a new instance from a json - static UpdateReminderResponse fromJson(Map json) => - _$UpdateReminderResponseFromJson(json); + static UpdateReminderResponse fromJson(Map json) => _$UpdateReminderResponseFromJson(json); } /// Model response for [StreamChatClient.queryReminders] api call @@ -776,8 +727,7 @@ class QueryRemindersResponse extends _BaseResponse { late String? next; /// Create a new instance from a json - static QueryRemindersResponse fromJson(Map json) => - _$QueryRemindersResponseFromJson(json); + static QueryRemindersResponse fromJson(Map json) => _$QueryRemindersResponseFromJson(json); } /// Model response for [StreamChatClient.getUnreadCount] api call @@ -802,8 +752,7 @@ class GetUnreadCountResponse extends _BaseResponse { late List threads; /// Create a new instance from a json - static GetUnreadCountResponse fromJson(Map json) => - _$GetUnreadCountResponseFromJson(json); + static GetUnreadCountResponse fromJson(Map json) => _$GetUnreadCountResponseFromJson(json); } /// Model response for [StreamChatClient.setPushPreferences] api call diff --git a/packages/stream_chat/lib/src/core/api/responses.g.dart b/packages/stream_chat/lib/src/core/api/responses.g.dart index adfa731942..6ee654641f 100644 --- a/packages/stream_chat/lib/src/core/api/responses.g.dart +++ b/packages/stream_chat/lib/src/core/api/responses.g.dart @@ -6,501 +6,324 @@ part of 'responses.dart'; // JsonSerializableGenerator // ************************************************************************** -ErrorResponse _$ErrorResponseFromJson(Map json) => - ErrorResponse() - ..duration = json['duration'] as String? - ..code = (json['code'] as num?)?.toInt() - ..message = json['message'] as String? - ..statusCode = (json['StatusCode'] as num?)?.toInt() - ..moreInfo = json['more_info'] as String?; - -Map _$ErrorResponseToJson(ErrorResponse instance) => - { - 'duration': instance.duration, - 'code': instance.code, - 'message': instance.message, - 'StatusCode': instance.statusCode, - 'more_info': instance.moreInfo, - }; +ErrorResponse _$ErrorResponseFromJson(Map json) => ErrorResponse() + ..duration = json['duration'] as String? + ..code = (json['code'] as num?)?.toInt() + ..message = json['message'] as String? + ..statusCode = (json['StatusCode'] as num?)?.toInt() + ..moreInfo = json['more_info'] as String?; + +Map _$ErrorResponseToJson(ErrorResponse instance) => { + 'duration': instance.duration, + 'code': instance.code, + 'message': instance.message, + 'StatusCode': instance.statusCode, + 'more_info': instance.moreInfo, +}; SyncResponse _$SyncResponseFromJson(Map json) => SyncResponse() ..duration = json['duration'] as String? - ..events = (json['events'] as List?) - ?.map((e) => Event.fromJson(e as Map)) - .toList() ?? - []; + ..events = (json['events'] as List?)?.map((e) => Event.fromJson(e as Map)).toList() ?? []; -QueryChannelsResponse _$QueryChannelsResponseFromJson( - Map json) => - QueryChannelsResponse() - ..duration = json['duration'] as String? - ..channels = (json['channels'] as List?) - ?.map((e) => ChannelState.fromJson(e as Map)) - .toList() ?? - []; - -TranslateMessageResponse _$TranslateMessageResponseFromJson( - Map json) => - TranslateMessageResponse() - ..duration = json['duration'] as String? - ..message = Message.fromJson(json['message'] as Map); +QueryChannelsResponse _$QueryChannelsResponseFromJson(Map json) => QueryChannelsResponse() + ..duration = json['duration'] as String? + ..channels = + (json['channels'] as List?)?.map((e) => ChannelState.fromJson(e as Map)).toList() ?? []; -QueryMembersResponse _$QueryMembersResponseFromJson( - Map json) => - QueryMembersResponse() - ..duration = json['duration'] as String? - ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() ?? - []; +TranslateMessageResponse _$TranslateMessageResponseFromJson(Map json) => TranslateMessageResponse() + ..duration = json['duration'] as String? + ..message = Message.fromJson(json['message'] as Map); -PartialUpdateMemberResponse _$PartialUpdateMemberResponseFromJson( - Map json) => +QueryMembersResponse _$QueryMembersResponseFromJson(Map json) => QueryMembersResponse() + ..duration = json['duration'] as String? + ..members = + (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? []; + +PartialUpdateMemberResponse _$PartialUpdateMemberResponseFromJson(Map json) => PartialUpdateMemberResponse() ..duration = json['duration'] as String? - ..channelMember = - Member.fromJson(json['channel_member'] as Map); + ..channelMember = Member.fromJson(json['channel_member'] as Map); -QueryUsersResponse _$QueryUsersResponseFromJson(Map json) => - QueryUsersResponse() - ..duration = json['duration'] as String? - ..users = (json['users'] as List?) - ?.map((e) => User.fromJson(e as Map)) - .toList() ?? - []; - -QueryBannedUsersResponse _$QueryBannedUsersResponseFromJson( - Map json) => - QueryBannedUsersResponse() - ..duration = json['duration'] as String? - ..bans = (json['bans'] as List?) - ?.map((e) => BannedUser.fromJson(e as Map)) - .toList() ?? - []; - -QueryReactionsResponse _$QueryReactionsResponseFromJson( - Map json) => - QueryReactionsResponse() - ..duration = json['duration'] as String? - ..reactions = (json['reactions'] as List?) - ?.map((e) => Reaction.fromJson(e as Map)) - .toList() ?? - []; - -QueryRepliesResponse _$QueryRepliesResponseFromJson( - Map json) => - QueryRepliesResponse() - ..duration = json['duration'] as String? - ..messages = (json['messages'] as List?) - ?.map((e) => Message.fromJson(e as Map)) - .toList() ?? - []; +QueryUsersResponse _$QueryUsersResponseFromJson(Map json) => QueryUsersResponse() + ..duration = json['duration'] as String? + ..users = (json['users'] as List?)?.map((e) => User.fromJson(e as Map)).toList() ?? []; -ListDevicesResponse _$ListDevicesResponseFromJson(Map json) => - ListDevicesResponse() - ..duration = json['duration'] as String? - ..devices = (json['devices'] as List?) - ?.map((e) => Device.fromJson(e as Map)) - .toList() ?? - []; - -SendAttachmentResponse _$SendAttachmentResponseFromJson( - Map json) => - SendAttachmentResponse() - ..duration = json['duration'] as String? - ..file = json['file'] as String?; +QueryBannedUsersResponse _$QueryBannedUsersResponseFromJson(Map json) => QueryBannedUsersResponse() + ..duration = json['duration'] as String? + ..bans = (json['bans'] as List?)?.map((e) => BannedUser.fromJson(e as Map)).toList() ?? []; -SendFileResponse _$SendFileResponseFromJson(Map json) => - SendFileResponse() - ..duration = json['duration'] as String? - ..file = json['file'] as String? - ..thumbUrl = json['thumb_url'] as String?; +QueryReactionsResponse _$QueryReactionsResponseFromJson(Map json) => QueryReactionsResponse() + ..duration = json['duration'] as String? + ..reactions = + (json['reactions'] as List?)?.map((e) => Reaction.fromJson(e as Map)).toList() ?? []; -SendReactionResponse _$SendReactionResponseFromJson( - Map json) => - SendReactionResponse() - ..duration = json['duration'] as String? - ..message = Message.fromJson(json['message'] as Map) - ..reaction = Reaction.fromJson(json['reaction'] as Map); +QueryRepliesResponse _$QueryRepliesResponseFromJson(Map json) => QueryRepliesResponse() + ..duration = json['duration'] as String? + ..messages = + (json['messages'] as List?)?.map((e) => Message.fromJson(e as Map)).toList() ?? []; -ConnectGuestUserResponse _$ConnectGuestUserResponseFromJson( - Map json) => - ConnectGuestUserResponse() - ..duration = json['duration'] as String? - ..accessToken = json['access_token'] as String - ..user = User.fromJson(json['user'] as Map); +ListDevicesResponse _$ListDevicesResponseFromJson(Map json) => ListDevicesResponse() + ..duration = json['duration'] as String? + ..devices = + (json['devices'] as List?)?.map((e) => Device.fromJson(e as Map)).toList() ?? []; -UpdateUsersResponse _$UpdateUsersResponseFromJson(Map json) => - UpdateUsersResponse() - ..duration = json['duration'] as String? - ..users = (json['users'] as Map?)?.map( - (k, e) => MapEntry(k, User.fromJson(e as Map)), - ) ?? - {}; +SendAttachmentResponse _$SendAttachmentResponseFromJson(Map json) => SendAttachmentResponse() + ..duration = json['duration'] as String? + ..file = json['file'] as String?; -UpdateMessageResponse _$UpdateMessageResponseFromJson( - Map json) => - UpdateMessageResponse() - ..duration = json['duration'] as String? - ..message = Message.fromJson(json['message'] as Map); +SendFileResponse _$SendFileResponseFromJson(Map json) => SendFileResponse() + ..duration = json['duration'] as String? + ..file = json['file'] as String? + ..thumbUrl = json['thumb_url'] as String?; -SendMessageResponse _$SendMessageResponseFromJson(Map json) => - SendMessageResponse() - ..duration = json['duration'] as String? - ..message = Message.fromJson(json['message'] as Map); +SendReactionResponse _$SendReactionResponseFromJson(Map json) => SendReactionResponse() + ..duration = json['duration'] as String? + ..message = Message.fromJson(json['message'] as Map) + ..reaction = Reaction.fromJson(json['reaction'] as Map); -GetMessageResponse _$GetMessageResponseFromJson(Map json) => - GetMessageResponse() - ..duration = json['duration'] as String? - ..message = Message.fromJson(json['message'] as Map) - ..channel = json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map); - -SearchMessagesResponse _$SearchMessagesResponseFromJson( - Map json) => - SearchMessagesResponse() - ..duration = json['duration'] as String? - ..results = (json['results'] as List?) - ?.map( - (e) => GetMessageResponse.fromJson(e as Map)) - .toList() ?? - [] - ..next = json['next'] as String? - ..previous = json['previous'] as String?; - -GetMessagesByIdResponse _$GetMessagesByIdResponseFromJson( - Map json) => - GetMessagesByIdResponse() - ..duration = json['duration'] as String? - ..messages = (json['messages'] as List?) - ?.map((e) => Message.fromJson(e as Map)) - .toList() ?? - []; - -UpdateChannelResponse _$UpdateChannelResponseFromJson( - Map json) => - UpdateChannelResponse() - ..duration = json['duration'] as String? - ..channel = ChannelModel.fromJson(json['channel'] as Map) - ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() - ..message = json['message'] == null - ? null - : Message.fromJson(json['message'] as Map); - -PartialUpdateChannelResponse _$PartialUpdateChannelResponseFromJson( - Map json) => +ConnectGuestUserResponse _$ConnectGuestUserResponseFromJson(Map json) => ConnectGuestUserResponse() + ..duration = json['duration'] as String? + ..accessToken = json['access_token'] as String + ..user = User.fromJson(json['user'] as Map); + +UpdateUsersResponse _$UpdateUsersResponseFromJson(Map json) => UpdateUsersResponse() + ..duration = json['duration'] as String? + ..users = + (json['users'] as Map?)?.map( + (k, e) => MapEntry(k, User.fromJson(e as Map)), + ) ?? + {}; + +UpdateMessageResponse _$UpdateMessageResponseFromJson(Map json) => UpdateMessageResponse() + ..duration = json['duration'] as String? + ..message = Message.fromJson(json['message'] as Map); + +SendMessageResponse _$SendMessageResponseFromJson(Map json) => SendMessageResponse() + ..duration = json['duration'] as String? + ..message = Message.fromJson(json['message'] as Map); + +GetMessageResponse _$GetMessageResponseFromJson(Map json) => GetMessageResponse() + ..duration = json['duration'] as String? + ..message = Message.fromJson(json['message'] as Map) + ..channel = json['channel'] == null ? null : ChannelModel.fromJson(json['channel'] as Map); + +SearchMessagesResponse _$SearchMessagesResponseFromJson(Map json) => SearchMessagesResponse() + ..duration = json['duration'] as String? + ..results = + (json['results'] as List?) + ?.map((e) => GetMessageResponse.fromJson(e as Map)) + .toList() ?? + [] + ..next = json['next'] as String? + ..previous = json['previous'] as String?; + +GetMessagesByIdResponse _$GetMessagesByIdResponseFromJson(Map json) => GetMessagesByIdResponse() + ..duration = json['duration'] as String? + ..messages = + (json['messages'] as List?)?.map((e) => Message.fromJson(e as Map)).toList() ?? []; + +UpdateChannelResponse _$UpdateChannelResponseFromJson(Map json) => UpdateChannelResponse() + ..duration = json['duration'] as String? + ..channel = ChannelModel.fromJson(json['channel'] as Map) + ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() + ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); + +PartialUpdateChannelResponse _$PartialUpdateChannelResponseFromJson(Map json) => PartialUpdateChannelResponse() ..duration = json['duration'] as String? ..channel = ChannelModel.fromJson(json['channel'] as Map) - ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList(); + ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList(); -InviteMembersResponse _$InviteMembersResponseFromJson( - Map json) => - InviteMembersResponse() - ..duration = json['duration'] as String? - ..channel = ChannelModel.fromJson(json['channel'] as Map) - ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() ?? - [] - ..message = json['message'] == null - ? null - : Message.fromJson(json['message'] as Map); - -RemoveMembersResponse _$RemoveMembersResponseFromJson( - Map json) => - RemoveMembersResponse() - ..duration = json['duration'] as String? - ..channel = ChannelModel.fromJson(json['channel'] as Map) - ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() ?? - [] - ..message = json['message'] == null - ? null - : Message.fromJson(json['message'] as Map); - -SendActionResponse _$SendActionResponseFromJson(Map json) => - SendActionResponse() - ..duration = json['duration'] as String? - ..message = json['message'] == null - ? null - : Message.fromJson(json['message'] as Map); +InviteMembersResponse _$InviteMembersResponseFromJson(Map json) => InviteMembersResponse() + ..duration = json['duration'] as String? + ..channel = ChannelModel.fromJson(json['channel'] as Map) + ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? [] + ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); -AddMembersResponse _$AddMembersResponseFromJson(Map json) => - AddMembersResponse() - ..duration = json['duration'] as String? - ..channel = ChannelModel.fromJson(json['channel'] as Map) - ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() ?? - [] - ..message = json['message'] == null - ? null - : Message.fromJson(json['message'] as Map); - -AcceptInviteResponse _$AcceptInviteResponseFromJson( - Map json) => - AcceptInviteResponse() - ..duration = json['duration'] as String? - ..channel = ChannelModel.fromJson(json['channel'] as Map) - ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() ?? - [] - ..message = json['message'] == null - ? null - : Message.fromJson(json['message'] as Map); - -RejectInviteResponse _$RejectInviteResponseFromJson( - Map json) => - RejectInviteResponse() - ..duration = json['duration'] as String? - ..channel = ChannelModel.fromJson(json['channel'] as Map) - ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() ?? - [] - ..message = json['message'] == null - ? null - : Message.fromJson(json['message'] as Map); +RemoveMembersResponse _$RemoveMembersResponseFromJson(Map json) => RemoveMembersResponse() + ..duration = json['duration'] as String? + ..channel = ChannelModel.fromJson(json['channel'] as Map) + ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? [] + ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); + +SendActionResponse _$SendActionResponseFromJson(Map json) => SendActionResponse() + ..duration = json['duration'] as String? + ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); + +AddMembersResponse _$AddMembersResponseFromJson(Map json) => AddMembersResponse() + ..duration = json['duration'] as String? + ..channel = ChannelModel.fromJson(json['channel'] as Map) + ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? [] + ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); + +AcceptInviteResponse _$AcceptInviteResponseFromJson(Map json) => AcceptInviteResponse() + ..duration = json['duration'] as String? + ..channel = ChannelModel.fromJson(json['channel'] as Map) + ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? [] + ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); + +RejectInviteResponse _$RejectInviteResponseFromJson(Map json) => RejectInviteResponse() + ..duration = json['duration'] as String? + ..channel = ChannelModel.fromJson(json['channel'] as Map) + ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? [] + ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); EmptyResponse _$EmptyResponseFromJson(Map json) => EmptyResponse()..duration = json['duration'] as String?; -ChannelStateResponse _$ChannelStateResponseFromJson( - Map json) => - ChannelStateResponse() - ..duration = json['duration'] as String? - ..channel = ChannelModel.fromJson(json['channel'] as Map) - ..messages = (json['messages'] as List?) - ?.map((e) => Message.fromJson(e as Map)) - .toList() ?? - [] - ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() ?? - [] - ..watcherCount = (json['watcher_count'] as num?)?.toInt() ?? 0 - ..read = (json['read'] as List?) - ?.map((e) => Read.fromJson(e as Map)) - .toList() ?? - []; - -OGAttachmentResponse _$OGAttachmentResponseFromJson( - Map json) => - OGAttachmentResponse() - ..duration = json['duration'] as String? - ..ogScrapeUrl = json['og_scrape_url'] as String - ..assetUrl = json['asset_url'] as String? - ..authorLink = json['author_link'] as String? - ..authorName = json['author_name'] as String? - ..imageUrl = json['image_url'] as String? - ..text = json['text'] as String? - ..thumbUrl = json['thumb_url'] as String? - ..title = json['title'] as String? - ..titleLink = json['title_link'] as String? - ..type = json['type'] as String?; - -UserBlockResponse _$UserBlockResponseFromJson(Map json) => - UserBlockResponse() - ..duration = json['duration'] as String? - ..blockedByUserId = json['blocked_by_user_id'] as String? ?? '' - ..blockedUserId = json['blocked_user_id'] as String? ?? '' - ..createdAt = DateTime.parse(json['created_at'] as String); +ChannelStateResponse _$ChannelStateResponseFromJson(Map json) => ChannelStateResponse() + ..duration = json['duration'] as String? + ..channel = ChannelModel.fromJson(json['channel'] as Map) + ..messages = + (json['messages'] as List?)?.map((e) => Message.fromJson(e as Map)).toList() ?? [] + ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? [] + ..watcherCount = (json['watcher_count'] as num?)?.toInt() ?? 0 + ..read = (json['read'] as List?)?.map((e) => Read.fromJson(e as Map)).toList() ?? []; + +OGAttachmentResponse _$OGAttachmentResponseFromJson(Map json) => OGAttachmentResponse() + ..duration = json['duration'] as String? + ..ogScrapeUrl = json['og_scrape_url'] as String + ..assetUrl = json['asset_url'] as String? + ..authorLink = json['author_link'] as String? + ..authorName = json['author_name'] as String? + ..imageUrl = json['image_url'] as String? + ..text = json['text'] as String? + ..thumbUrl = json['thumb_url'] as String? + ..title = json['title'] as String? + ..titleLink = json['title_link'] as String? + ..type = json['type'] as String?; + +UserBlockResponse _$UserBlockResponseFromJson(Map json) => UserBlockResponse() + ..duration = json['duration'] as String? + ..blockedByUserId = json['blocked_by_user_id'] as String? ?? '' + ..blockedUserId = json['blocked_user_id'] as String? ?? '' + ..createdAt = DateTime.parse(json['created_at'] as String); -BlockedUsersResponse _$BlockedUsersResponseFromJson( - Map json) => - BlockedUsersResponse() - ..duration = json['duration'] as String? - ..blocks = (json['blocks'] as List?) - ?.map((e) => UserBlock.fromJson(e as Map)) - .toList() ?? - []; +BlockedUsersResponse _$BlockedUsersResponseFromJson(Map json) => BlockedUsersResponse() + ..duration = json['duration'] as String? + ..blocks = + (json['blocks'] as List?)?.map((e) => UserBlock.fromJson(e as Map)).toList() ?? []; -CreatePollResponse _$CreatePollResponseFromJson(Map json) => - CreatePollResponse() - ..duration = json['duration'] as String? - ..poll = Poll.fromJson(json['poll'] as Map); +CreatePollResponse _$CreatePollResponseFromJson(Map json) => CreatePollResponse() + ..duration = json['duration'] as String? + ..poll = Poll.fromJson(json['poll'] as Map); -GetPollResponse _$GetPollResponseFromJson(Map json) => - GetPollResponse() - ..duration = json['duration'] as String? - ..poll = Poll.fromJson(json['poll'] as Map); +GetPollResponse _$GetPollResponseFromJson(Map json) => GetPollResponse() + ..duration = json['duration'] as String? + ..poll = Poll.fromJson(json['poll'] as Map); -UpdatePollResponse _$UpdatePollResponseFromJson(Map json) => - UpdatePollResponse() - ..duration = json['duration'] as String? - ..poll = Poll.fromJson(json['poll'] as Map); +UpdatePollResponse _$UpdatePollResponseFromJson(Map json) => UpdatePollResponse() + ..duration = json['duration'] as String? + ..poll = Poll.fromJson(json['poll'] as Map); -CreatePollOptionResponse _$CreatePollOptionResponseFromJson( - Map json) => - CreatePollOptionResponse() - ..duration = json['duration'] as String? - ..pollOption = - PollOption.fromJson(json['poll_option'] as Map); +CreatePollOptionResponse _$CreatePollOptionResponseFromJson(Map json) => CreatePollOptionResponse() + ..duration = json['duration'] as String? + ..pollOption = PollOption.fromJson(json['poll_option'] as Map); -GetPollOptionResponse _$GetPollOptionResponseFromJson( - Map json) => - GetPollOptionResponse() - ..duration = json['duration'] as String? - ..pollOption = - PollOption.fromJson(json['poll_option'] as Map); +GetPollOptionResponse _$GetPollOptionResponseFromJson(Map json) => GetPollOptionResponse() + ..duration = json['duration'] as String? + ..pollOption = PollOption.fromJson(json['poll_option'] as Map); -UpdatePollOptionResponse _$UpdatePollOptionResponseFromJson( - Map json) => - UpdatePollOptionResponse() - ..duration = json['duration'] as String? - ..pollOption = - PollOption.fromJson(json['poll_option'] as Map); +UpdatePollOptionResponse _$UpdatePollOptionResponseFromJson(Map json) => UpdatePollOptionResponse() + ..duration = json['duration'] as String? + ..pollOption = PollOption.fromJson(json['poll_option'] as Map); -CastPollVoteResponse _$CastPollVoteResponseFromJson( - Map json) => - CastPollVoteResponse() - ..duration = json['duration'] as String? - ..vote = PollVote.fromJson(json['vote'] as Map); +CastPollVoteResponse _$CastPollVoteResponseFromJson(Map json) => CastPollVoteResponse() + ..duration = json['duration'] as String? + ..vote = PollVote.fromJson(json['vote'] as Map); -RemovePollVoteResponse _$RemovePollVoteResponseFromJson( - Map json) => - RemovePollVoteResponse() - ..duration = json['duration'] as String? - ..vote = PollVote.fromJson(json['vote'] as Map); +RemovePollVoteResponse _$RemovePollVoteResponseFromJson(Map json) => RemovePollVoteResponse() + ..duration = json['duration'] as String? + ..vote = PollVote.fromJson(json['vote'] as Map); -QueryPollsResponse _$QueryPollsResponseFromJson(Map json) => - QueryPollsResponse() - ..duration = json['duration'] as String? - ..polls = (json['polls'] as List?) - ?.map((e) => Poll.fromJson(e as Map)) - .toList() ?? - [] - ..next = json['next'] as String?; - -QueryPollVotesResponse _$QueryPollVotesResponseFromJson( - Map json) => - QueryPollVotesResponse() - ..duration = json['duration'] as String? - ..votes = (json['votes'] as List?) - ?.map((e) => PollVote.fromJson(e as Map)) - .toList() ?? - [] - ..next = json['next'] as String?; - -GetThreadResponse _$GetThreadResponseFromJson(Map json) => - GetThreadResponse() - ..duration = json['duration'] as String? - ..thread = Thread.fromJson(json['thread'] as Map); +QueryPollsResponse _$QueryPollsResponseFromJson(Map json) => QueryPollsResponse() + ..duration = json['duration'] as String? + ..polls = (json['polls'] as List?)?.map((e) => Poll.fromJson(e as Map)).toList() ?? [] + ..next = json['next'] as String?; -UpdateThreadResponse _$UpdateThreadResponseFromJson( - Map json) => - UpdateThreadResponse() - ..duration = json['duration'] as String? - ..thread = Thread.fromJson(json['thread'] as Map); +QueryPollVotesResponse _$QueryPollVotesResponseFromJson(Map json) => QueryPollVotesResponse() + ..duration = json['duration'] as String? + ..votes = (json['votes'] as List?)?.map((e) => PollVote.fromJson(e as Map)).toList() ?? [] + ..next = json['next'] as String?; -QueryThreadsResponse _$QueryThreadsResponseFromJson( - Map json) => - QueryThreadsResponse() - ..duration = json['duration'] as String? - ..threads = (json['threads'] as List?) - ?.map((e) => Thread.fromJson(e as Map)) - .toList() ?? - [] - ..next = json['next'] as String?; - -CreateDraftResponse _$CreateDraftResponseFromJson(Map json) => - CreateDraftResponse() - ..duration = json['duration'] as String? - ..draft = Draft.fromJson(json['draft'] as Map); +GetThreadResponse _$GetThreadResponseFromJson(Map json) => GetThreadResponse() + ..duration = json['duration'] as String? + ..thread = Thread.fromJson(json['thread'] as Map); -GetDraftResponse _$GetDraftResponseFromJson(Map json) => - GetDraftResponse() - ..duration = json['duration'] as String? - ..draft = Draft.fromJson(json['draft'] as Map); +UpdateThreadResponse _$UpdateThreadResponseFromJson(Map json) => UpdateThreadResponse() + ..duration = json['duration'] as String? + ..thread = Thread.fromJson(json['thread'] as Map); -QueryDraftsResponse _$QueryDraftsResponseFromJson(Map json) => - QueryDraftsResponse() - ..duration = json['duration'] as String? - ..drafts = (json['drafts'] as List?) - ?.map((e) => Draft.fromJson(e as Map)) - .toList() ?? - [] - ..next = json['next'] as String?; - -CreateReminderResponse _$CreateReminderResponseFromJson( - Map json) => - CreateReminderResponse() - ..duration = json['duration'] as String? - ..reminder = - MessageReminder.fromJson(json['reminder'] as Map); +QueryThreadsResponse _$QueryThreadsResponseFromJson(Map json) => QueryThreadsResponse() + ..duration = json['duration'] as String? + ..threads = (json['threads'] as List?)?.map((e) => Thread.fromJson(e as Map)).toList() ?? [] + ..next = json['next'] as String?; -UpdateReminderResponse _$UpdateReminderResponseFromJson( - Map json) => - UpdateReminderResponse() - ..duration = json['duration'] as String? - ..reminder = - MessageReminder.fromJson(json['reminder'] as Map); +CreateDraftResponse _$CreateDraftResponseFromJson(Map json) => CreateDraftResponse() + ..duration = json['duration'] as String? + ..draft = Draft.fromJson(json['draft'] as Map); -QueryRemindersResponse _$QueryRemindersResponseFromJson( - Map json) => - QueryRemindersResponse() - ..duration = json['duration'] as String? - ..reminders = (json['reminders'] as List?) - ?.map((e) => MessageReminder.fromJson(e as Map)) - .toList() ?? - [] - ..next = json['next'] as String?; - -GetUnreadCountResponse _$GetUnreadCountResponseFromJson( - Map json) => - GetUnreadCountResponse() - ..duration = json['duration'] as String? - ..totalUnreadCount = (json['total_unread_count'] as num).toInt() - ..totalUnreadThreadsCount = - (json['total_unread_threads_count'] as num).toInt() - ..totalUnreadCountByTeam = - (json['total_unread_count_by_team'] as Map?)?.map( - (k, e) => MapEntry(k, (e as num).toInt()), - ) - ..channels = (json['channels'] as List) - .map((e) => UnreadCountsChannel.fromJson(e as Map)) - .toList() - ..channelType = (json['channel_type'] as List) - .map((e) => - UnreadCountsChannelType.fromJson(e as Map)) - .toList() - ..threads = (json['threads'] as List) - .map((e) => UnreadCountsThread.fromJson(e as Map)) - .toList(); +GetDraftResponse _$GetDraftResponseFromJson(Map json) => GetDraftResponse() + ..duration = json['duration'] as String? + ..draft = Draft.fromJson(json['draft'] as Map); + +QueryDraftsResponse _$QueryDraftsResponseFromJson(Map json) => QueryDraftsResponse() + ..duration = json['duration'] as String? + ..drafts = (json['drafts'] as List?)?.map((e) => Draft.fromJson(e as Map)).toList() ?? [] + ..next = json['next'] as String?; + +CreateReminderResponse _$CreateReminderResponseFromJson(Map json) => CreateReminderResponse() + ..duration = json['duration'] as String? + ..reminder = MessageReminder.fromJson(json['reminder'] as Map); -UpsertPushPreferencesResponse _$UpsertPushPreferencesResponseFromJson( - Map json) => +UpdateReminderResponse _$UpdateReminderResponseFromJson(Map json) => UpdateReminderResponse() + ..duration = json['duration'] as String? + ..reminder = MessageReminder.fromJson(json['reminder'] as Map); + +QueryRemindersResponse _$QueryRemindersResponseFromJson(Map json) => QueryRemindersResponse() + ..duration = json['duration'] as String? + ..reminders = + (json['reminders'] as List?)?.map((e) => MessageReminder.fromJson(e as Map)).toList() ?? + [] + ..next = json['next'] as String?; + +GetUnreadCountResponse _$GetUnreadCountResponseFromJson(Map json) => GetUnreadCountResponse() + ..duration = json['duration'] as String? + ..totalUnreadCount = (json['total_unread_count'] as num).toInt() + ..totalUnreadThreadsCount = (json['total_unread_threads_count'] as num).toInt() + ..totalUnreadCountByTeam = (json['total_unread_count_by_team'] as Map?)?.map( + (k, e) => MapEntry(k, (e as num).toInt()), + ) + ..channels = (json['channels'] as List) + .map((e) => UnreadCountsChannel.fromJson(e as Map)) + .toList() + ..channelType = (json['channel_type'] as List) + .map((e) => UnreadCountsChannelType.fromJson(e as Map)) + .toList() + ..threads = (json['threads'] as List) + .map((e) => UnreadCountsThread.fromJson(e as Map)) + .toList(); + +UpsertPushPreferencesResponse _$UpsertPushPreferencesResponseFromJson(Map json) => UpsertPushPreferencesResponse() ..duration = json['duration'] as String? - ..userPreferences = (json['user_preferences'] as Map?) - ?.map( - (k, e) => - MapEntry(k, PushPreference.fromJson(e as Map)), + ..userPreferences = + (json['user_preferences'] as Map?)?.map( + (k, e) => MapEntry(k, PushPreference.fromJson(e as Map)), ) ?? {} ..userChannelPreferences = (json['user_channel_preferences'] as Map?)?.map( - (k, e) => MapEntry( - k, - (e as Map).map( - (k, e) => MapEntry( - k, - ChannelPushPreference.fromJson( - e as Map)), - )), - ) ?? - {}; - -GetActiveLiveLocationsResponse _$GetActiveLiveLocationsResponseFromJson( - Map json) => + (k, e) => MapEntry( + k, + (e as Map).map( + (k, e) => MapEntry(k, ChannelPushPreference.fromJson(e as Map)), + ), + ), + ) ?? + {}; + +GetActiveLiveLocationsResponse _$GetActiveLiveLocationsResponseFromJson(Map json) => GetActiveLiveLocationsResponse() ..duration = json['duration'] as String? ..activeLiveLocations = (json['active_live_locations'] as List) diff --git a/packages/stream_chat/lib/src/core/api/sort_order.dart b/packages/stream_chat/lib/src/core/api/sort_order.dart index e04ca10264..2a1c7cad58 100644 --- a/packages/stream_chat/lib/src/core/api/sort_order.dart +++ b/packages/stream_chat/lib/src/core/api/sort_order.dart @@ -21,7 +21,7 @@ enum NullOrdering { /// Null values appear at the end of the sorted list, /// regardless of sort direction (ASC or DESC). - nullsLast; + nullsLast, } /// A sort specification for objects that implement [ComparableFieldProvider]. @@ -47,8 +47,8 @@ class SortOption { this.field, { this.nullOrdering = NullOrdering.nullsFirst, Comparator? comparator, - }) : direction = SortOption.DESC, - _comparator = comparator; + }) : direction = SortOption.DESC, + _comparator = comparator; /// Creates a SortOption for ascending order sorting by the specified field. /// @@ -61,8 +61,8 @@ class SortOption { this.field, { this.nullOrdering = NullOrdering.nullsLast, Comparator? comparator, - }) : direction = SortOption.ASC, - _comparator = comparator; + }) : direction = SortOption.ASC, + _comparator = comparator; /// Ascending order (1) static const ASC = 1; @@ -132,8 +132,7 @@ class SortOption { } /// Extension that allows a [SortOrder] to be used as a comparator function. -extension CompositeComparator - on SortOrder { +extension CompositeComparator on SortOrder { /// Compares two objects using all sort options in sequence. /// /// Returns the first non-zero comparison result, or 0 if all comparisons diff --git a/packages/stream_chat/lib/src/core/api/sort_order.g.dart b/packages/stream_chat/lib/src/core/api/sort_order.g.dart index 31f18e61c9..c54ab585ab 100644 --- a/packages/stream_chat/lib/src/core/api/sort_order.g.dart +++ b/packages/stream_chat/lib/src/core/api/sort_order.g.dart @@ -6,9 +6,7 @@ part of 'sort_order.dart'; // JsonSerializableGenerator // ************************************************************************** -Map _$SortOptionToJson( - SortOption instance) => - { - 'field': instance.field, - 'direction': instance.direction, - }; +Map _$SortOptionToJson(SortOption instance) => { + 'field': instance.field, + 'direction': instance.direction, +}; diff --git a/packages/stream_chat/lib/src/core/api/stream_chat_api.dart b/packages/stream_chat/lib/src/core/api/stream_chat_api.dart index 85cbf4aa7e..5ece808a93 100644 --- a/packages/stream_chat/lib/src/core/api/stream_chat_api.dart +++ b/packages/stream_chat/lib/src/core/api/stream_chat_api.dart @@ -28,23 +28,23 @@ class StreamChatApi { TokenManager? tokenManager, ConnectionIdManager? connectionIdManager, SystemEnvironmentManager? systemEnvironmentManager, - AttachmentFileUploaderProvider attachmentFileUploaderProvider = - StreamAttachmentFileUploader.new, + AttachmentFileUploaderProvider attachmentFileUploaderProvider = StreamAttachmentFileUploader.new, Logger? logger, Iterable? interceptors, HttpClientAdapter? httpClientAdapter, - }) : _fileUploaderProvider = attachmentFileUploaderProvider, - _client = client ?? - StreamHttpClient( - apiKey, - options: options, - tokenManager: tokenManager, - connectionIdManager: connectionIdManager, - systemEnvironmentManager: systemEnvironmentManager, - logger: logger, - interceptors: interceptors, - httpClientAdapter: httpClientAdapter, - ); + }) : _fileUploaderProvider = attachmentFileUploaderProvider, + _client = + client ?? + StreamHttpClient( + apiKey, + options: options, + tokenManager: tokenManager, + connectionIdManager: connectionIdManager, + systemEnvironmentManager: systemEnvironmentManager, + logger: logger, + interceptors: interceptors, + httpClientAdapter: httpClientAdapter, + ); final StreamHttpClient _client; final AttachmentFileUploaderProvider _fileUploaderProvider; @@ -90,7 +90,6 @@ class StreamChatApi { GeneralApi? _general; /// Class responsible for uploading images and files to a given channel - AttachmentFileUploader get fileUploader => - _fileUploader ??= _fileUploaderProvider.call(_client); + AttachmentFileUploader get fileUploader => _fileUploader ??= _fileUploaderProvider.call(_client); AttachmentFileUploader? _fileUploader; } diff --git a/packages/stream_chat/lib/src/core/api/user_api.dart b/packages/stream_chat/lib/src/core/api/user_api.dart index 668e7ac70a..00a38f0ad4 100644 --- a/packages/stream_chat/lib/src/core/api/user_api.dart +++ b/packages/stream_chat/lib/src/core/api/user_api.dart @@ -118,8 +118,7 @@ class UserApi { '/users/live_locations', data: json.encode({ 'message_id': messageId, - if (createdByDeviceId != null) - 'created_by_device_id': createdByDeviceId, + if (createdByDeviceId != null) 'created_by_device_id': createdByDeviceId, if (location?.latitude case final latitude) 'latitude': latitude, if (location?.longitude case final longitude) 'longitude': longitude, if (endAt != null) 'end_at': endAt.toIso8601String(), diff --git a/packages/stream_chat/lib/src/core/error/chat_error_code.dart b/packages/stream_chat/lib/src/core/error/chat_error_code.dart index d597000ad6..b35c010da2 100644 --- a/packages/stream_chat/lib/src/core/error/chat_error_code.dart +++ b/packages/stream_chat/lib/src/core/error/chat_error_code.dart @@ -94,10 +94,8 @@ enum ChatErrorCode { } const _errorCodeWithDescription = { - ChatErrorCode.undefinedToken: - MapEntry(1000, 'Unauthorised, token not defined'), - ChatErrorCode.inputError: - MapEntry(4, 'Wrong data/parameter is sent to the API'), + ChatErrorCode.undefinedToken: MapEntry(1000, 'Unauthorised, token not defined'), + ChatErrorCode.inputError: MapEntry(4, 'Wrong data/parameter is sent to the API'), ChatErrorCode.duplicateUsername: MapEntry( 6, 'Duplicate username is sent while enforce_unique_usernames is enabled', @@ -112,36 +110,24 @@ const _errorCodeWithDescription = { 21, 'Multiple Levels Reply is not supported - the API only supports 1 level deep reply threads', ), - ChatErrorCode.customCommandEndpointCall: - MapEntry(45, 'Custom Command handler returned an error'), - ChatErrorCode.customCommandEndpointMissing: - MapEntry(44, 'App config does not have custom_action_handler_url'), - ChatErrorCode.authenticationError: - MapEntry(5, 'Unauthenticated, problem with authentication'), + ChatErrorCode.customCommandEndpointCall: MapEntry(45, 'Custom Command handler returned an error'), + ChatErrorCode.customCommandEndpointMissing: MapEntry(44, 'App config does not have custom_action_handler_url'), + ChatErrorCode.authenticationError: MapEntry(5, 'Unauthenticated, problem with authentication'), ChatErrorCode.tokenExpired: MapEntry(40, 'Unauthenticated, token expired'), - ChatErrorCode.tokenBeforeIssuedAt: - MapEntry(42, 'Unauthenticated, token date incorrect'), - ChatErrorCode.tokenNotValid: - MapEntry(41, 'Unauthenticated, token not valid yet'), - ChatErrorCode.tokenSignatureInvalid: - MapEntry(43, 'Unauthenticated, token signature invalid'), + ChatErrorCode.tokenBeforeIssuedAt: MapEntry(42, 'Unauthenticated, token date incorrect'), + ChatErrorCode.tokenNotValid: MapEntry(41, 'Unauthenticated, token not valid yet'), + ChatErrorCode.tokenSignatureInvalid: MapEntry(43, 'Unauthenticated, token signature invalid'), ChatErrorCode.accessKeyError: MapEntry(2, 'Access Key invalid'), - ChatErrorCode.notAllowed: - MapEntry(17, 'Unauthorised / forbidden to make request'), + ChatErrorCode.notAllowed: MapEntry(17, 'Unauthorised / forbidden to make request'), ChatErrorCode.appSuspended: MapEntry(99, 'App suspended'), - ChatErrorCode.cooldownError: - MapEntry(60, 'User tried to post a message during the cooldown period'), + ChatErrorCode.cooldownError: MapEntry(60, 'User tried to post a message during the cooldown period'), ChatErrorCode.doesNotExist: MapEntry(16, 'Resource not found'), ChatErrorCode.requestTimeout: MapEntry(23, 'Request timed out'), ChatErrorCode.payloadTooBig: MapEntry(22, 'Payload too big'), - ChatErrorCode.rateLimitError: - MapEntry(9, 'Too many requests in a certain time frame'), - ChatErrorCode.maximumHeaderSizeExceeded: - MapEntry(24, 'Request headers are too large'), - ChatErrorCode.internalSystemError: - MapEntry(-1, 'Something goes wrong in the system'), - ChatErrorCode.noAccessToChannels: - MapEntry(70, 'No access to requested channels'), + ChatErrorCode.rateLimitError: MapEntry(9, 'Too many requests in a certain time frame'), + ChatErrorCode.maximumHeaderSizeExceeded: MapEntry(24, 'Request headers are too large'), + ChatErrorCode.internalSystemError: MapEntry(-1, 'Something goes wrong in the system'), + ChatErrorCode.noAccessToChannels: MapEntry(70, 'No access to requested channels'), }; const _authenticationErrors = [ @@ -156,8 +142,8 @@ const _authenticationErrors = [ ]; /// -ChatErrorCode? chatErrorCodeFromCode(int code) => _errorCodeWithDescription.keys - .firstWhereOrNull((key) => _errorCodeWithDescription[key]!.key == code); +ChatErrorCode? chatErrorCodeFromCode(int code) => + _errorCodeWithDescription.keys.firstWhereOrNull((key) => _errorCodeWithDescription[key]!.key == code); /// extension ChatErrorCodeX on ChatErrorCode { diff --git a/packages/stream_chat/lib/src/core/error/stream_chat_error.dart b/packages/stream_chat/lib/src/core/error/stream_chat_error.dart index 5e88fb3bc9..b2925940e5 100644 --- a/packages/stream_chat/lib/src/core/error/stream_chat_error.dart +++ b/packages/stream_chat/lib/src/core/error/stream_chat_error.dart @@ -78,10 +78,10 @@ class StreamChatNetworkError extends StreamChatError { this.data, StackTrace? stacktrace, this.isRequestCancelledError = false, - }) : code = errorCode.code, - statusCode = statusCode ?? data?.statusCode, - stackTrace = stacktrace ?? StackTrace.current, - super(errorCode.message); + }) : code = errorCode.code, + statusCode = statusCode ?? data?.statusCode, + stackTrace = stacktrace ?? StackTrace.current, + super(errorCode.message); /// StreamChatNetworkError.raw({ @@ -91,8 +91,8 @@ class StreamChatNetworkError extends StreamChatError { this.data, StackTrace? stacktrace, this.isRequestCancelledError = false, - }) : stackTrace = stacktrace ?? StackTrace.current, - super(message); + }) : stackTrace = stacktrace ?? StackTrace.current, + super(message); /// factory StreamChatNetworkError.fromDioException(DioException exception) { @@ -106,10 +106,7 @@ class StreamChatNetworkError extends StreamChatError { } return StreamChatNetworkError.raw( code: errorResponse?.code ?? -1, - message: errorResponse?.message ?? - response?.statusMessage ?? - exception.message ?? - '', + message: errorResponse?.message ?? response?.statusMessage ?? exception.message ?? '', statusCode: errorResponse?.statusCode ?? response?.statusCode, data: errorResponse, stacktrace: exception.stackTrace, diff --git a/packages/stream_chat/lib/src/core/http/interceptor/logging_interceptor.dart b/packages/stream_chat/lib/src/core/http/interceptor/logging_interceptor.dart index 6d6f74c301..258489471e 100644 --- a/packages/stream_chat/lib/src/core/http/interceptor/logging_interceptor.dart +++ b/packages/stream_chat/lib/src/core/http/interceptor/logging_interceptor.dart @@ -125,8 +125,7 @@ class LoggingInterceptor extends Interceptor { final uri = exception.response?.requestOptions.uri; _printBoxed( _logPrintError, - header: - 'DioException ║ Status: ${exception.response?.statusCode} ${exception.response?.statusMessage}', + header: 'DioException ║ Status: ${exception.response?.statusCode} ${exception.response?.statusMessage}', text: uri.toString(), ); if (exception.response != null && exception.response?.data != null) { @@ -152,8 +151,7 @@ class LoggingInterceptor extends Interceptor { _printResponseHeader(_logPrintResponse, response); if (responseHeader) { final responseHeaders = {}; - response.headers - .forEach((k, list) => responseHeaders[k] = list.toString()); + response.headers.forEach((k, list) => responseHeaders[k] = list.toString()); _printMapAsTable(_logPrintResponse, responseHeaders, header: 'Headers'); } @@ -197,8 +195,7 @@ class LoggingInterceptor extends Interceptor { final method = response.requestOptions.method; _printBoxed( logPrint, - header: - 'Response ║ $method ║ Status: ${response.statusCode} ${response.statusMessage}', + header: 'Response ║ $method ║ Status: ${response.statusCode} ${response.statusMessage}', text: uri.toString(), ); } @@ -216,8 +213,7 @@ class LoggingInterceptor extends Interceptor { void Function(Object) logPrint, [ String pre = '', String suf = '╝', - ]) => - logPrint('$pre${'═' * maxWidth}$suf'); + ]) => logPrint('$pre${'═' * maxWidth}$suf'); void _printKV(void Function(Object) logPrint, String? key, Object? v) { final pre = '╟ $key: '; @@ -234,11 +230,13 @@ class LoggingInterceptor extends Interceptor { void _printBlock(void Function(Object) logPrint, String msg) { final lines = (msg.length / maxWidth).ceil(); for (var i = 0; i < lines; ++i) { - logPrint((i >= 0 ? '║ ' : '') + - msg.substring( - i * maxWidth, - math.min(i * maxWidth + maxWidth, msg.length), - )); + logPrint( + (i >= 0 ? '║ ' : '') + + msg.substring( + i * maxWidth, + math.min(i * maxWidth + maxWidth, msg.length), + ), + ); } } @@ -286,10 +284,12 @@ class LoggingInterceptor extends Interceptor { if (msg.length + indent.length > linWidth) { final lines = (msg.length / linWidth).ceil(); for (var i = 0; i < lines; ++i) { - logPrint('║${_indent(_tabs)} ${msg.substring( - i * linWidth, - math.min(i * linWidth + linWidth, msg.length), - )}'); + logPrint( + '║${_indent(_tabs)} ${msg.substring( + i * linWidth, + math.min(i * linWidth + linWidth, msg.length), + )}', + ); } } else { logPrint('║${_indent(_tabs)} $key: $msg${!isLast ? ',' : ''}'); @@ -332,16 +332,13 @@ class LoggingInterceptor extends Interceptor { }) { if (map == null || map.isEmpty) return; logPrint('╔ $header '); - map.forEach((dynamic key, dynamic value) => - _printKV(logPrint, key.toString(), value)); + map.forEach((dynamic key, dynamic value) => _printKV(logPrint, key.toString(), value)); _printLine(logPrint, '╚'); } - void _logPrintRequest(Object object) => - logPrint(InterceptStep.request, object); + void _logPrintRequest(Object object) => logPrint(InterceptStep.request, object); - void _logPrintResponse(Object object) => - logPrint(InterceptStep.response, object); + void _logPrintResponse(Object object) => logPrint(InterceptStep.response, object); void _logPrintError(Object object) => logPrint(InterceptStep.error, object); } diff --git a/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart b/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart index d59a032c63..25b75fd82e 100644 --- a/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart +++ b/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart @@ -12,9 +12,9 @@ class StreamChatDioError extends DioException { StackTrace? stackTrace, super.message, }) : super( - error: error, - stackTrace: stackTrace ?? StackTrace.current, - ); + error: error, + stackTrace: stackTrace ?? StackTrace.current, + ); @override final StreamChatNetworkError error; diff --git a/packages/stream_chat/lib/src/core/http/stream_http_client.dart b/packages/stream_chat/lib/src/core/http/stream_http_client.dart index 16141c0a26..7c887246e0 100644 --- a/packages/stream_chat/lib/src/core/http/stream_http_client.dart +++ b/packages/stream_chat/lib/src/core/http/stream_http_client.dart @@ -29,8 +29,8 @@ class StreamHttpClient { Logger? logger, Iterable? interceptors, HttpClientAdapter? httpClientAdapter, - }) : _options = options ?? const StreamHttpClientOptions(), - httpClient = dio ?? Dio() { + }) : _options = options ?? const StreamHttpClientOptions(), + httpClient = dio ?? Dio() { httpClient ..options.baseUrl = _options.baseUrl ..options.receiveTimeout = _options.receiveTimeout @@ -47,8 +47,7 @@ class StreamHttpClient { ..interceptors.addAll([ AdditionalHeadersInterceptor(systemEnvironmentManager), if (tokenManager != null) AuthInterceptor(this, tokenManager), - if (connectionIdManager != null) - ConnectionIdInterceptor(connectionIdManager), + if (connectionIdManager != null) ConnectionIdInterceptor(connectionIdManager), ...interceptors ?? [ // Add a default logging interceptor if no interceptors are diff --git a/packages/stream_chat/lib/src/core/http/system_environment_manager.dart b/packages/stream_chat/lib/src/core/http/system_environment_manager.dart index 42bd01c3ea..bb4648e7d3 100644 --- a/packages/stream_chat/lib/src/core/http/system_environment_manager.dart +++ b/packages/stream_chat/lib/src/core/http/system_environment_manager.dart @@ -12,14 +12,14 @@ class SystemEnvironmentManager { SystemEnvironmentManager({ SystemEnvironment? environment, }) : _environment = switch (environment) { - final env? => env, - _ => SystemEnvironment( - sdkName: 'stream-chat', - sdkIdentifier: 'dart', - sdkVersion: PACKAGE_VERSION, - osName: CurrentPlatform.name, - ), - }; + final env? => env, + _ => SystemEnvironment( + sdkName: 'stream-chat', + sdkIdentifier: 'dart', + sdkVersion: PACKAGE_VERSION, + osName: CurrentPlatform.name, + ), + }; /// Returns the Stream client user agent string based on the current /// [environment] value. diff --git a/packages/stream_chat/lib/src/core/http/token.dart b/packages/stream_chat/lib/src/core/http/token.dart index 2472a1f7f5..df1114a0ca 100644 --- a/packages/stream_chat/lib/src/core/http/token.dart +++ b/packages/stream_chat/lib/src/core/http/token.dart @@ -29,10 +29,10 @@ class Token extends Equatable { /// The token that can be used when user is unknown. /// Is used by `anonymous` token provider. factory Token.anonymous({String? userId}) => Token._( - rawValue: '', - userId: userId ?? randomId(), - authType: AuthType.anonymous, - ); + rawValue: '', + userId: userId ?? randomId(), + authType: AuthType.anonymous, + ); /// Creates a [Token] instance from the provided [rawValue] if it's valid. factory Token.fromRawValue(String rawValue) { diff --git a/packages/stream_chat/lib/src/core/http/token_manager.dart b/packages/stream_chat/lib/src/core/http/token_manager.dart index e5af0ddc33..96b90bdcc0 100644 --- a/packages/stream_chat/lib/src/core/http/token_manager.dart +++ b/packages/stream_chat/lib/src/core/http/token_manager.dart @@ -12,9 +12,9 @@ class TokenManager { String? userId, Token? token, TokenProvider? tokenProvider, - }) : _userId = userId, - _token = token, - _provider = tokenProvider; + }) : _userId = userId, + _token = token, + _provider = tokenProvider; String? _type; Token? _token; diff --git a/packages/stream_chat/lib/src/core/models/action.g.dart b/packages/stream_chat/lib/src/core/models/action.g.dart index 2c9148d92b..ec604d553f 100644 --- a/packages/stream_chat/lib/src/core/models/action.g.dart +++ b/packages/stream_chat/lib/src/core/models/action.g.dart @@ -7,17 +7,17 @@ part of 'action.dart'; // ************************************************************************** Action _$ActionFromJson(Map json) => Action( - name: json['name'] as String, - style: json['style'] as String? ?? 'default', - text: json['text'] as String, - type: json['type'] as String, - value: json['value'] as String?, - ); + name: json['name'] as String, + style: json['style'] as String? ?? 'default', + text: json['text'] as String, + type: json['type'] as String, + value: json['value'] as String?, +); Map _$ActionToJson(Action instance) => { - 'name': instance.name, - 'style': instance.style, - 'text': instance.text, - 'type': instance.type, - 'value': instance.value, - }; + 'name': instance.name, + 'style': instance.style, + 'text': instance.text, + 'type': instance.type, + 'value': instance.value, +}; diff --git a/packages/stream_chat/lib/src/core/models/attachment.dart b/packages/stream_chat/lib/src/core/models/attachment.dart index a4e15241c8..486750cf45 100644 --- a/packages/stream_chat/lib/src/core/models/attachment.dart +++ b/packages/stream_chat/lib/src/core/models/attachment.dart @@ -39,49 +39,48 @@ class Attachment extends Equatable { Map extraData = const {}, this.file, this.uploadState = const UploadState.preparing(), - }) : id = id ?? const Uuid().v4(), - _type = switch (type) { - String() => AttachmentType(type), - _ => null, - }, - title = title ?? file?.name, - localUri = file?.path != null ? Uri.parse(file!.path!) : null, - // For backwards compatibility, - // set 'file_size', 'mime_type' in [extraData]. - extraData = { - ...extraData, - if (file?.size != null) 'file_size': file?.size, - if (file?.mediaType != null) 'mime_type': file?.mediaType?.mimeType, - }; + }) : id = id ?? const Uuid().v4(), + _type = switch (type) { + String() => AttachmentType(type), + _ => null, + }, + title = title ?? file?.name, + localUri = file?.path != null ? Uri.parse(file!.path!) : null, + // For backwards compatibility, + // set 'file_size', 'mime_type' in [extraData]. + extraData = { + ...extraData, + if (file?.size != null) 'file_size': file?.size, + if (file?.mediaType != null) 'mime_type': file?.mediaType?.mimeType, + }; /// Create a new instance from a json - factory Attachment.fromJson(Map json) => - _$AttachmentFromJson( - Serializer.moveToExtraDataFromRoot(json, topLevelFields), - ); + factory Attachment.fromJson(Map json) => _$AttachmentFromJson( + Serializer.moveToExtraDataFromRoot(json, topLevelFields), + ); /// Create a new instance from a db data - factory Attachment.fromData(Map json) => - _$AttachmentFromJson(Serializer.moveToExtraDataFromRoot( - json, - topLevelFields + dbSpecificTopLevelFields, - )); - - factory Attachment.fromOGAttachment(OGAttachmentResponse ogAttachment) => - Attachment( - // If the type is not specified, we default to urlPreview. - type: ogAttachment.type ?? AttachmentType.urlPreview, - title: ogAttachment.title, - titleLink: ogAttachment.titleLink, - text: ogAttachment.text, - imageUrl: ogAttachment.imageUrl, - thumbUrl: ogAttachment.thumbUrl, - authorName: ogAttachment.authorName, - authorLink: ogAttachment.authorLink, - assetUrl: ogAttachment.assetUrl, - ogScrapeUrl: ogAttachment.ogScrapeUrl, - uploadState: const UploadState.success(), - ); + factory Attachment.fromData(Map json) => _$AttachmentFromJson( + Serializer.moveToExtraDataFromRoot( + json, + topLevelFields + dbSpecificTopLevelFields, + ), + ); + + factory Attachment.fromOGAttachment(OGAttachmentResponse ogAttachment) => Attachment( + // If the type is not specified, we default to urlPreview. + type: ogAttachment.type ?? AttachmentType.urlPreview, + title: ogAttachment.title, + titleLink: ogAttachment.titleLink, + text: ogAttachment.text, + imageUrl: ogAttachment.imageUrl, + thumbUrl: ogAttachment.thumbUrl, + authorName: ogAttachment.authorName, + authorLink: ogAttachment.authorLink, + assetUrl: ogAttachment.assetUrl, + ogScrapeUrl: ogAttachment.ogScrapeUrl, + uploadState: const UploadState.success(), + ); ///The attachment type based on the URL resource. This can be: audio, ///image or video @@ -219,8 +218,7 @@ class Attachment extends Equatable { ..removeWhere((key, value) => dbSpecificTopLevelFields.contains(key)); /// Serialize to db data - Map toData() => - Serializer.moveFromExtraDataToRoot(_$AttachmentToJson(this)); + Map toData() => Serializer.moveFromExtraDataToRoot(_$AttachmentToJson(this)); Attachment copyWith({ String? id, @@ -247,33 +245,32 @@ class Attachment extends Equatable { AttachmentFile? file, UploadState? uploadState, Map? extraData, - }) => - Attachment( - id: id ?? this.id, - type: type ?? this.type, - titleLink: titleLink ?? this.titleLink, - title: title ?? this.title, - thumbUrl: thumbUrl ?? this.thumbUrl, - text: text ?? this.text, - pretext: pretext ?? this.pretext, - ogScrapeUrl: ogScrapeUrl ?? this.ogScrapeUrl, - imageUrl: imageUrl ?? this.imageUrl, - footerIcon: footerIcon ?? this.footerIcon, - footer: footer ?? this.footer, - fields: fields ?? this.fields, - fallback: fallback ?? this.fallback, - color: color ?? this.color, - authorName: authorName ?? this.authorName, - authorLink: authorLink ?? this.authorLink, - authorIcon: authorIcon ?? this.authorIcon, - assetUrl: assetUrl ?? this.assetUrl, - actions: actions ?? this.actions, - originalWidth: originalWidth ?? this.originalWidth, - originalHeight: originalHeight ?? this.originalHeight, - file: file ?? this.file, - uploadState: uploadState ?? this.uploadState, - extraData: extraData ?? this.extraData, - ); + }) => Attachment( + id: id ?? this.id, + type: type ?? this.type, + titleLink: titleLink ?? this.titleLink, + title: title ?? this.title, + thumbUrl: thumbUrl ?? this.thumbUrl, + text: text ?? this.text, + pretext: pretext ?? this.pretext, + ogScrapeUrl: ogScrapeUrl ?? this.ogScrapeUrl, + imageUrl: imageUrl ?? this.imageUrl, + footerIcon: footerIcon ?? this.footerIcon, + footer: footer ?? this.footer, + fields: fields ?? this.fields, + fallback: fallback ?? this.fallback, + color: color ?? this.color, + authorName: authorName ?? this.authorName, + authorLink: authorLink ?? this.authorLink, + authorIcon: authorIcon ?? this.authorIcon, + assetUrl: assetUrl ?? this.assetUrl, + actions: actions ?? this.actions, + originalWidth: originalWidth ?? this.originalWidth, + originalHeight: originalHeight ?? this.originalHeight, + file: file ?? this.file, + uploadState: uploadState ?? this.uploadState, + extraData: extraData ?? this.extraData, + ); Attachment merge(Attachment? other) { if (other == null) return this; @@ -306,31 +303,31 @@ class Attachment extends Equatable { @override List get props => [ - id, - type, - titleLink, - title, - thumbUrl, - text, - pretext, - ogScrapeUrl, - imageUrl, - footerIcon, - footer, - fields, - fallback, - color, - authorName, - authorLink, - authorIcon, - assetUrl, - actions, - originalWidth, - originalHeight, - file, - uploadState, - extraData, - ]; + id, + type, + titleLink, + title, + thumbUrl, + text, + pretext, + ogScrapeUrl, + imageUrl, + footerIcon, + footer, + fields, + fallback, + color, + authorName, + authorLink, + authorIcon, + assetUrl, + actions, + originalWidth, + originalHeight, + file, + uploadState, + extraData, + ]; } /// {@template attachmentType} diff --git a/packages/stream_chat/lib/src/core/models/attachment.g.dart b/packages/stream_chat/lib/src/core/models/attachment.g.dart index 8fcc4fe4c6..8dd8e75a8c 100644 --- a/packages/stream_chat/lib/src/core/models/attachment.g.dart +++ b/packages/stream_chat/lib/src/core/models/attachment.g.dart @@ -7,64 +7,58 @@ part of 'attachment.dart'; // ************************************************************************** Attachment _$AttachmentFromJson(Map json) => Attachment( - id: json['id'] as String?, - type: AttachmentType.fromJson(json['type'] as String?), - titleLink: json['title_link'] as String?, - title: json['title'] as String?, - thumbUrl: json['thumb_url'] as String?, - text: json['text'] as String?, - pretext: json['pretext'] as String?, - ogScrapeUrl: json['og_scrape_url'] as String?, - imageUrl: json['image_url'] as String?, - footerIcon: json['footer_icon'] as String?, - footer: json['footer'] as String?, - fields: json['fields'], - fallback: json['fallback'] as String?, - color: json['color'] as String?, - authorName: json['author_name'] as String?, - authorLink: json['author_link'] as String?, - authorIcon: json['author_icon'] as String?, - assetUrl: json['asset_url'] as String?, - actions: (json['actions'] as List?) - ?.map((e) => Action.fromJson(e as Map)) - .toList() ?? - const [], - originalWidth: (json['original_width'] as num?)?.toInt(), - originalHeight: (json['original_height'] as num?)?.toInt(), - extraData: json['extra_data'] as Map? ?? const {}, - file: json['file'] == null - ? null - : AttachmentFile.fromJson(json['file'] as Map), - uploadState: json['upload_state'] == null - ? const UploadState.success() - : UploadState.fromJson(json['upload_state'] as Map), - ); + id: json['id'] as String?, + type: AttachmentType.fromJson(json['type'] as String?), + titleLink: json['title_link'] as String?, + title: json['title'] as String?, + thumbUrl: json['thumb_url'] as String?, + text: json['text'] as String?, + pretext: json['pretext'] as String?, + ogScrapeUrl: json['og_scrape_url'] as String?, + imageUrl: json['image_url'] as String?, + footerIcon: json['footer_icon'] as String?, + footer: json['footer'] as String?, + fields: json['fields'], + fallback: json['fallback'] as String?, + color: json['color'] as String?, + authorName: json['author_name'] as String?, + authorLink: json['author_link'] as String?, + authorIcon: json['author_icon'] as String?, + assetUrl: json['asset_url'] as String?, + actions: + (json['actions'] as List?)?.map((e) => Action.fromJson(e as Map)).toList() ?? const [], + originalWidth: (json['original_width'] as num?)?.toInt(), + originalHeight: (json['original_height'] as num?)?.toInt(), + extraData: json['extra_data'] as Map? ?? const {}, + file: json['file'] == null ? null : AttachmentFile.fromJson(json['file'] as Map), + uploadState: json['upload_state'] == null + ? const UploadState.success() + : UploadState.fromJson(json['upload_state'] as Map), +); -Map _$AttachmentToJson(Attachment instance) => - { - if (AttachmentType.toJson(instance.type) case final value?) 'type': value, - if (instance.titleLink case final value?) 'title_link': value, - if (instance.title case final value?) 'title': value, - if (instance.thumbUrl case final value?) 'thumb_url': value, - if (instance.text case final value?) 'text': value, - if (instance.pretext case final value?) 'pretext': value, - if (instance.ogScrapeUrl case final value?) 'og_scrape_url': value, - if (instance.imageUrl case final value?) 'image_url': value, - if (instance.footerIcon case final value?) 'footer_icon': value, - if (instance.footer case final value?) 'footer': value, - if (instance.fields case final value?) 'fields': value, - if (instance.fallback case final value?) 'fallback': value, - if (instance.color case final value?) 'color': value, - if (instance.authorName case final value?) 'author_name': value, - if (instance.authorLink case final value?) 'author_link': value, - if (instance.authorIcon case final value?) 'author_icon': value, - if (instance.assetUrl case final value?) 'asset_url': value, - if (instance.actions?.map((e) => e.toJson()).toList() case final value?) - 'actions': value, - if (instance.originalWidth case final value?) 'original_width': value, - if (instance.originalHeight case final value?) 'original_height': value, - if (instance.file?.toJson() case final value?) 'file': value, - 'upload_state': instance.uploadState.toJson(), - 'extra_data': instance.extraData, - 'id': instance.id, - }; +Map _$AttachmentToJson(Attachment instance) => { + if (AttachmentType.toJson(instance.type) case final value?) 'type': value, + if (instance.titleLink case final value?) 'title_link': value, + if (instance.title case final value?) 'title': value, + if (instance.thumbUrl case final value?) 'thumb_url': value, + if (instance.text case final value?) 'text': value, + if (instance.pretext case final value?) 'pretext': value, + if (instance.ogScrapeUrl case final value?) 'og_scrape_url': value, + if (instance.imageUrl case final value?) 'image_url': value, + if (instance.footerIcon case final value?) 'footer_icon': value, + if (instance.footer case final value?) 'footer': value, + if (instance.fields case final value?) 'fields': value, + if (instance.fallback case final value?) 'fallback': value, + if (instance.color case final value?) 'color': value, + if (instance.authorName case final value?) 'author_name': value, + if (instance.authorLink case final value?) 'author_link': value, + if (instance.authorIcon case final value?) 'author_icon': value, + if (instance.assetUrl case final value?) 'asset_url': value, + if (instance.actions?.map((e) => e.toJson()).toList() case final value?) 'actions': value, + if (instance.originalWidth case final value?) 'original_width': value, + if (instance.originalHeight case final value?) 'original_height': value, + if (instance.file?.toJson() case final value?) 'file': value, + 'upload_state': instance.uploadState.toJson(), + 'extra_data': instance.extraData, + 'id': instance.id, +}; diff --git a/packages/stream_chat/lib/src/core/models/attachment_file.dart b/packages/stream_chat/lib/src/core/models/attachment_file.dart index b5bb007633..9d476090fa 100644 --- a/packages/stream_chat/lib/src/core/models/attachment_file.dart +++ b/packages/stream_chat/lib/src/core/models/attachment_file.dart @@ -19,23 +19,22 @@ class AttachmentFile { this.path, String? name, this.bytes, - }) : assert( - path != null || bytes != null, - 'Either path or bytes should be != null', - ), - assert( - !CurrentPlatform.isWeb || bytes != null, - 'File by path is not supported in web, Please provide bytes', - ), - assert( - name == null || name.isEmpty || name.contains('.'), - 'Invalid file name, should also contain file extension', - ), - _name = name; + }) : assert( + path != null || bytes != null, + 'Either path or bytes should be != null', + ), + assert( + !CurrentPlatform.isWeb || bytes != null, + 'File by path is not supported in web, Please provide bytes', + ), + assert( + name == null || name.isEmpty || name.contains('.'), + 'Invalid file name, should also contain file extension', + ), + _name = name; /// Create a new instance from a json - factory AttachmentFile.fromJson(Map json) => - _$AttachmentFileFromJson(json); + factory AttachmentFile.fromJson(Map json) => _$AttachmentFileFromJson(json); /// The absolute path for a cached copy of this file. It can be used to /// create a file instance with a descriptor for the given path. @@ -73,15 +72,15 @@ class AttachmentFile { Future toMultipartFile() async { return switch (CurrentPlatform.type) { PlatformType.web => MultipartFile.fromBytes( - bytes!, - filename: name, - contentType: mediaType, - ), + bytes!, + filename: name, + contentType: mediaType, + ), _ => await MultipartFile.fromFile( - path!, - filename: name, - contentType: mediaType, - ), + path!, + filename: name, + contentType: mediaType, + ), }; } @@ -124,8 +123,7 @@ sealed class UploadState with _$UploadState { const factory UploadState.failed({required String error}) = Failed; /// Creates a new instance from a json - factory UploadState.fromJson(Map json) => - _$UploadStateFromJson(json); + factory UploadState.fromJson(Map json) => _$UploadStateFromJson(json); /// Returns true if state is [Preparing] bool get isPreparing => this is Preparing; diff --git a/packages/stream_chat/lib/src/core/models/attachment_file.g.dart b/packages/stream_chat/lib/src/core/models/attachment_file.g.dart index 256713cae0..7ebe7ea111 100644 --- a/packages/stream_chat/lib/src/core/models/attachment_file.g.dart +++ b/packages/stream_chat/lib/src/core/models/attachment_file.g.dart @@ -6,55 +6,52 @@ part of 'attachment_file.dart'; // JsonSerializableGenerator // ************************************************************************** -AttachmentFile _$AttachmentFileFromJson(Map json) => - AttachmentFile( - size: (json['size'] as num?)?.toInt(), - path: json['path'] as String?, - name: json['name'] as String?, - ); - -Map _$AttachmentFileToJson(AttachmentFile instance) => - { - 'path': instance.path, - 'name': instance.name, - 'size': instance.size, - }; +AttachmentFile _$AttachmentFileFromJson(Map json) => AttachmentFile( + size: (json['size'] as num?)?.toInt(), + path: json['path'] as String?, + name: json['name'] as String?, +); + +Map _$AttachmentFileToJson(AttachmentFile instance) => { + 'path': instance.path, + 'name': instance.name, + 'size': instance.size, +}; Preparing _$PreparingFromJson(Map json) => Preparing( - $type: json['runtimeType'] as String?, - ); + $type: json['runtimeType'] as String?, +); Map _$PreparingToJson(Preparing instance) => { - 'runtimeType': instance.$type, - }; + 'runtimeType': instance.$type, +}; InProgress _$InProgressFromJson(Map json) => InProgress( - uploaded: (json['uploaded'] as num).toInt(), - total: (json['total'] as num).toInt(), - $type: json['runtimeType'] as String?, - ); - -Map _$InProgressToJson(InProgress instance) => - { - 'uploaded': instance.uploaded, - 'total': instance.total, - 'runtimeType': instance.$type, - }; + uploaded: (json['uploaded'] as num).toInt(), + total: (json['total'] as num).toInt(), + $type: json['runtimeType'] as String?, +); + +Map _$InProgressToJson(InProgress instance) => { + 'uploaded': instance.uploaded, + 'total': instance.total, + 'runtimeType': instance.$type, +}; Success _$SuccessFromJson(Map json) => Success( - $type: json['runtimeType'] as String?, - ); + $type: json['runtimeType'] as String?, +); Map _$SuccessToJson(Success instance) => { - 'runtimeType': instance.$type, - }; + 'runtimeType': instance.$type, +}; Failed _$FailedFromJson(Map json) => Failed( - error: json['error'] as String, - $type: json['runtimeType'] as String?, - ); + error: json['error'] as String, + $type: json['runtimeType'] as String?, +); Map _$FailedToJson(Failed instance) => { - 'error': instance.error, - 'runtimeType': instance.$type, - }; + 'error': instance.error, + 'runtimeType': instance.$type, +}; diff --git a/packages/stream_chat/lib/src/core/models/attachment_giphy_info.dart b/packages/stream_chat/lib/src/core/models/attachment_giphy_info.dart index bc5d59a326..26ffbb9bce 100644 --- a/packages/stream_chat/lib/src/core/models/attachment_giphy_info.dart +++ b/packages/stream_chat/lib/src/core/models/attachment_giphy_info.dart @@ -17,7 +17,8 @@ enum GiphyInfoType { /// Lower quality with a fixed height with width adjusted according to the /// aspect ratio and played at a lower frame rate. Significantly lower size, /// but visually less appealing. - fixedHeightDownsampled('fixed_height_downsampled'); + fixedHeightDownsampled('fixed_height_downsampled') + ; /// {@macro giphy_info_type} const GiphyInfoType(this.value); diff --git a/packages/stream_chat/lib/src/core/models/banned_user.dart b/packages/stream_chat/lib/src/core/models/banned_user.dart index 4f25ac75fe..7717593b1a 100644 --- a/packages/stream_chat/lib/src/core/models/banned_user.dart +++ b/packages/stream_chat/lib/src/core/models/banned_user.dart @@ -21,8 +21,7 @@ class BannedUser extends Equatable implements ComparableFieldProvider { }); /// Create a new instance from a json - factory BannedUser.fromJson(Map json) => - _$BannedUserFromJson(json); + factory BannedUser.fromJson(Map json) => _$BannedUserFromJson(json); /// Banned user. final User user; @@ -57,27 +56,26 @@ class BannedUser extends Equatable implements ComparableFieldProvider { DateTime? expires, bool? shadow, String? reason, - }) => - BannedUser( - user: user ?? this.user, - bannedBy: bannedBy ?? this.bannedBy, - channel: channel ?? this.channel, - createdAt: createdAt ?? this.createdAt, - expires: expires ?? this.expires, - shadow: shadow ?? this.shadow, - reason: reason ?? this.reason, - ); + }) => BannedUser( + user: user ?? this.user, + bannedBy: bannedBy ?? this.bannedBy, + channel: channel ?? this.channel, + createdAt: createdAt ?? this.createdAt, + expires: expires ?? this.expires, + shadow: shadow ?? this.shadow, + reason: reason ?? this.reason, + ); @override List get props => [ - user, - bannedBy, - channel, - createdAt, - expires, - shadow, - reason, - ]; + user, + bannedBy, + channel, + createdAt, + expires, + shadow, + reason, + ]; @override ComparableField? getComparableField(String sortKey) { diff --git a/packages/stream_chat/lib/src/core/models/banned_user.g.dart b/packages/stream_chat/lib/src/core/models/banned_user.g.dart index 1f2a335b34..024a8fd912 100644 --- a/packages/stream_chat/lib/src/core/models/banned_user.g.dart +++ b/packages/stream_chat/lib/src/core/models/banned_user.g.dart @@ -7,30 +7,21 @@ part of 'banned_user.dart'; // ************************************************************************** BannedUser _$BannedUserFromJson(Map json) => BannedUser( - user: User.fromJson(json['user'] as Map), - bannedBy: json['banned_by'] == null - ? null - : User.fromJson(json['banned_by'] as Map), - channel: json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map), - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - expires: json['expires'] == null - ? null - : DateTime.parse(json['expires'] as String), - shadow: json['shadow'] as bool? ?? false, - reason: json['reason'] as String?, - ); + user: User.fromJson(json['user'] as Map), + bannedBy: json['banned_by'] == null ? null : User.fromJson(json['banned_by'] as Map), + channel: json['channel'] == null ? null : ChannelModel.fromJson(json['channel'] as Map), + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + expires: json['expires'] == null ? null : DateTime.parse(json['expires'] as String), + shadow: json['shadow'] as bool? ?? false, + reason: json['reason'] as String?, +); -Map _$BannedUserToJson(BannedUser instance) => - { - 'user': instance.user.toJson(), - 'banned_by': instance.bannedBy?.toJson(), - 'channel': instance.channel?.toJson(), - 'created_at': instance.createdAt?.toIso8601String(), - 'expires': instance.expires?.toIso8601String(), - 'shadow': instance.shadow, - 'reason': instance.reason, - }; +Map _$BannedUserToJson(BannedUser instance) => { + 'user': instance.user.toJson(), + 'banned_by': instance.bannedBy?.toJson(), + 'channel': instance.channel?.toJson(), + 'created_at': instance.createdAt?.toIso8601String(), + 'expires': instance.expires?.toIso8601String(), + 'shadow': instance.shadow, + 'reason': instance.reason, +}; diff --git a/packages/stream_chat/lib/src/core/models/channel_config.dart b/packages/stream_chat/lib/src/core/models/channel_config.dart index dd54e0d40e..32e4760d6c 100644 --- a/packages/stream_chat/lib/src/core/models/channel_config.dart +++ b/packages/stream_chat/lib/src/core/models/channel_config.dart @@ -29,12 +29,11 @@ class ChannelConfig { this.markMessagesPending = false, this.deliveryEvents = false, this.sharedLocations = false, - }) : createdAt = createdAt ?? DateTime.now(), - updatedAt = updatedAt ?? DateTime.now(); + }) : createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json - factory ChannelConfig.fromJson(Map json) => - _$ChannelConfigFromJson(json); + factory ChannelConfig.fromJson(Map json) => _$ChannelConfigFromJson(json); /// Moderation configuration final String automod; diff --git a/packages/stream_chat/lib/src/core/models/channel_config.g.dart b/packages/stream_chat/lib/src/core/models/channel_config.g.dart index 2115711141..9bae9b1ba1 100644 --- a/packages/stream_chat/lib/src/core/models/channel_config.g.dart +++ b/packages/stream_chat/lib/src/core/models/channel_config.g.dart @@ -6,61 +6,52 @@ part of 'channel_config.dart'; // JsonSerializableGenerator // ************************************************************************** -ChannelConfig _$ChannelConfigFromJson(Map json) => - ChannelConfig( - automod: json['automod'] as String? ?? 'flag', - commands: (json['commands'] as List?) - ?.map((e) => Command.fromJson(e as Map)) - .toList() ?? - const [], - connectEvents: json['connect_events'] as bool? ?? false, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - maxMessageLength: (json['max_message_length'] as num?)?.toInt() ?? 0, - messageRetention: json['message_retention'] as String? ?? '', - mutes: json['mutes'] as bool? ?? false, - reactions: json['reactions'] as bool? ?? false, - readEvents: json['read_events'] as bool? ?? false, - replies: json['replies'] as bool? ?? false, - search: json['search'] as bool? ?? false, - polls: json['polls'] as bool? ?? false, - typingEvents: json['typing_events'] as bool? ?? false, - uploads: json['uploads'] as bool? ?? false, - urlEnrichment: json['url_enrichment'] as bool? ?? false, - skipLastMsgUpdateForSystemMsgs: - json['skip_last_msg_update_for_system_msgs'] as bool? ?? false, - userMessageReminders: json['user_message_reminders'] as bool? ?? false, - markMessagesPending: json['mark_messages_pending'] as bool? ?? false, - deliveryEvents: json['delivery_events'] as bool? ?? false, - sharedLocations: json['shared_locations'] as bool? ?? false, - ); +ChannelConfig _$ChannelConfigFromJson(Map json) => ChannelConfig( + automod: json['automod'] as String? ?? 'flag', + commands: + (json['commands'] as List?)?.map((e) => Command.fromJson(e as Map)).toList() ?? + const [], + connectEvents: json['connect_events'] as bool? ?? false, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + maxMessageLength: (json['max_message_length'] as num?)?.toInt() ?? 0, + messageRetention: json['message_retention'] as String? ?? '', + mutes: json['mutes'] as bool? ?? false, + reactions: json['reactions'] as bool? ?? false, + readEvents: json['read_events'] as bool? ?? false, + replies: json['replies'] as bool? ?? false, + search: json['search'] as bool? ?? false, + polls: json['polls'] as bool? ?? false, + typingEvents: json['typing_events'] as bool? ?? false, + uploads: json['uploads'] as bool? ?? false, + urlEnrichment: json['url_enrichment'] as bool? ?? false, + skipLastMsgUpdateForSystemMsgs: json['skip_last_msg_update_for_system_msgs'] as bool? ?? false, + userMessageReminders: json['user_message_reminders'] as bool? ?? false, + markMessagesPending: json['mark_messages_pending'] as bool? ?? false, + deliveryEvents: json['delivery_events'] as bool? ?? false, + sharedLocations: json['shared_locations'] as bool? ?? false, +); -Map _$ChannelConfigToJson(ChannelConfig instance) => - { - 'automod': instance.automod, - 'commands': instance.commands.map((e) => e.toJson()).toList(), - 'connect_events': instance.connectEvents, - 'created_at': instance.createdAt.toIso8601String(), - 'updated_at': instance.updatedAt.toIso8601String(), - 'max_message_length': instance.maxMessageLength, - 'message_retention': instance.messageRetention, - 'mutes': instance.mutes, - 'reactions': instance.reactions, - 'read_events': instance.readEvents, - 'replies': instance.replies, - 'search': instance.search, - 'polls': instance.polls, - 'typing_events': instance.typingEvents, - 'uploads': instance.uploads, - 'url_enrichment': instance.urlEnrichment, - 'skip_last_msg_update_for_system_msgs': - instance.skipLastMsgUpdateForSystemMsgs, - 'user_message_reminders': instance.userMessageReminders, - 'mark_messages_pending': instance.markMessagesPending, - 'delivery_events': instance.deliveryEvents, - 'shared_locations': instance.sharedLocations, - }; +Map _$ChannelConfigToJson(ChannelConfig instance) => { + 'automod': instance.automod, + 'commands': instance.commands.map((e) => e.toJson()).toList(), + 'connect_events': instance.connectEvents, + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'max_message_length': instance.maxMessageLength, + 'message_retention': instance.messageRetention, + 'mutes': instance.mutes, + 'reactions': instance.reactions, + 'read_events': instance.readEvents, + 'replies': instance.replies, + 'search': instance.search, + 'polls': instance.polls, + 'typing_events': instance.typingEvents, + 'uploads': instance.uploads, + 'url_enrichment': instance.urlEnrichment, + 'skip_last_msg_update_for_system_msgs': instance.skipLastMsgUpdateForSystemMsgs, + 'user_message_reminders': instance.userMessageReminders, + 'mark_messages_pending': instance.markMessagesPending, + 'delivery_events': instance.deliveryEvents, + 'shared_locations': instance.sharedLocations, +}; diff --git a/packages/stream_chat/lib/src/core/models/channel_model.dart b/packages/stream_chat/lib/src/core/models/channel_model.dart index 6cc396b744..846b91958e 100644 --- a/packages/stream_chat/lib/src/core/models/channel_model.dart +++ b/packages/stream_chat/lib/src/core/models/channel_model.dart @@ -33,33 +33,31 @@ class ChannelModel { DateTime? truncatedAt, this.messageCount, this.filterTags, - }) : assert( - (cid != null && cid.contains(':')) || (id != null && type != null), - 'provide either a cid or an id and type', - ), - id = id ?? cid!.split(':')[1], - type = type ?? cid!.split(':')[0], - cid = cid ?? '$type:$id', - config = config ?? ChannelConfig(), - createdAt = createdAt ?? DateTime.now(), - updatedAt = updatedAt ?? DateTime.now(), - ownCapabilities = ownCapabilities?.map(ChannelCapability.new).toList(), - - // For backwards compatibility, set 'disabled', 'hidden' - // and 'truncated_at' in [extraData]. - extraData = { - ...extraData, - if (disabled != null) 'disabled': disabled, - if (hidden != null) 'hidden': hidden, - if (truncatedAt != null) - 'truncated_at': truncatedAt.toIso8601String(), - }; + }) : assert( + (cid != null && cid.contains(':')) || (id != null && type != null), + 'provide either a cid or an id and type', + ), + id = id ?? cid!.split(':')[1], + type = type ?? cid!.split(':')[0], + cid = cid ?? '$type:$id', + config = config ?? ChannelConfig(), + createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(), + ownCapabilities = ownCapabilities?.map(ChannelCapability.new).toList(), + + // For backwards compatibility, set 'disabled', 'hidden' + // and 'truncated_at' in [extraData]. + extraData = { + ...extraData, + if (disabled != null) 'disabled': disabled, + if (hidden != null) 'hidden': hidden, + if (truncatedAt != null) 'truncated_at': truncatedAt.toIso8601String(), + }; /// Create a new instance from a json - factory ChannelModel.fromJson(Map json) => - _$ChannelModelFromJson( - Serializer.moveToExtraDataFromRoot(json, topLevelFields), - ); + factory ChannelModel.fromJson(Map json) => _$ChannelModelFromJson( + Serializer.moveToExtraDataFromRoot(json, topLevelFields), + ); /// The id of this channel final String id; @@ -190,8 +188,8 @@ class ChannelModel { /// Serialize to json Map toJson() => Serializer.moveFromExtraDataToRoot( - _$ChannelModelToJson(this), - ); + _$ChannelModelToJson(this), + ); /// Creates a copy of [ChannelModel] with specified attributes overridden. ChannelModel copyWith({ @@ -216,35 +214,35 @@ class ChannelModel { DateTime? truncatedAt, int? messageCount, List? filterTags, - }) => - ChannelModel( - id: id ?? this.id, - type: type ?? this.type, - cid: cid ?? this.cid, - ownCapabilities: ownCapabilities ?? this.ownCapabilities, - config: config ?? this.config, - createdBy: createdBy ?? this.createdBy, - frozen: frozen ?? this.frozen, - lastMessageAt: lastMessageAt ?? this.lastMessageAt, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - deletedAt: deletedAt ?? this.deletedAt, - memberCount: memberCount ?? this.memberCount, - members: members ?? this.members, - extraData: extraData ?? this.extraData, - team: team ?? this.team, - cooldown: cooldown ?? this.cooldown, - disabled: disabled ?? extraData?['disabled'] as bool? ?? this.disabled, - hidden: hidden ?? extraData?['hidden'] as bool? ?? this.hidden, - truncatedAt: truncatedAt ?? - (extraData?['truncated_at'] == null - ? null - // ignore: cast_nullable_to_non_nullable - : DateTime.parse(extraData?['truncated_at'] as String)) ?? - this.truncatedAt, - messageCount: messageCount ?? this.messageCount, - filterTags: filterTags ?? this.filterTags, - ); + }) => ChannelModel( + id: id ?? this.id, + type: type ?? this.type, + cid: cid ?? this.cid, + ownCapabilities: ownCapabilities ?? this.ownCapabilities, + config: config ?? this.config, + createdBy: createdBy ?? this.createdBy, + frozen: frozen ?? this.frozen, + lastMessageAt: lastMessageAt ?? this.lastMessageAt, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + memberCount: memberCount ?? this.memberCount, + members: members ?? this.members, + extraData: extraData ?? this.extraData, + team: team ?? this.team, + cooldown: cooldown ?? this.cooldown, + disabled: disabled ?? extraData?['disabled'] as bool? ?? this.disabled, + hidden: hidden ?? extraData?['hidden'] as bool? ?? this.hidden, + truncatedAt: + truncatedAt ?? + (extraData?['truncated_at'] == null + ? null + // ignore: cast_nullable_to_non_nullable + : DateTime.parse(extraData?['truncated_at'] as String)) ?? + this.truncatedAt, + messageCount: messageCount ?? this.messageCount, + filterTags: filterTags ?? this.filterTags, + ); /// Returns a new [ChannelModel] that is a combination of this channelModel /// and the given [other] channelModel. diff --git a/packages/stream_chat/lib/src/core/models/channel_model.g.dart b/packages/stream_chat/lib/src/core/models/channel_model.g.dart index cc64ccc034..d2c1ce62eb 100644 --- a/packages/stream_chat/lib/src/core/models/channel_model.g.dart +++ b/packages/stream_chat/lib/src/core/models/channel_model.g.dart @@ -7,49 +7,30 @@ part of 'channel_model.dart'; // ************************************************************************** ChannelModel _$ChannelModelFromJson(Map json) => ChannelModel( - id: json['id'] as String?, - type: json['type'] as String?, - cid: json['cid'] as String?, - ownCapabilities: (json['own_capabilities'] as List?) - ?.map((e) => e as String) - .toList(), - config: json['config'] == null - ? null - : ChannelConfig.fromJson(json['config'] as Map), - createdBy: json['created_by'] == null - ? null - : User.fromJson(json['created_by'] as Map), - frozen: json['frozen'] as bool? ?? false, - lastMessageAt: json['last_message_at'] == null - ? null - : DateTime.parse(json['last_message_at'] as String), - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - deletedAt: json['deleted_at'] == null - ? null - : DateTime.parse(json['deleted_at'] as String), - memberCount: (json['member_count'] as num?)?.toInt() ?? 0, - members: (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList(), - extraData: json['extra_data'] as Map? ?? const {}, - team: json['team'] as String?, - cooldown: (json['cooldown'] as num?)?.toInt() ?? 0, - messageCount: (json['message_count'] as num?)?.toInt(), - filterTags: (json['filter_tags'] as List?) - ?.map((e) => e as String) - .toList(), - ); + id: json['id'] as String?, + type: json['type'] as String?, + cid: json['cid'] as String?, + ownCapabilities: (json['own_capabilities'] as List?)?.map((e) => e as String).toList(), + config: json['config'] == null ? null : ChannelConfig.fromJson(json['config'] as Map), + createdBy: json['created_by'] == null ? null : User.fromJson(json['created_by'] as Map), + frozen: json['frozen'] as bool? ?? false, + lastMessageAt: json['last_message_at'] == null ? null : DateTime.parse(json['last_message_at'] as String), + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + deletedAt: json['deleted_at'] == null ? null : DateTime.parse(json['deleted_at'] as String), + memberCount: (json['member_count'] as num?)?.toInt() ?? 0, + members: (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList(), + extraData: json['extra_data'] as Map? ?? const {}, + team: json['team'] as String?, + cooldown: (json['cooldown'] as num?)?.toInt() ?? 0, + messageCount: (json['message_count'] as num?)?.toInt(), + filterTags: (json['filter_tags'] as List?)?.map((e) => e as String).toList(), +); -Map _$ChannelModelToJson(ChannelModel instance) => - { - 'id': instance.id, - 'type': instance.type, - 'frozen': instance.frozen, - 'cooldown': instance.cooldown, - 'extra_data': instance.extraData, - }; +Map _$ChannelModelToJson(ChannelModel instance) => { + 'id': instance.id, + 'type': instance.type, + 'frozen': instance.frozen, + 'cooldown': instance.cooldown, + 'extra_data': instance.extraData, +}; diff --git a/packages/stream_chat/lib/src/core/models/channel_mute.dart b/packages/stream_chat/lib/src/core/models/channel_mute.dart index c0d29a57c0..a393ae14da 100644 --- a/packages/stream_chat/lib/src/core/models/channel_mute.dart +++ b/packages/stream_chat/lib/src/core/models/channel_mute.dart @@ -17,8 +17,7 @@ class ChannelMute { }); /// Create a new instance from a json - factory ChannelMute.fromJson(Map json) => - _$ChannelMuteFromJson(json); + factory ChannelMute.fromJson(Map json) => _$ChannelMuteFromJson(json); /// The user that performed the muting action final User user; diff --git a/packages/stream_chat/lib/src/core/models/channel_mute.g.dart b/packages/stream_chat/lib/src/core/models/channel_mute.g.dart index f0b973fcbf..4b3576c0ae 100644 --- a/packages/stream_chat/lib/src/core/models/channel_mute.g.dart +++ b/packages/stream_chat/lib/src/core/models/channel_mute.g.dart @@ -7,20 +7,17 @@ part of 'channel_mute.dart'; // ************************************************************************** ChannelMute _$ChannelMuteFromJson(Map json) => ChannelMute( - user: User.fromJson(json['user'] as Map), - channel: ChannelModel.fromJson(json['channel'] as Map), - createdAt: DateTime.parse(json['created_at'] as String), - updatedAt: DateTime.parse(json['updated_at'] as String), - expires: json['expires'] == null - ? null - : DateTime.parse(json['expires'] as String), - ); + user: User.fromJson(json['user'] as Map), + channel: ChannelModel.fromJson(json['channel'] as Map), + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + expires: json['expires'] == null ? null : DateTime.parse(json['expires'] as String), +); -Map _$ChannelMuteToJson(ChannelMute instance) => - { - 'user': instance.user.toJson(), - 'channel': instance.channel.toJson(), - 'created_at': instance.createdAt.toIso8601String(), - 'updated_at': instance.updatedAt.toIso8601String(), - 'expires': instance.expires?.toIso8601String(), - }; +Map _$ChannelMuteToJson(ChannelMute instance) => { + 'user': instance.user.toJson(), + 'channel': instance.channel.toJson(), + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'expires': instance.expires?.toIso8601String(), +}; diff --git a/packages/stream_chat/lib/src/core/models/channel_state.dart b/packages/stream_chat/lib/src/core/models/channel_state.dart index 830170646b..718e4b74f5 100644 --- a/packages/stream_chat/lib/src/core/models/channel_state.dart +++ b/packages/stream_chat/lib/src/core/models/channel_state.dart @@ -92,8 +92,7 @@ class ChannelState implements ComparableFieldProvider { final List? activeLiveLocations; /// Create a new instance from a json - static ChannelState fromJson(Map json) => - _$ChannelStateFromJson(json); + static ChannelState fromJson(Map json) => _$ChannelStateFromJson(json); /// Serialize to json Map toJson() => _$ChannelStateToJson(this); @@ -112,21 +111,20 @@ class ChannelState implements ComparableFieldProvider { List? pendingMessages, ChannelPushPreference? pushPreferences, List? activeLiveLocations, - }) => - ChannelState( - channel: channel ?? this.channel, - messages: messages ?? this.messages, - members: members ?? this.members, - pinnedMessages: pinnedMessages ?? this.pinnedMessages, - watcherCount: watcherCount ?? this.watcherCount, - watchers: watchers ?? this.watchers, - read: read ?? this.read, - membership: membership ?? this.membership, - draft: draft == _nullConst ? this.draft : draft as Draft?, - pendingMessages: pendingMessages ?? this.pendingMessages, - pushPreferences: pushPreferences ?? this.pushPreferences, - activeLiveLocations: activeLiveLocations ?? this.activeLiveLocations, - ); + }) => ChannelState( + channel: channel ?? this.channel, + messages: messages ?? this.messages, + members: members ?? this.members, + pinnedMessages: pinnedMessages ?? this.pinnedMessages, + watcherCount: watcherCount ?? this.watcherCount, + watchers: watchers ?? this.watchers, + read: read ?? this.read, + membership: membership ?? this.membership, + draft: draft == _nullConst ? this.draft : draft as Draft?, + pendingMessages: pendingMessages ?? this.pendingMessages, + pushPreferences: pushPreferences ?? this.pushPreferences, + activeLiveLocations: activeLiveLocations ?? this.activeLiveLocations, + ); @override ComparableField? getComparableField(String sortKey) { diff --git a/packages/stream_chat/lib/src/core/models/channel_state.g.dart b/packages/stream_chat/lib/src/core/models/channel_state.g.dart index cb73c3a42e..a3c6c2692d 100644 --- a/packages/stream_chat/lib/src/core/models/channel_state.g.dart +++ b/packages/stream_chat/lib/src/core/models/channel_state.g.dart @@ -7,60 +7,39 @@ part of 'channel_state.dart'; // ************************************************************************** ChannelState _$ChannelStateFromJson(Map json) => ChannelState( - channel: json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map), - messages: (json['messages'] as List?) - ?.map((e) => Message.fromJson(e as Map)) - .toList(), - members: (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList(), - pinnedMessages: (json['pinned_messages'] as List?) - ?.map((e) => Message.fromJson(e as Map)) - .toList(), - watcherCount: (json['watcher_count'] as num?)?.toInt(), - watchers: (json['watchers'] as List?) - ?.map((e) => User.fromJson(e as Map)) - .toList(), - read: (json['read'] as List?) - ?.map((e) => Read.fromJson(e as Map)) - .toList(), - membership: json['membership'] == null - ? null - : Member.fromJson(json['membership'] as Map), - draft: json['draft'] == null - ? null - : Draft.fromJson(json['draft'] as Map), - pendingMessages: - (ChannelState._pendingMessagesReadValue(json, 'pending_messages') - as List?) - ?.map((e) => Message.fromJson(e as Map)) - .toList(), - pushPreferences: json['push_preferences'] == null - ? null - : ChannelPushPreference.fromJson( - json['push_preferences'] as Map), - activeLiveLocations: (json['active_live_locations'] as List?) - ?.map((e) => Location.fromJson(e as Map)) - .toList(), - ); + channel: json['channel'] == null ? null : ChannelModel.fromJson(json['channel'] as Map), + messages: (json['messages'] as List?)?.map((e) => Message.fromJson(e as Map)).toList(), + members: (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList(), + pinnedMessages: (json['pinned_messages'] as List?) + ?.map((e) => Message.fromJson(e as Map)) + .toList(), + watcherCount: (json['watcher_count'] as num?)?.toInt(), + watchers: (json['watchers'] as List?)?.map((e) => User.fromJson(e as Map)).toList(), + read: (json['read'] as List?)?.map((e) => Read.fromJson(e as Map)).toList(), + membership: json['membership'] == null ? null : Member.fromJson(json['membership'] as Map), + draft: json['draft'] == null ? null : Draft.fromJson(json['draft'] as Map), + pendingMessages: (ChannelState._pendingMessagesReadValue(json, 'pending_messages') as List?) + ?.map((e) => Message.fromJson(e as Map)) + .toList(), + pushPreferences: json['push_preferences'] == null + ? null + : ChannelPushPreference.fromJson(json['push_preferences'] as Map), + activeLiveLocations: (json['active_live_locations'] as List?) + ?.map((e) => Location.fromJson(e as Map)) + .toList(), +); -Map _$ChannelStateToJson(ChannelState instance) => - { - 'channel': instance.channel?.toJson(), - 'messages': instance.messages?.map((e) => e.toJson()).toList(), - 'members': instance.members?.map((e) => e.toJson()).toList(), - 'pinned_messages': - instance.pinnedMessages?.map((e) => e.toJson()).toList(), - 'watcher_count': instance.watcherCount, - 'watchers': instance.watchers?.map((e) => e.toJson()).toList(), - 'read': instance.read?.map((e) => e.toJson()).toList(), - 'membership': instance.membership?.toJson(), - 'draft': instance.draft?.toJson(), - 'pending_messages': - instance.pendingMessages?.map((e) => e.toJson()).toList(), - 'push_preferences': instance.pushPreferences?.toJson(), - 'active_live_locations': - instance.activeLiveLocations?.map((e) => e.toJson()).toList(), - }; +Map _$ChannelStateToJson(ChannelState instance) => { + 'channel': instance.channel?.toJson(), + 'messages': instance.messages?.map((e) => e.toJson()).toList(), + 'members': instance.members?.map((e) => e.toJson()).toList(), + 'pinned_messages': instance.pinnedMessages?.map((e) => e.toJson()).toList(), + 'watcher_count': instance.watcherCount, + 'watchers': instance.watchers?.map((e) => e.toJson()).toList(), + 'read': instance.read?.map((e) => e.toJson()).toList(), + 'membership': instance.membership?.toJson(), + 'draft': instance.draft?.toJson(), + 'pending_messages': instance.pendingMessages?.map((e) => e.toJson()).toList(), + 'push_preferences': instance.pushPreferences?.toJson(), + 'active_live_locations': instance.activeLiveLocations?.map((e) => e.toJson()).toList(), +}; diff --git a/packages/stream_chat/lib/src/core/models/command.dart b/packages/stream_chat/lib/src/core/models/command.dart index 5ba0043c18..0247637560 100644 --- a/packages/stream_chat/lib/src/core/models/command.dart +++ b/packages/stream_chat/lib/src/core/models/command.dart @@ -13,8 +13,7 @@ class Command { }); /// Create a new instance from a json - factory Command.fromJson(Map json) => - _$CommandFromJson(json); + factory Command.fromJson(Map json) => _$CommandFromJson(json); /// The name of the command final String name; diff --git a/packages/stream_chat/lib/src/core/models/command.g.dart b/packages/stream_chat/lib/src/core/models/command.g.dart index 0fe9d54c6e..24c9362156 100644 --- a/packages/stream_chat/lib/src/core/models/command.g.dart +++ b/packages/stream_chat/lib/src/core/models/command.g.dart @@ -7,13 +7,13 @@ part of 'command.dart'; // ************************************************************************** Command _$CommandFromJson(Map json) => Command( - name: json['name'] as String, - description: json['description'] as String, - args: json['args'] as String, - ); + name: json['name'] as String, + description: json['description'] as String, + args: json['args'] as String, +); Map _$CommandToJson(Command instance) => { - 'name': instance.name, - 'description': instance.description, - 'args': instance.args, - }; + 'name': instance.name, + 'description': instance.description, + 'args': instance.args, +}; diff --git a/packages/stream_chat/lib/src/core/models/comparable_field.dart b/packages/stream_chat/lib/src/core/models/comparable_field.dart index f43df58a33..5b192541b2 100644 --- a/packages/stream_chat/lib/src/core/models/comparable_field.dart +++ b/packages/stream_chat/lib/src/core/models/comparable_field.dart @@ -28,7 +28,7 @@ class ComparableField implements Comparable> { (final DateTime a, final DateTime b) => a.compareTo(b), (final bool a, final bool b) when a == b => 0, (final bool a, final bool b) => a && !b ? 1 : -1, // true > false - _ => 0 // All comparisons were equal or incomparable types + _ => 0, // All comparisons were equal or incomparable types }; } } diff --git a/packages/stream_chat/lib/src/core/models/device.g.dart b/packages/stream_chat/lib/src/core/models/device.g.dart index 4de1f064af..2725a082f1 100644 --- a/packages/stream_chat/lib/src/core/models/device.g.dart +++ b/packages/stream_chat/lib/src/core/models/device.g.dart @@ -7,11 +7,11 @@ part of 'device.dart'; // ************************************************************************** Device _$DeviceFromJson(Map json) => Device( - id: json['id'] as String, - pushProvider: json['push_provider'] as String, - ); + id: json['id'] as String, + pushProvider: json['push_provider'] as String, +); Map _$DeviceToJson(Device instance) => { - 'id': instance.id, - 'push_provider': instance.pushProvider, - }; + 'id': instance.id, + 'push_provider': instance.pushProvider, +}; diff --git a/packages/stream_chat/lib/src/core/models/draft.dart b/packages/stream_chat/lib/src/core/models/draft.dart index fa4520114c..c6fb6139b2 100644 --- a/packages/stream_chat/lib/src/core/models/draft.dart +++ b/packages/stream_chat/lib/src/core/models/draft.dart @@ -73,14 +73,14 @@ class Draft extends Equatable implements ComparableFieldProvider { @override List get props => [ - channelCid, - createdAt, - message, - channel, - parentId, - parentMessage, - quotedMessage, - ]; + channelCid, + createdAt, + message, + channel, + parentId, + parentMessage, + quotedMessage, + ]; @override ComparableField? getComparableField(String sortKey) { diff --git a/packages/stream_chat/lib/src/core/models/draft.g.dart b/packages/stream_chat/lib/src/core/models/draft.g.dart index 9a7b7de9ce..1ea7feafc3 100644 --- a/packages/stream_chat/lib/src/core/models/draft.g.dart +++ b/packages/stream_chat/lib/src/core/models/draft.g.dart @@ -7,29 +7,25 @@ part of 'draft.dart'; // ************************************************************************** Draft _$DraftFromJson(Map json) => Draft( - channelCid: json['channel_cid'] as String, - createdAt: DateTime.parse(json['created_at'] as String), - message: DraftMessage.fromJson(json['message'] as Map), - channel: json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map), - parentId: json['parent_id'] as String?, - parentMessage: json['parent_message'] == null - ? null - : Message.fromJson(json['parent_message'] as Map), - quotedMessage: json['quoted_message'] == null - ? null - : Message.fromJson(json['quoted_message'] as Map), - ); + channelCid: json['channel_cid'] as String, + createdAt: DateTime.parse(json['created_at'] as String), + message: DraftMessage.fromJson(json['message'] as Map), + channel: json['channel'] == null ? null : ChannelModel.fromJson(json['channel'] as Map), + parentId: json['parent_id'] as String?, + parentMessage: json['parent_message'] == null + ? null + : Message.fromJson(json['parent_message'] as Map), + quotedMessage: json['quoted_message'] == null + ? null + : Message.fromJson(json['quoted_message'] as Map), +); Map _$DraftToJson(Draft instance) => { - 'channel_cid': instance.channelCid, - 'created_at': instance.createdAt.toIso8601String(), - 'message': instance.message.toJson(), - if (instance.channel?.toJson() case final value?) 'channel': value, - if (instance.parentId case final value?) 'parent_id': value, - if (instance.parentMessage?.toJson() case final value?) - 'parent_message': value, - if (instance.quotedMessage?.toJson() case final value?) - 'quoted_message': value, - }; + 'channel_cid': instance.channelCid, + 'created_at': instance.createdAt.toIso8601String(), + 'message': instance.message.toJson(), + if (instance.channel?.toJson() case final value?) 'channel': value, + if (instance.parentId case final value?) 'parent_id': value, + if (instance.parentMessage?.toJson() case final value?) 'parent_message': value, + if (instance.quotedMessage?.toJson() case final value?) 'quoted_message': value, +}; diff --git a/packages/stream_chat/lib/src/core/models/draft_message.dart b/packages/stream_chat/lib/src/core/models/draft_message.dart index 13c373475b..b6ae3dede0 100644 --- a/packages/stream_chat/lib/src/core/models/draft_message.dart +++ b/packages/stream_chat/lib/src/core/models/draft_message.dart @@ -28,16 +28,15 @@ class DraftMessage extends Equatable { this.poll, String? pollId, this.extraData = const {}, - }) : id = id ?? const Uuid().v4(), - type = MessageType(type), - _quotedMessageId = quotedMessageId, - _pollId = pollId; + }) : id = id ?? const Uuid().v4(), + type = MessageType(type), + _quotedMessageId = quotedMessageId, + _pollId = pollId; /// Create a new instance from JSON. - factory DraftMessage.fromJson(Map json) => - _$DraftMessageFromJson( - Serializer.moveToExtraDataFromRoot(json, topLevelFields), - ); + factory DraftMessage.fromJson(Map json) => _$DraftMessageFromJson( + Serializer.moveToExtraDataFromRoot(json, topLevelFields), + ); /// The message ID. This is either created by Stream or set client side when /// the message is added. @@ -167,19 +166,19 @@ class DraftMessage extends Equatable { @override List get props => [ - id, - text, - type, - attachments, - parentId, - showInChannel, - mentionedUsers, - quotedMessageId, - silent, - command, - pollId, - extraData, - ]; + id, + text, + type, + attachments, + parentId, + showInChannel, + mentionedUsers, + quotedMessageId, + silent, + command, + pollId, + extraData, + ]; } /// Extension on [Message] to convert it to a [DraftMessage]. diff --git a/packages/stream_chat/lib/src/core/models/draft_message.g.dart b/packages/stream_chat/lib/src/core/models/draft_message.g.dart index 910ca3b090..b1b96bf505 100644 --- a/packages/stream_chat/lib/src/core/models/draft_message.g.dart +++ b/packages/stream_chat/lib/src/core/models/draft_message.g.dart @@ -7,47 +7,38 @@ part of 'draft_message.dart'; // ************************************************************************** DraftMessage _$DraftMessageFromJson(Map json) => DraftMessage( - id: json['id'] as String?, - text: json['text'] as String?, - type: json['type'] == null - ? MessageType.regular - : MessageType.fromJson(json['type'] as String), - attachments: (json['attachments'] as List?) - ?.map((e) => Attachment.fromJson(e as Map)) - .toList() ?? - const [], - parentId: json['parent_id'] as String?, - showInChannel: json['show_in_channel'] as bool?, - mentionedUsers: (json['mentioned_users'] as List?) - ?.map((e) => User.fromJson(e as Map)) - .toList() ?? - const [], - quotedMessage: json['quoted_message'] == null - ? null - : Message.fromJson(json['quoted_message'] as Map), - quotedMessageId: json['quoted_message_id'] as String?, - silent: json['silent'] as bool? ?? false, - command: json['command'] as String?, - poll: json['poll'] == null - ? null - : Poll.fromJson(json['poll'] as Map), - pollId: json['poll_id'] as String?, - extraData: json['extra_data'] as Map? ?? const {}, - ); + id: json['id'] as String?, + text: json['text'] as String?, + type: json['type'] == null ? MessageType.regular : MessageType.fromJson(json['type'] as String), + attachments: + (json['attachments'] as List?)?.map((e) => Attachment.fromJson(e as Map)).toList() ?? + const [], + parentId: json['parent_id'] as String?, + showInChannel: json['show_in_channel'] as bool?, + mentionedUsers: + (json['mentioned_users'] as List?)?.map((e) => User.fromJson(e as Map)).toList() ?? + const [], + quotedMessage: json['quoted_message'] == null + ? null + : Message.fromJson(json['quoted_message'] as Map), + quotedMessageId: json['quoted_message_id'] as String?, + silent: json['silent'] as bool? ?? false, + command: json['command'] as String?, + poll: json['poll'] == null ? null : Poll.fromJson(json['poll'] as Map), + pollId: json['poll_id'] as String?, + extraData: json['extra_data'] as Map? ?? const {}, +); -Map _$DraftMessageToJson(DraftMessage instance) => - { - 'id': instance.id, - if (instance.text case final value?) 'text': value, - if (MessageType.toJson(instance.type) case final value?) 'type': value, - 'attachments': instance.attachments.map((e) => e.toJson()).toList(), - if (instance.parentId case final value?) 'parent_id': value, - if (instance.showInChannel case final value?) 'show_in_channel': value, - if (User.toIds(instance.mentionedUsers) case final value?) - 'mentioned_users': value, - if (instance.quotedMessageId case final value?) - 'quoted_message_id': value, - 'silent': instance.silent, - if (instance.pollId case final value?) 'poll_id': value, - 'extra_data': instance.extraData, - }; +Map _$DraftMessageToJson(DraftMessage instance) => { + 'id': instance.id, + if (instance.text case final value?) 'text': value, + if (MessageType.toJson(instance.type) case final value?) 'type': value, + 'attachments': instance.attachments.map((e) => e.toJson()).toList(), + if (instance.parentId case final value?) 'parent_id': value, + if (instance.showInChannel case final value?) 'show_in_channel': value, + if (User.toIds(instance.mentionedUsers) case final value?) 'mentioned_users': value, + if (instance.quotedMessageId case final value?) 'quoted_message_id': value, + 'silent': instance.silent, + if (instance.pollId case final value?) 'poll_id': value, + 'extra_data': instance.extraData, +}; diff --git a/packages/stream_chat/lib/src/core/models/event.dart b/packages/stream_chat/lib/src/core/models/event.dart index 1a864ce304..bc2eb27986 100644 --- a/packages/stream_chat/lib/src/core/models/event.dart +++ b/packages/stream_chat/lib/src/core/models/event.dart @@ -52,11 +52,12 @@ class Event { }) : createdAt = createdAt?.toUtc() ?? DateTime.now().toUtc(); /// Create a new instance from a json - factory Event.fromJson(Map json) => - _$EventFromJson(Serializer.moveToExtraDataFromRoot( - json, - topLevelFields, - )); + factory Event.fromJson(Map json) => _$EventFromJson( + Serializer.moveToExtraDataFromRoot( + json, + topLevelFields, + ), + ); /// The type of the event /// [EventType] contains some predefined constant types @@ -227,8 +228,8 @@ class Event { /// Serialize to json Map toJson() => Serializer.moveFromExtraDataToRoot( - _$EventToJson(this), - ); + _$EventToJson(this), + ); /// Creates a copy of [Event] with specified attributes overridden. Event copyWith({ @@ -271,51 +272,48 @@ class Event { DateTime? lastDeliveredAt, String? lastDeliveredMessageId, Map? extraData, - }) => - Event( - type: type ?? this.type, - userId: userId ?? this.userId, - cid: cid ?? this.cid, - connectionId: connectionId ?? this.connectionId, - createdAt: createdAt ?? this.createdAt, - me: me ?? this.me, - user: user ?? this.user, - message: message ?? this.message, - poll: poll ?? this.poll, - pollVote: pollVote ?? this.pollVote, - totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount, - unreadChannels: unreadChannels ?? this.unreadChannels, - reaction: reaction ?? this.reaction, - online: online ?? this.online, - channel: channel ?? this.channel, - member: member ?? this.member, - channelId: channelId ?? this.channelId, - channelType: channelType ?? this.channelType, - channelLastMessageAt: channelLastMessageAt ?? this.channelLastMessageAt, - parentId: parentId ?? this.parentId, - hardDelete: hardDelete ?? this.hardDelete, - deletedForMe: deletedForMe ?? this.deletedForMe, - aiState: aiState ?? this.aiState, - aiMessage: aiMessage ?? this.aiMessage, - messageId: messageId ?? this.messageId, - thread: thread ?? this.thread, - unreadThreadMessages: unreadThreadMessages ?? this.unreadThreadMessages, - unreadThreads: unreadThreads ?? this.unreadThreads, - lastReadAt: lastReadAt ?? this.lastReadAt, - unreadMessages: unreadMessages ?? this.unreadMessages, - lastReadMessageId: lastReadMessageId ?? this.lastReadMessageId, - draft: draft ?? this.draft, - reminder: reminder ?? this.reminder, - pushPreference: pushPreference ?? this.pushPreference, - channelPushPreference: - channelPushPreference ?? this.channelPushPreference, - channelMessageCount: channelMessageCount ?? this.channelMessageCount, - lastDeliveredAt: lastDeliveredAt ?? this.lastDeliveredAt, - lastDeliveredMessageId: - lastDeliveredMessageId ?? this.lastDeliveredMessageId, - isLocal: isLocal, - extraData: extraData ?? this.extraData, - ); + }) => Event( + type: type ?? this.type, + userId: userId ?? this.userId, + cid: cid ?? this.cid, + connectionId: connectionId ?? this.connectionId, + createdAt: createdAt ?? this.createdAt, + me: me ?? this.me, + user: user ?? this.user, + message: message ?? this.message, + poll: poll ?? this.poll, + pollVote: pollVote ?? this.pollVote, + totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount, + unreadChannels: unreadChannels ?? this.unreadChannels, + reaction: reaction ?? this.reaction, + online: online ?? this.online, + channel: channel ?? this.channel, + member: member ?? this.member, + channelId: channelId ?? this.channelId, + channelType: channelType ?? this.channelType, + channelLastMessageAt: channelLastMessageAt ?? this.channelLastMessageAt, + parentId: parentId ?? this.parentId, + hardDelete: hardDelete ?? this.hardDelete, + deletedForMe: deletedForMe ?? this.deletedForMe, + aiState: aiState ?? this.aiState, + aiMessage: aiMessage ?? this.aiMessage, + messageId: messageId ?? this.messageId, + thread: thread ?? this.thread, + unreadThreadMessages: unreadThreadMessages ?? this.unreadThreadMessages, + unreadThreads: unreadThreads ?? this.unreadThreads, + lastReadAt: lastReadAt ?? this.lastReadAt, + unreadMessages: unreadMessages ?? this.unreadMessages, + lastReadMessageId: lastReadMessageId ?? this.lastReadMessageId, + draft: draft ?? this.draft, + reminder: reminder ?? this.reminder, + pushPreference: pushPreference ?? this.pushPreference, + channelPushPreference: channelPushPreference ?? this.channelPushPreference, + channelMessageCount: channelMessageCount ?? this.channelMessageCount, + lastDeliveredAt: lastDeliveredAt ?? this.lastDeliveredAt, + lastDeliveredMessageId: lastDeliveredMessageId ?? this.lastDeliveredMessageId, + isLocal: isLocal, + extraData: extraData ?? this.extraData, + ); } /// {@template aiState} diff --git a/packages/stream_chat/lib/src/core/models/event.g.dart b/packages/stream_chat/lib/src/core/models/event.g.dart index fc7bbe0db0..c3aa215012 100644 --- a/packages/stream_chat/lib/src/core/models/event.g.dart +++ b/packages/stream_chat/lib/src/core/models/event.g.dart @@ -7,138 +7,96 @@ part of 'event.dart'; // ************************************************************************** Event _$EventFromJson(Map json) => Event( - type: json['type'] as String? ?? 'local.event', - userId: json['user_id'] as String?, - cid: json['cid'] as String?, - connectionId: json['connection_id'] as String?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - me: json['me'] == null - ? null - : OwnUser.fromJson(json['me'] as Map), - user: json['user'] == null - ? null - : User.fromJson(json['user'] as Map), - message: json['message'] == null - ? null - : Message.fromJson(json['message'] as Map), - poll: json['poll'] == null - ? null - : Poll.fromJson(json['poll'] as Map), - pollVote: json['poll_vote'] == null - ? null - : PollVote.fromJson(json['poll_vote'] as Map), - totalUnreadCount: (json['total_unread_count'] as num?)?.toInt(), - unreadChannels: (json['unread_channels'] as num?)?.toInt(), - reaction: json['reaction'] == null - ? null - : Reaction.fromJson(json['reaction'] as Map), - online: json['online'] as bool?, - channel: json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map), - member: json['member'] == null - ? null - : Member.fromJson(json['member'] as Map), - channelId: json['channel_id'] as String?, - channelType: json['channel_type'] as String?, - channelLastMessageAt: json['channel_last_message_at'] == null - ? null - : DateTime.parse(json['channel_last_message_at'] as String), - parentId: json['parent_id'] as String?, - hardDelete: json['hard_delete'] as bool?, - deletedForMe: json['deleted_for_me'] as bool?, - aiState: $enumDecodeNullable(_$AITypingStateEnumMap, json['ai_state'], - unknownValue: AITypingState.idle), - aiMessage: json['ai_message'] as String?, - messageId: json['message_id'] as String?, - thread: json['thread'] == null - ? null - : Thread.fromJson(json['thread'] as Map), - unreadThreadMessages: (json['unread_thread_messages'] as num?)?.toInt(), - unreadThreads: (json['unread_threads'] as num?)?.toInt(), - lastReadAt: json['last_read_at'] == null - ? null - : DateTime.parse(json['last_read_at'] as String), - unreadMessages: (json['unread_messages'] as num?)?.toInt(), - lastReadMessageId: json['last_read_message_id'] as String?, - draft: json['draft'] == null - ? null - : Draft.fromJson(json['draft'] as Map), - reminder: json['reminder'] == null - ? null - : MessageReminder.fromJson(json['reminder'] as Map), - pushPreference: json['push_preference'] == null - ? null - : PushPreference.fromJson( - json['push_preference'] as Map), - channelPushPreference: json['channel_push_preference'] == null - ? null - : ChannelPushPreference.fromJson( - json['channel_push_preference'] as Map), - channelMessageCount: (json['channel_message_count'] as num?)?.toInt(), - lastDeliveredAt: json['last_delivered_at'] == null - ? null - : DateTime.parse(json['last_delivered_at'] as String), - lastDeliveredMessageId: json['last_delivered_message_id'] as String?, - extraData: json['extra_data'] as Map? ?? const {}, - isLocal: json['is_local'] as bool? ?? false, - ); + type: json['type'] as String? ?? 'local.event', + userId: json['user_id'] as String?, + cid: json['cid'] as String?, + connectionId: json['connection_id'] as String?, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + me: json['me'] == null ? null : OwnUser.fromJson(json['me'] as Map), + user: json['user'] == null ? null : User.fromJson(json['user'] as Map), + message: json['message'] == null ? null : Message.fromJson(json['message'] as Map), + poll: json['poll'] == null ? null : Poll.fromJson(json['poll'] as Map), + pollVote: json['poll_vote'] == null ? null : PollVote.fromJson(json['poll_vote'] as Map), + totalUnreadCount: (json['total_unread_count'] as num?)?.toInt(), + unreadChannels: (json['unread_channels'] as num?)?.toInt(), + reaction: json['reaction'] == null ? null : Reaction.fromJson(json['reaction'] as Map), + online: json['online'] as bool?, + channel: json['channel'] == null ? null : ChannelModel.fromJson(json['channel'] as Map), + member: json['member'] == null ? null : Member.fromJson(json['member'] as Map), + channelId: json['channel_id'] as String?, + channelType: json['channel_type'] as String?, + channelLastMessageAt: json['channel_last_message_at'] == null + ? null + : DateTime.parse(json['channel_last_message_at'] as String), + parentId: json['parent_id'] as String?, + hardDelete: json['hard_delete'] as bool?, + deletedForMe: json['deleted_for_me'] as bool?, + aiState: $enumDecodeNullable(_$AITypingStateEnumMap, json['ai_state'], unknownValue: AITypingState.idle), + aiMessage: json['ai_message'] as String?, + messageId: json['message_id'] as String?, + thread: json['thread'] == null ? null : Thread.fromJson(json['thread'] as Map), + unreadThreadMessages: (json['unread_thread_messages'] as num?)?.toInt(), + unreadThreads: (json['unread_threads'] as num?)?.toInt(), + lastReadAt: json['last_read_at'] == null ? null : DateTime.parse(json['last_read_at'] as String), + unreadMessages: (json['unread_messages'] as num?)?.toInt(), + lastReadMessageId: json['last_read_message_id'] as String?, + draft: json['draft'] == null ? null : Draft.fromJson(json['draft'] as Map), + reminder: json['reminder'] == null ? null : MessageReminder.fromJson(json['reminder'] as Map), + pushPreference: json['push_preference'] == null + ? null + : PushPreference.fromJson(json['push_preference'] as Map), + channelPushPreference: json['channel_push_preference'] == null + ? null + : ChannelPushPreference.fromJson(json['channel_push_preference'] as Map), + channelMessageCount: (json['channel_message_count'] as num?)?.toInt(), + lastDeliveredAt: json['last_delivered_at'] == null ? null : DateTime.parse(json['last_delivered_at'] as String), + lastDeliveredMessageId: json['last_delivered_message_id'] as String?, + extraData: json['extra_data'] as Map? ?? const {}, + isLocal: json['is_local'] as bool? ?? false, +); Map _$EventToJson(Event instance) => { - 'type': instance.type, - if (instance.userId case final value?) 'user_id': value, - if (instance.cid case final value?) 'cid': value, - if (instance.channelId case final value?) 'channel_id': value, - if (instance.channelType case final value?) 'channel_type': value, - if (instance.channelLastMessageAt?.toIso8601String() case final value?) - 'channel_last_message_at': value, - if (instance.connectionId case final value?) 'connection_id': value, - 'created_at': instance.createdAt.toIso8601String(), - if (instance.me?.toJson() case final value?) 'me': value, - if (instance.user?.toJson() case final value?) 'user': value, - if (instance.message?.toJson() case final value?) 'message': value, - if (instance.poll?.toJson() case final value?) 'poll': value, - if (instance.pollVote?.toJson() case final value?) 'poll_vote': value, - if (instance.channel?.toJson() case final value?) 'channel': value, - if (instance.member?.toJson() case final value?) 'member': value, - if (instance.reaction?.toJson() case final value?) 'reaction': value, - if (instance.totalUnreadCount case final value?) - 'total_unread_count': value, - if (instance.unreadChannels case final value?) 'unread_channels': value, - if (instance.online case final value?) 'online': value, - if (instance.parentId case final value?) 'parent_id': value, - 'is_local': instance.isLocal, - if (instance.hardDelete case final value?) 'hard_delete': value, - if (instance.deletedForMe case final value?) 'deleted_for_me': value, - if (_$AITypingStateEnumMap[instance.aiState] case final value?) - 'ai_state': value, - if (instance.aiMessage case final value?) 'ai_message': value, - if (instance.messageId case final value?) 'message_id': value, - if (instance.thread?.toJson() case final value?) 'thread': value, - if (instance.unreadThreadMessages case final value?) - 'unread_thread_messages': value, - if (instance.unreadThreads case final value?) 'unread_threads': value, - if (instance.lastReadAt?.toIso8601String() case final value?) - 'last_read_at': value, - if (instance.unreadMessages case final value?) 'unread_messages': value, - if (instance.lastReadMessageId case final value?) - 'last_read_message_id': value, - if (instance.draft?.toJson() case final value?) 'draft': value, - if (instance.reminder?.toJson() case final value?) 'reminder': value, - if (instance.pushPreference?.toJson() case final value?) - 'push_preference': value, - if (instance.channelPushPreference?.toJson() case final value?) - 'channel_push_preference': value, - if (instance.channelMessageCount case final value?) - 'channel_message_count': value, - if (instance.lastDeliveredAt?.toIso8601String() case final value?) - 'last_delivered_at': value, - if (instance.lastDeliveredMessageId case final value?) - 'last_delivered_message_id': value, - 'extra_data': instance.extraData, - }; + 'type': instance.type, + if (instance.userId case final value?) 'user_id': value, + if (instance.cid case final value?) 'cid': value, + if (instance.channelId case final value?) 'channel_id': value, + if (instance.channelType case final value?) 'channel_type': value, + if (instance.channelLastMessageAt?.toIso8601String() case final value?) 'channel_last_message_at': value, + if (instance.connectionId case final value?) 'connection_id': value, + 'created_at': instance.createdAt.toIso8601String(), + if (instance.me?.toJson() case final value?) 'me': value, + if (instance.user?.toJson() case final value?) 'user': value, + if (instance.message?.toJson() case final value?) 'message': value, + if (instance.poll?.toJson() case final value?) 'poll': value, + if (instance.pollVote?.toJson() case final value?) 'poll_vote': value, + if (instance.channel?.toJson() case final value?) 'channel': value, + if (instance.member?.toJson() case final value?) 'member': value, + if (instance.reaction?.toJson() case final value?) 'reaction': value, + if (instance.totalUnreadCount case final value?) 'total_unread_count': value, + if (instance.unreadChannels case final value?) 'unread_channels': value, + if (instance.online case final value?) 'online': value, + if (instance.parentId case final value?) 'parent_id': value, + 'is_local': instance.isLocal, + if (instance.hardDelete case final value?) 'hard_delete': value, + if (instance.deletedForMe case final value?) 'deleted_for_me': value, + if (_$AITypingStateEnumMap[instance.aiState] case final value?) 'ai_state': value, + if (instance.aiMessage case final value?) 'ai_message': value, + if (instance.messageId case final value?) 'message_id': value, + if (instance.thread?.toJson() case final value?) 'thread': value, + if (instance.unreadThreadMessages case final value?) 'unread_thread_messages': value, + if (instance.unreadThreads case final value?) 'unread_threads': value, + if (instance.lastReadAt?.toIso8601String() case final value?) 'last_read_at': value, + if (instance.unreadMessages case final value?) 'unread_messages': value, + if (instance.lastReadMessageId case final value?) 'last_read_message_id': value, + if (instance.draft?.toJson() case final value?) 'draft': value, + if (instance.reminder?.toJson() case final value?) 'reminder': value, + if (instance.pushPreference?.toJson() case final value?) 'push_preference': value, + if (instance.channelPushPreference?.toJson() case final value?) 'channel_push_preference': value, + if (instance.channelMessageCount case final value?) 'channel_message_count': value, + if (instance.lastDeliveredAt?.toIso8601String() case final value?) 'last_delivered_at': value, + if (instance.lastDeliveredMessageId case final value?) 'last_delivered_message_id': value, + 'extra_data': instance.extraData, +}; const _$AITypingStateEnumMap = { AITypingState.idle: 'AI_STATE_IDLE', diff --git a/packages/stream_chat/lib/src/core/models/filter.dart b/packages/stream_chat/lib/src/core/models/filter.dart index 2122519d0f..fc8dbadd0c 100644 --- a/packages/stream_chat/lib/src/core/models/filter.dart +++ b/packages/stream_chat/lib/src/core/models/filter.dart @@ -53,7 +53,8 @@ enum FilterOperator { nor, /// Matches any list that contains the specified value - contains; + contains + ; @override String toString() { @@ -117,29 +118,22 @@ class Filter extends Equatable { }) : operator = '$operator'; /// An empty filter - const Filter.empty() - : value = const {}, - operator = null, - key = null; + const Filter.empty() : value = const {}, operator = null, key = null; /// Combines the provided filters and matches the values /// matched by all filters. - factory Filter.and(List filters) => - Filter._(operator: FilterOperator.and, value: filters); + factory Filter.and(List filters) => Filter._(operator: FilterOperator.and, value: filters); /// Combines the provided filters and matches the values /// matched by at least one of the filters. - factory Filter.or(List filters) => - Filter._(operator: FilterOperator.or, value: filters); + factory Filter.or(List filters) => Filter._(operator: FilterOperator.or, value: filters); /// Combines the provided filters and matches the values /// not matched by all the filters. - factory Filter.nor(List filters) => - Filter._(operator: FilterOperator.nor, value: filters); + factory Filter.nor(List filters) => Filter._(operator: FilterOperator.nor, value: filters); /// Matches values that are equal to a specified value. - factory Filter.equal(String key, Object value) => - Filter._(operator: FilterOperator.equal, key: key, value: value); + factory Filter.equal(String key, Object value) => Filter._(operator: FilterOperator.equal, key: key, value: value); /// Matches all values that are not equal to a specified value. factory Filter.notEqual(String key, Object value) => @@ -154,8 +148,7 @@ class Filter extends Equatable { Filter._(operator: FilterOperator.greaterOrEqual, key: key, value: value); /// Matches values that are less than a specified value. - factory Filter.less(String key, Object value) => - Filter._(operator: FilterOperator.less, key: key, value: value); + factory Filter.less(String key, Object value) => Filter._(operator: FilterOperator.less, key: key, value: value); /// Matches values that are less than or equal to a specified value. factory Filter.lessOrEqual(String key, Object value) => @@ -170,8 +163,7 @@ class Filter extends Equatable { Filter._(operator: FilterOperator.notIn, key: key, value: values); /// Matches values by performing text search with the specified value. - factory Filter.query(String key, String text) => - Filter._(operator: FilterOperator.query, key: key, value: text); + factory Filter.query(String key, String text) => Filter._(operator: FilterOperator.query, key: key, value: text); /// Matches values with the specified prefix. factory Filter.autoComplete(String key, String text) => diff --git a/packages/stream_chat/lib/src/core/models/location.dart b/packages/stream_chat/lib/src/core/models/location.dart index 665fe4c031..9b66746f24 100644 --- a/packages/stream_chat/lib/src/core/models/location.dart +++ b/packages/stream_chat/lib/src/core/models/location.dart @@ -32,13 +32,12 @@ class Location extends Equatable { DateTime? endAt, DateTime? createdAt, DateTime? updatedAt, - }) : endAt = endAt?.toUtc(), - createdAt = createdAt ?? DateTime.timestamp(), - updatedAt = updatedAt ?? DateTime.timestamp(); + }) : endAt = endAt?.toUtc(), + createdAt = createdAt ?? DateTime.timestamp(), + updatedAt = updatedAt ?? DateTime.timestamp(); /// Create a new instance from a json - factory Location.fromJson(Map json) => - _$LocationFromJson(json); + factory Location.fromJson(Map json) => _$LocationFromJson(json); /// The channel CID where the message exists. /// @@ -143,16 +142,16 @@ class Location extends Equatable { @override List get props => [ - channelCid, - channel, - messageId, - message, - userId, - latitude, - longitude, - createdByDeviceId, - endAt, - createdAt, - updatedAt, - ]; + channelCid, + channel, + messageId, + message, + userId, + latitude, + longitude, + createdByDeviceId, + endAt, + createdAt, + updatedAt, + ]; } diff --git a/packages/stream_chat/lib/src/core/models/location.g.dart b/packages/stream_chat/lib/src/core/models/location.g.dart index e1fac9ac92..662fc500d5 100644 --- a/packages/stream_chat/lib/src/core/models/location.g.dart +++ b/packages/stream_chat/lib/src/core/models/location.g.dart @@ -7,33 +7,22 @@ part of 'location.dart'; // ************************************************************************** Location _$LocationFromJson(Map json) => Location( - channelCid: json['channel_cid'] as String?, - channel: json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map), - messageId: json['message_id'] as String?, - message: json['message'] == null - ? null - : Message.fromJson(json['message'] as Map), - userId: json['user_id'] as String?, - latitude: (json['latitude'] as num).toDouble(), - longitude: (json['longitude'] as num).toDouble(), - createdByDeviceId: json['created_by_device_id'] as String?, - endAt: json['end_at'] == null - ? null - : DateTime.parse(json['end_at'] as String), - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - ); + channelCid: json['channel_cid'] as String?, + channel: json['channel'] == null ? null : ChannelModel.fromJson(json['channel'] as Map), + messageId: json['message_id'] as String?, + message: json['message'] == null ? null : Message.fromJson(json['message'] as Map), + userId: json['user_id'] as String?, + latitude: (json['latitude'] as num).toDouble(), + longitude: (json['longitude'] as num).toDouble(), + createdByDeviceId: json['created_by_device_id'] as String?, + endAt: json['end_at'] == null ? null : DateTime.parse(json['end_at'] as String), + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), +); Map _$LocationToJson(Location instance) => { - 'latitude': instance.latitude, - 'longitude': instance.longitude, - if (instance.createdByDeviceId case final value?) - 'created_by_device_id': value, - if (instance.endAt?.toIso8601String() case final value?) 'end_at': value, - }; + 'latitude': instance.latitude, + 'longitude': instance.longitude, + if (instance.createdByDeviceId case final value?) 'created_by_device_id': value, + if (instance.endAt?.toIso8601String() case final value?) 'end_at': value, +}; diff --git a/packages/stream_chat/lib/src/core/models/member.dart b/packages/stream_chat/lib/src/core/models/member.dart index 452412f7c1..06c7185f1d 100644 --- a/packages/stream_chat/lib/src/core/models/member.dart +++ b/packages/stream_chat/lib/src/core/models/member.dart @@ -28,14 +28,14 @@ class Member extends Equatable implements ComparableFieldProvider { this.archivedAt, this.deletedMessages = const [], this.extraData = const {}, - }) : userId = userId ?? user?.id, - createdAt = createdAt ?? DateTime.now(), - updatedAt = updatedAt ?? DateTime.now(); + }) : userId = userId ?? user?.id, + createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json factory Member.fromJson(Map json) => _$MemberFromJson( - Serializer.moveToExtraDataFromRoot(json, _topLevelFields), - ); + Serializer.moveToExtraDataFromRoot(json, _topLevelFields), + ); /// Known top level fields. /// @@ -128,50 +128,49 @@ class Member extends Equatable implements ComparableFieldProvider { bool? shadowBanned, List? deletedMessages, Map? extraData, - }) => - Member( - user: user ?? this.user, - inviteAcceptedAt: inviteAcceptedAt ?? this.inviteAcceptedAt, - inviteRejectedAt: inviteRejectedAt ?? this.inviteRejectedAt, - invited: invited ?? this.invited, - banned: banned ?? this.banned, - banExpires: banExpires ?? this.banExpires, - shadowBanned: shadowBanned ?? this.shadowBanned, - channelRole: channelRole ?? this.channelRole, - userId: userId ?? this.userId, - isModerator: isModerator ?? this.isModerator, - pinnedAt: pinnedAt ?? this.pinnedAt, - archivedAt: archivedAt ?? this.archivedAt, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - deletedMessages: deletedMessages ?? this.deletedMessages, - extraData: extraData ?? this.extraData, - ); + }) => Member( + user: user ?? this.user, + inviteAcceptedAt: inviteAcceptedAt ?? this.inviteAcceptedAt, + inviteRejectedAt: inviteRejectedAt ?? this.inviteRejectedAt, + invited: invited ?? this.invited, + banned: banned ?? this.banned, + banExpires: banExpires ?? this.banExpires, + shadowBanned: shadowBanned ?? this.shadowBanned, + channelRole: channelRole ?? this.channelRole, + userId: userId ?? this.userId, + isModerator: isModerator ?? this.isModerator, + pinnedAt: pinnedAt ?? this.pinnedAt, + archivedAt: archivedAt ?? this.archivedAt, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedMessages: deletedMessages ?? this.deletedMessages, + extraData: extraData ?? this.extraData, + ); /// Serialize to json Map toJson() => Serializer.moveFromExtraDataToRoot( - _$MemberToJson(this), - ); + _$MemberToJson(this), + ); @override List get props => [ - user, - inviteAcceptedAt, - inviteRejectedAt, - invited, - channelRole, - userId, - isModerator, - banned, - banExpires, - shadowBanned, - pinnedAt, - archivedAt, - createdAt, - updatedAt, - deletedMessages, - extraData, - ]; + user, + inviteAcceptedAt, + inviteRejectedAt, + invited, + channelRole, + userId, + isModerator, + banned, + banExpires, + shadowBanned, + pinnedAt, + archivedAt, + createdAt, + updatedAt, + deletedMessages, + extraData, + ]; @override ComparableField? getComparableField(String sortKey) { diff --git a/packages/stream_chat/lib/src/core/models/member.g.dart b/packages/stream_chat/lib/src/core/models/member.g.dart index abdaf29d65..730051a2db 100644 --- a/packages/stream_chat/lib/src/core/models/member.g.dart +++ b/packages/stream_chat/lib/src/core/models/member.g.dart @@ -7,58 +7,39 @@ part of 'member.dart'; // ************************************************************************** Member _$MemberFromJson(Map json) => Member( - user: json['user'] == null - ? null - : User.fromJson(json['user'] as Map), - inviteAcceptedAt: json['invite_accepted_at'] == null - ? null - : DateTime.parse(json['invite_accepted_at'] as String), - inviteRejectedAt: json['invite_rejected_at'] == null - ? null - : DateTime.parse(json['invite_rejected_at'] as String), - invited: json['invited'] as bool? ?? false, - channelRole: json['channel_role'] as String?, - userId: json['user_id'] as String?, - isModerator: json['is_moderator'] as bool? ?? false, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - banned: json['banned'] as bool? ?? false, - banExpires: json['ban_expires'] == null - ? null - : DateTime.parse(json['ban_expires'] as String), - shadowBanned: json['shadow_banned'] as bool? ?? false, - pinnedAt: json['pinned_at'] == null - ? null - : DateTime.parse(json['pinned_at'] as String), - archivedAt: json['archived_at'] == null - ? null - : DateTime.parse(json['archived_at'] as String), - deletedMessages: (json['deleted_messages'] as List?) - ?.map((e) => e as String) - .toList() ?? - const [], - extraData: json['extra_data'] as Map? ?? const {}, - ); + user: json['user'] == null ? null : User.fromJson(json['user'] as Map), + inviteAcceptedAt: json['invite_accepted_at'] == null ? null : DateTime.parse(json['invite_accepted_at'] as String), + inviteRejectedAt: json['invite_rejected_at'] == null ? null : DateTime.parse(json['invite_rejected_at'] as String), + invited: json['invited'] as bool? ?? false, + channelRole: json['channel_role'] as String?, + userId: json['user_id'] as String?, + isModerator: json['is_moderator'] as bool? ?? false, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + banned: json['banned'] as bool? ?? false, + banExpires: json['ban_expires'] == null ? null : DateTime.parse(json['ban_expires'] as String), + shadowBanned: json['shadow_banned'] as bool? ?? false, + pinnedAt: json['pinned_at'] == null ? null : DateTime.parse(json['pinned_at'] as String), + archivedAt: json['archived_at'] == null ? null : DateTime.parse(json['archived_at'] as String), + deletedMessages: (json['deleted_messages'] as List?)?.map((e) => e as String).toList() ?? const [], + extraData: json['extra_data'] as Map? ?? const {}, +); Map _$MemberToJson(Member instance) => { - 'user': instance.user?.toJson(), - 'invite_accepted_at': instance.inviteAcceptedAt?.toIso8601String(), - 'invite_rejected_at': instance.inviteRejectedAt?.toIso8601String(), - 'invited': instance.invited, - 'channel_role': instance.channelRole, - 'user_id': instance.userId, - 'is_moderator': instance.isModerator, - 'banned': instance.banned, - 'ban_expires': instance.banExpires?.toIso8601String(), - 'shadow_banned': instance.shadowBanned, - 'pinned_at': instance.pinnedAt?.toIso8601String(), - 'archived_at': instance.archivedAt?.toIso8601String(), - 'created_at': instance.createdAt.toIso8601String(), - 'updated_at': instance.updatedAt.toIso8601String(), - 'deleted_messages': instance.deletedMessages, - 'extra_data': instance.extraData, - }; + 'user': instance.user?.toJson(), + 'invite_accepted_at': instance.inviteAcceptedAt?.toIso8601String(), + 'invite_rejected_at': instance.inviteRejectedAt?.toIso8601String(), + 'invited': instance.invited, + 'channel_role': instance.channelRole, + 'user_id': instance.userId, + 'is_moderator': instance.isModerator, + 'banned': instance.banned, + 'ban_expires': instance.banExpires?.toIso8601String(), + 'shadow_banned': instance.shadowBanned, + 'pinned_at': instance.pinnedAt?.toIso8601String(), + 'archived_at': instance.archivedAt?.toIso8601String(), + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'deleted_messages': instance.deletedMessages, + 'extra_data': instance.extraData, +}; diff --git a/packages/stream_chat/lib/src/core/models/message.dart b/packages/stream_chat/lib/src/core/models/message.dart index 7c849e972b..12b32cbbf5 100644 --- a/packages/stream_chat/lib/src/core/models/message.dart +++ b/packages/stream_chat/lib/src/core/models/message.dart @@ -70,14 +70,14 @@ class Message extends Equatable implements ComparableFieldProvider { this.reminder, this.channelRole, this.sharedLocation, - }) : id = id ?? const Uuid().v4(), - type = MessageType(type), - pinExpires = pinExpires?.toUtc(), - remoteCreatedAt = createdAt, - remoteUpdatedAt = updatedAt, - remoteDeletedAt = deletedAt, - _quotedMessageId = quotedMessageId, - _pollId = pollId; + }) : id = id ?? const Uuid().v4(), + type = MessageType(type), + pinExpires = pinExpires?.toUtc(), + remoteCreatedAt = createdAt, + remoteUpdatedAt = updatedAt, + remoteDeletedAt = deletedAt, + _quotedMessageId = quotedMessageId, + _pollId = pollId; /// Create a new instance from JSON. factory Message.fromJson(Map json) { @@ -453,18 +453,14 @@ class Message extends Equatable implements ComparableFieldProvider { bool? deletedForMe, }) { assert(() { - if (pinExpires is! DateTime && - pinExpires != null && - pinExpires is! _NullConst) { + if (pinExpires is! DateTime && pinExpires != null && pinExpires is! _NullConst) { throw ArgumentError('`pinExpires` can only be set as DateTime or null'); } return true; }(), 'Validate type for pinExpires'); assert(() { - if (quotedMessage is! Message && - quotedMessage != null && - quotedMessage is! _NullConst) { + if (quotedMessage is! Message && quotedMessage != null && quotedMessage is! _NullConst) { throw ArgumentError( '`quotedMessage` can only be set as Message or null', ); @@ -473,9 +469,7 @@ class Message extends Equatable implements ComparableFieldProvider { }(), 'Validate type for quotedMessage'); assert(() { - if (quotedMessageId is! String && - quotedMessageId != null && - quotedMessageId is! _NullConst) { + if (quotedMessageId is! String && quotedMessageId != null && quotedMessageId is! _NullConst) { throw ArgumentError( '`quotedMessage` can only be set as String or null', ); @@ -495,12 +489,8 @@ class Message extends Equatable implements ComparableFieldProvider { latestReactions: latestReactions ?? this.latestReactions, ownReactions: ownReactions ?? this.ownReactions, parentId: parentId ?? this.parentId, - quotedMessage: quotedMessage == _nullConst - ? this.quotedMessage - : quotedMessage as Message?, - quotedMessageId: quotedMessageId == _nullConst - ? _quotedMessageId - : quotedMessageId as String?, + quotedMessage: quotedMessage == _nullConst ? this.quotedMessage : quotedMessage as Message?, + quotedMessageId: quotedMessageId == _nullConst ? _quotedMessageId : quotedMessageId as String?, replyCount: replyCount ?? this.replyCount, threadParticipants: threadParticipants ?? this.threadParticipants, showInChannel: showInChannel ?? this.showInChannel, @@ -515,8 +505,7 @@ class Message extends Equatable implements ComparableFieldProvider { user: user ?? this.user, pinned: pinned ?? this.pinned, pinnedAt: pinnedAt ?? this.pinnedAt, - pinExpires: - pinExpires == _nullConst ? this.pinExpires : pinExpires as DateTime?, + pinExpires: pinExpires == _nullConst ? this.pinExpires : pinExpires as DateTime?, pinnedBy: pinnedBy ?? this.pinnedBy, poll: poll ?? this.poll, pollId: pollId ?? _pollId, @@ -526,8 +515,7 @@ class Message extends Equatable implements ComparableFieldProvider { restrictedVisibility: restrictedVisibility ?? this.restrictedVisibility, moderation: moderation ?? this.moderation, draft: draft == _nullConst ? this.draft : draft as Draft?, - reminder: - reminder == _nullConst ? this.reminder : reminder as MessageReminder?, + reminder: reminder == _nullConst ? this.reminder : reminder as MessageReminder?, channelRole: channelRole ?? this.channelRole, sharedLocation: sharedLocation ?? this.sharedLocation, deletedForMe: deletedForMe ?? this.deletedForMe, @@ -606,48 +594,48 @@ class Message extends Equatable implements ComparableFieldProvider { @override List get props => [ - id, - text, - type, - attachments, - mentionedUsers, - reactionGroups, - latestReactions, - ownReactions, - parentId, - quotedMessage, - quotedMessageId, - replyCount, - threadParticipants, - showInChannel, - shadowed, - silent, - command, - localCreatedAt, - remoteCreatedAt, - localUpdatedAt, - remoteUpdatedAt, - localDeletedAt, - remoteDeletedAt, - messageTextUpdatedAt, - user, - pinned, - pinnedAt, - pinExpires, - pinnedBy, - poll, - pollId, - extraData, - state, - i18n, - restrictedVisibility, - moderation, - draft, - reminder, - channelRole, - sharedLocation, - deletedForMe, - ]; + id, + text, + type, + attachments, + mentionedUsers, + reactionGroups, + latestReactions, + ownReactions, + parentId, + quotedMessage, + quotedMessageId, + replyCount, + threadParticipants, + showInChannel, + shadowed, + silent, + command, + localCreatedAt, + remoteCreatedAt, + localUpdatedAt, + remoteUpdatedAt, + localDeletedAt, + remoteDeletedAt, + messageTextUpdatedAt, + user, + pinned, + pinnedAt, + pinExpires, + pinnedBy, + poll, + pollId, + extraData, + state, + i18n, + restrictedVisibility, + moderation, + draft, + reminder, + channelRole, + sharedLocation, + deletedForMe, + ]; @override ComparableField? getComparableField(String sortKey) { diff --git a/packages/stream_chat/lib/src/core/models/message.g.dart b/packages/stream_chat/lib/src/core/models/message.g.dart index 4b47a343c6..fe9d4721fd 100644 --- a/packages/stream_chat/lib/src/core/models/message.g.dart +++ b/packages/stream_chat/lib/src/core/models/message.g.dart @@ -7,114 +7,81 @@ part of 'message.dart'; // ************************************************************************** Message _$MessageFromJson(Map json) => Message( - id: json['id'] as String?, - text: json['text'] as String?, - type: json['type'] == null - ? MessageType.regular - : MessageType.fromJson(json['type'] as String), - attachments: (json['attachments'] as List?) - ?.map((e) => Attachment.fromJson(e as Map)) - .toList() ?? - const [], - mentionedUsers: (json['mentioned_users'] as List?) - ?.map((e) => User.fromJson(e as Map)) - .toList() ?? - const [], - silent: json['silent'] as bool? ?? false, - shadowed: json['shadowed'] as bool? ?? false, - reactionGroups: (Message._reactionGroupsReadValue(json, 'reaction_groups') - as Map?) - ?.map( - (k, e) => - MapEntry(k, ReactionGroup.fromJson(e as Map)), - ), - latestReactions: (json['latest_reactions'] as List?) - ?.map((e) => Reaction.fromJson(e as Map)) - .toList(), - ownReactions: (json['own_reactions'] as List?) - ?.map((e) => Reaction.fromJson(e as Map)) - .toList(), - parentId: json['parent_id'] as String?, - quotedMessage: json['quoted_message'] == null - ? null - : Message.fromJson(json['quoted_message'] as Map), - quotedMessageId: json['quoted_message_id'] as String?, - replyCount: (json['reply_count'] as num?)?.toInt() ?? 0, - threadParticipants: (json['thread_participants'] as List?) - ?.map((e) => User.fromJson(e as Map)) - .toList(), - showInChannel: json['show_in_channel'] as bool?, - command: json['command'] as String?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - deletedAt: json['deleted_at'] == null - ? null - : DateTime.parse(json['deleted_at'] as String), - deletedForMe: json['deleted_for_me'] as bool?, - messageTextUpdatedAt: json['message_text_updated_at'] == null - ? null - : DateTime.parse(json['message_text_updated_at'] as String), - user: json['user'] == null - ? null - : User.fromJson(json['user'] as Map), - pinned: json['pinned'] as bool? ?? false, - pinnedAt: json['pinned_at'] == null - ? null - : DateTime.parse(json['pinned_at'] as String), - pinExpires: json['pin_expires'] == null - ? null - : DateTime.parse(json['pin_expires'] as String), - pinnedBy: json['pinned_by'] == null - ? null - : User.fromJson(json['pinned_by'] as Map), - poll: json['poll'] == null - ? null - : Poll.fromJson(json['poll'] as Map), - pollId: json['poll_id'] as String?, - extraData: json['extra_data'] as Map? ?? const {}, - i18n: (json['i18n'] as Map?)?.map( - (k, e) => MapEntry(k, e as String), - ), - restrictedVisibility: (json['restricted_visibility'] as List?) - ?.map((e) => e as String) - .toList(), - moderation: Message._moderationReadValue(json, 'moderation') == null - ? null - : Moderation.fromJson(Message._moderationReadValue(json, 'moderation') - as Map), - draft: json['draft'] == null - ? null - : Draft.fromJson(json['draft'] as Map), - reminder: json['reminder'] == null - ? null - : MessageReminder.fromJson(json['reminder'] as Map), - channelRole: - Message._channelRoleReadValue(json, 'channel_role') as String?, - sharedLocation: json['shared_location'] == null - ? null - : Location.fromJson(json['shared_location'] as Map), - ); + id: json['id'] as String?, + text: json['text'] as String?, + type: json['type'] == null ? MessageType.regular : MessageType.fromJson(json['type'] as String), + attachments: + (json['attachments'] as List?)?.map((e) => Attachment.fromJson(e as Map)).toList() ?? + const [], + mentionedUsers: + (json['mentioned_users'] as List?)?.map((e) => User.fromJson(e as Map)).toList() ?? + const [], + silent: json['silent'] as bool? ?? false, + shadowed: json['shadowed'] as bool? ?? false, + reactionGroups: (Message._reactionGroupsReadValue(json, 'reaction_groups') as Map?)?.map( + (k, e) => MapEntry(k, ReactionGroup.fromJson(e as Map)), + ), + latestReactions: (json['latest_reactions'] as List?) + ?.map((e) => Reaction.fromJson(e as Map)) + .toList(), + ownReactions: (json['own_reactions'] as List?) + ?.map((e) => Reaction.fromJson(e as Map)) + .toList(), + parentId: json['parent_id'] as String?, + quotedMessage: json['quoted_message'] == null + ? null + : Message.fromJson(json['quoted_message'] as Map), + quotedMessageId: json['quoted_message_id'] as String?, + replyCount: (json['reply_count'] as num?)?.toInt() ?? 0, + threadParticipants: (json['thread_participants'] as List?) + ?.map((e) => User.fromJson(e as Map)) + .toList(), + showInChannel: json['show_in_channel'] as bool?, + command: json['command'] as String?, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + deletedAt: json['deleted_at'] == null ? null : DateTime.parse(json['deleted_at'] as String), + deletedForMe: json['deleted_for_me'] as bool?, + messageTextUpdatedAt: json['message_text_updated_at'] == null + ? null + : DateTime.parse(json['message_text_updated_at'] as String), + user: json['user'] == null ? null : User.fromJson(json['user'] as Map), + pinned: json['pinned'] as bool? ?? false, + pinnedAt: json['pinned_at'] == null ? null : DateTime.parse(json['pinned_at'] as String), + pinExpires: json['pin_expires'] == null ? null : DateTime.parse(json['pin_expires'] as String), + pinnedBy: json['pinned_by'] == null ? null : User.fromJson(json['pinned_by'] as Map), + poll: json['poll'] == null ? null : Poll.fromJson(json['poll'] as Map), + pollId: json['poll_id'] as String?, + extraData: json['extra_data'] as Map? ?? const {}, + i18n: (json['i18n'] as Map?)?.map( + (k, e) => MapEntry(k, e as String), + ), + restrictedVisibility: (json['restricted_visibility'] as List?)?.map((e) => e as String).toList(), + moderation: Message._moderationReadValue(json, 'moderation') == null + ? null + : Moderation.fromJson(Message._moderationReadValue(json, 'moderation') as Map), + draft: json['draft'] == null ? null : Draft.fromJson(json['draft'] as Map), + reminder: json['reminder'] == null ? null : MessageReminder.fromJson(json['reminder'] as Map), + channelRole: Message._channelRoleReadValue(json, 'channel_role') as String?, + sharedLocation: json['shared_location'] == null + ? null + : Location.fromJson(json['shared_location'] as Map), +); Map _$MessageToJson(Message instance) => { - 'id': instance.id, - 'text': instance.text, - if (MessageType.toJson(instance.type) case final value?) 'type': value, - 'attachments': instance.attachments.map((e) => e.toJson()).toList(), - 'mentioned_users': User.toIds(instance.mentionedUsers), - 'parent_id': instance.parentId, - 'quoted_message_id': instance.quotedMessageId, - 'show_in_channel': instance.showInChannel, - 'silent': instance.silent, - 'pinned': instance.pinned, - 'pin_expires': instance.pinExpires?.toIso8601String(), - 'poll_id': instance.pollId, - if (instance.restrictedVisibility case final value?) - 'restricted_visibility': value, - if (instance.sharedLocation?.toJson() case final value?) - 'shared_location': value, - 'extra_data': instance.extraData, - }; + 'id': instance.id, + 'text': instance.text, + if (MessageType.toJson(instance.type) case final value?) 'type': value, + 'attachments': instance.attachments.map((e) => e.toJson()).toList(), + 'mentioned_users': User.toIds(instance.mentionedUsers), + 'parent_id': instance.parentId, + 'quoted_message_id': instance.quotedMessageId, + 'show_in_channel': instance.showInChannel, + 'silent': instance.silent, + 'pinned': instance.pinned, + 'pin_expires': instance.pinExpires?.toIso8601String(), + 'poll_id': instance.pollId, + if (instance.restrictedVisibility case final value?) 'restricted_visibility': value, + if (instance.sharedLocation?.toJson() case final value?) 'shared_location': value, + 'extra_data': instance.extraData, +}; diff --git a/packages/stream_chat/lib/src/core/models/message_delete_scope.dart b/packages/stream_chat/lib/src/core/models/message_delete_scope.dart index c08f1ea01c..9a53a777bf 100644 --- a/packages/stream_chat/lib/src/core/models/message_delete_scope.dart +++ b/packages/stream_chat/lib/src/core/models/message_delete_scope.dart @@ -28,8 +28,7 @@ sealed class MessageDeleteScope with _$MessageDeleteScope { }) = DeleteForAll; /// Creates a instance of [MessageDeleteScope] from a JSON map. - factory MessageDeleteScope.fromJson(Map json) => - _$MessageDeleteScopeFromJson(json); + factory MessageDeleteScope.fromJson(Map json) => _$MessageDeleteScopeFromJson(json); // region Predefined Scopes diff --git a/packages/stream_chat/lib/src/core/models/message_delete_scope.g.dart b/packages/stream_chat/lib/src/core/models/message_delete_scope.g.dart index 0998631d2a..75f4373b42 100644 --- a/packages/stream_chat/lib/src/core/models/message_delete_scope.g.dart +++ b/packages/stream_chat/lib/src/core/models/message_delete_scope.g.dart @@ -7,21 +7,19 @@ part of 'message_delete_scope.dart'; // ************************************************************************** DeleteForMe _$DeleteForMeFromJson(Map json) => DeleteForMe( - $type: json['runtimeType'] as String?, - ); + $type: json['runtimeType'] as String?, +); -Map _$DeleteForMeToJson(DeleteForMe instance) => - { - 'runtimeType': instance.$type, - }; +Map _$DeleteForMeToJson(DeleteForMe instance) => { + 'runtimeType': instance.$type, +}; DeleteForAll _$DeleteForAllFromJson(Map json) => DeleteForAll( - hard: json['hard'] as bool? ?? false, - $type: json['runtimeType'] as String?, - ); + hard: json['hard'] as bool? ?? false, + $type: json['runtimeType'] as String?, +); -Map _$DeleteForAllToJson(DeleteForAll instance) => - { - 'hard': instance.hard, - 'runtimeType': instance.$type, - }; +Map _$DeleteForAllToJson(DeleteForAll instance) => { + 'hard': instance.hard, + 'runtimeType': instance.$type, +}; diff --git a/packages/stream_chat/lib/src/core/models/message_delivery.g.dart b/packages/stream_chat/lib/src/core/models/message_delivery.g.dart index 7a4f1431b9..96a3c10157 100644 --- a/packages/stream_chat/lib/src/core/models/message_delivery.g.dart +++ b/packages/stream_chat/lib/src/core/models/message_delivery.g.dart @@ -6,8 +6,7 @@ part of 'message_delivery.dart'; // JsonSerializableGenerator // ************************************************************************** -Map _$MessageDeliveryToJson(MessageDelivery instance) => - { - 'cid': instance.channelCid, - 'id': instance.messageId, - }; +Map _$MessageDeliveryToJson(MessageDelivery instance) => { + 'cid': instance.channelCid, + 'id': instance.messageId, +}; diff --git a/packages/stream_chat/lib/src/core/models/message_reminder.dart b/packages/stream_chat/lib/src/core/models/message_reminder.dart index d816e06148..23128f53aa 100644 --- a/packages/stream_chat/lib/src/core/models/message_reminder.dart +++ b/packages/stream_chat/lib/src/core/models/message_reminder.dart @@ -38,12 +38,11 @@ class MessageReminder extends Equatable implements ComparableFieldProvider { this.remindAt, DateTime? createdAt, DateTime? updatedAt, - }) : createdAt = createdAt ?? DateTime.now(), - updatedAt = updatedAt ?? DateTime.now(); + }) : createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json - factory MessageReminder.fromJson(Map json) => - _$MessageReminderFromJson(json); + factory MessageReminder.fromJson(Map json) => _$MessageReminderFromJson(json); /// The channel CID where the message exists. final String channelCid; @@ -124,16 +123,16 @@ class MessageReminder extends Equatable implements ComparableFieldProvider { @override List get props => [ - channelCid, - channel, - messageId, - message, - userId, - user, - remindAt, - createdAt, - updatedAt, - ]; + channelCid, + channel, + messageId, + message, + userId, + user, + remindAt, + createdAt, + updatedAt, + ]; @override ComparableField? getComparableField(String sortKey) { diff --git a/packages/stream_chat/lib/src/core/models/message_reminder.g.dart b/packages/stream_chat/lib/src/core/models/message_reminder.g.dart index 1562ace757..2df37be652 100644 --- a/packages/stream_chat/lib/src/core/models/message_reminder.g.dart +++ b/packages/stream_chat/lib/src/core/models/message_reminder.g.dart @@ -6,37 +6,23 @@ part of 'message_reminder.dart'; // JsonSerializableGenerator // ************************************************************************** -MessageReminder _$MessageReminderFromJson(Map json) => - MessageReminder( - channelCid: json['channel_cid'] as String, - channel: json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map), - messageId: json['message_id'] as String, - message: json['message'] == null - ? null - : Message.fromJson(json['message'] as Map), - userId: json['user_id'] as String, - user: json['user'] == null - ? null - : User.fromJson(json['user'] as Map), - remindAt: json['remind_at'] == null - ? null - : DateTime.parse(json['remind_at'] as String), - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - ); +MessageReminder _$MessageReminderFromJson(Map json) => MessageReminder( + channelCid: json['channel_cid'] as String, + channel: json['channel'] == null ? null : ChannelModel.fromJson(json['channel'] as Map), + messageId: json['message_id'] as String, + message: json['message'] == null ? null : Message.fromJson(json['message'] as Map), + userId: json['user_id'] as String, + user: json['user'] == null ? null : User.fromJson(json['user'] as Map), + remindAt: json['remind_at'] == null ? null : DateTime.parse(json['remind_at'] as String), + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), +); -Map _$MessageReminderToJson(MessageReminder instance) => - { - 'channel_cid': instance.channelCid, - 'message_id': instance.messageId, - 'user_id': instance.userId, - 'remind_at': instance.remindAt?.toIso8601String(), - 'created_at': instance.createdAt.toIso8601String(), - 'updated_at': instance.updatedAt.toIso8601String(), - }; +Map _$MessageReminderToJson(MessageReminder instance) => { + 'channel_cid': instance.channelCid, + 'message_id': instance.messageId, + 'user_id': instance.userId, + 'remind_at': instance.remindAt?.toIso8601String(), + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), +}; diff --git a/packages/stream_chat/lib/src/core/models/message_state.dart b/packages/stream_chat/lib/src/core/models/message_state.dart index 141bdfd1d8..bcd7f20550 100644 --- a/packages/stream_chat/lib/src/core/models/message_state.dart +++ b/packages/stream_chat/lib/src/core/models/message_state.dart @@ -147,8 +147,7 @@ extension MessageStateX on MessageState { } /// Returns true if the message is in failed deleting state. - bool get isDeletingFailed => - isSoftDeletingFailed || isHardDeletingFailed || isDeletingForMeFailed; + bool get isDeletingFailed => isSoftDeletingFailed || isHardDeletingFailed || isDeletingForMeFailed; /// Returns true if the message is in failed soft deleting state. bool get isSoftDeletingFailed { @@ -215,8 +214,7 @@ sealed class MessageState with _$MessageState { }) = MessageFailed; /// Creates a new instance from a json - factory MessageState.fromJson(Map json) => - _$MessageStateFromJson(json); + factory MessageState.fromJson(Map json) => _$MessageStateFromJson(json); // region Factory Constructors for Common States @@ -387,8 +385,7 @@ sealed class OutgoingState with _$OutgoingState { }) = Deleting; /// Creates a new instance from a json - factory OutgoingState.fromJson(Map json) => - _$OutgoingStateFromJson(json); + factory OutgoingState.fromJson(Map json) => _$OutgoingStateFromJson(json); } /// Represents the completed state of a message. @@ -406,8 +403,7 @@ sealed class CompletedState with _$CompletedState { }) = Deleted; /// Creates a new instance from a json - factory CompletedState.fromJson(Map json) => - _$CompletedStateFromJson(json); + factory CompletedState.fromJson(Map json) => _$CompletedStateFromJson(json); } /// Represents the failed state of a message. @@ -437,8 +433,7 @@ sealed class FailedState with _$FailedState { }) = DeletingFailed; /// Creates a new instance from a json - factory FailedState.fromJson(Map json) => - _$FailedStateFromJson(json); + factory FailedState.fromJson(Map json) => _$FailedStateFromJson(json); } // coverage:ignore-start @@ -759,19 +754,14 @@ extension FailedStatePatternMatching on FailedState { TResult when({ required TResult Function(bool skipPush, bool skipEnrichUrl) sendingFailed, required TResult Function(bool skipPush, bool skipEnrichUrl) updatingFailed, - required TResult Function( - Map? set, List? unset, bool skipEnrichUrl) - partialUpdatingFailed, + required TResult Function(Map? set, List? unset, bool skipEnrichUrl) partialUpdatingFailed, required TResult Function(MessageDeleteScope scope) deletingFailed, }) { final failedState = this; return switch (failedState) { - SendingFailed() => - sendingFailed(failedState.skipPush, failedState.skipEnrichUrl), - UpdatingFailed() => - updatingFailed(failedState.skipPush, failedState.skipEnrichUrl), - PartialUpdatingFailed() => partialUpdatingFailed( - failedState.set, failedState.unset, failedState.skipEnrichUrl), + SendingFailed() => sendingFailed(failedState.skipPush, failedState.skipEnrichUrl), + UpdatingFailed() => updatingFailed(failedState.skipPush, failedState.skipEnrichUrl), + PartialUpdatingFailed() => partialUpdatingFailed(failedState.set, failedState.unset, failedState.skipEnrichUrl), DeletingFailed() => deletingFailed(failedState.scope), }; } @@ -781,19 +771,14 @@ extension FailedStatePatternMatching on FailedState { TResult? whenOrNull({ TResult? Function(bool skipPush, bool skipEnrichUrl)? sendingFailed, TResult? Function(bool skipPush, bool skipEnrichUrl)? updatingFailed, - required TResult Function( - Map? set, List? unset, bool skipEnrichUrl) - partialUpdatingFailed, + required TResult Function(Map? set, List? unset, bool skipEnrichUrl) partialUpdatingFailed, TResult? Function(MessageDeleteScope scope)? deletingFailed, }) { final failedState = this; return switch (failedState) { - SendingFailed() => - sendingFailed?.call(failedState.skipPush, failedState.skipEnrichUrl), - UpdatingFailed() => - updatingFailed?.call(failedState.skipPush, failedState.skipEnrichUrl), - PartialUpdatingFailed() => partialUpdatingFailed( - failedState.set, failedState.unset, failedState.skipEnrichUrl), + SendingFailed() => sendingFailed?.call(failedState.skipPush, failedState.skipEnrichUrl), + UpdatingFailed() => updatingFailed?.call(failedState.skipPush, failedState.skipEnrichUrl), + PartialUpdatingFailed() => partialUpdatingFailed(failedState.set, failedState.unset, failedState.skipEnrichUrl), DeletingFailed() => deletingFailed?.call(failedState.scope), }; } @@ -803,20 +788,15 @@ extension FailedStatePatternMatching on FailedState { TResult maybeWhen({ TResult Function(bool skipPush, bool skipEnrichUrl)? sendingFailed, TResult Function(bool skipPush, bool skipEnrichUrl)? updatingFailed, - required TResult Function( - Map? set, List? unset, bool skipEnrichUrl) - partialUpdatingFailed, + required TResult Function(Map? set, List? unset, bool skipEnrichUrl) partialUpdatingFailed, TResult Function(MessageDeleteScope scope)? deletingFailed, required TResult orElse(), }) { final failedState = this; final result = switch (failedState) { - SendingFailed() => - sendingFailed?.call(failedState.skipPush, failedState.skipEnrichUrl), - UpdatingFailed() => - updatingFailed?.call(failedState.skipPush, failedState.skipEnrichUrl), - PartialUpdatingFailed() => partialUpdatingFailed( - failedState.set, failedState.unset, failedState.skipEnrichUrl), + SendingFailed() => sendingFailed?.call(failedState.skipPush, failedState.skipEnrichUrl), + UpdatingFailed() => updatingFailed?.call(failedState.skipPush, failedState.skipEnrichUrl), + PartialUpdatingFailed() => partialUpdatingFailed(failedState.set, failedState.unset, failedState.skipEnrichUrl), DeletingFailed() => deletingFailed?.call(failedState.scope), }; @@ -828,8 +808,7 @@ extension FailedStatePatternMatching on FailedState { TResult map({ required TResult Function(SendingFailed value) sendingFailed, required TResult Function(UpdatingFailed value) updatingFailed, - required TResult Function(PartialUpdatingFailed value) - partialUpdatingFailed, + required TResult Function(PartialUpdatingFailed value) partialUpdatingFailed, required TResult Function(DeletingFailed value) deletingFailed, }) { final failedState = this; diff --git a/packages/stream_chat/lib/src/core/models/message_state.g.dart b/packages/stream_chat/lib/src/core/models/message_state.g.dart index 693a642e17..6336fe611a 100644 --- a/packages/stream_chat/lib/src/core/models/message_state.g.dart +++ b/packages/stream_chat/lib/src/core/models/message_state.g.dart @@ -6,167 +6,148 @@ part of 'message_state.dart'; // JsonSerializableGenerator // ************************************************************************** -MessageInitial _$MessageInitialFromJson(Map json) => - MessageInitial( - $type: json['runtimeType'] as String?, - ); - -Map _$MessageInitialToJson(MessageInitial instance) => - { - 'runtimeType': instance.$type, - }; - -MessageOutgoing _$MessageOutgoingFromJson(Map json) => - MessageOutgoing( - state: OutgoingState.fromJson(json['state'] as Map), - $type: json['runtimeType'] as String?, - ); - -Map _$MessageOutgoingToJson(MessageOutgoing instance) => - { - 'state': instance.state.toJson(), - 'runtimeType': instance.$type, - }; - -MessageCompleted _$MessageCompletedFromJson(Map json) => - MessageCompleted( - state: CompletedState.fromJson(json['state'] as Map), - $type: json['runtimeType'] as String?, - ); - -Map _$MessageCompletedToJson(MessageCompleted instance) => - { - 'state': instance.state.toJson(), - 'runtimeType': instance.$type, - }; - -MessageFailed _$MessageFailedFromJson(Map json) => - MessageFailed( - state: FailedState.fromJson(json['state'] as Map), - reason: json['reason'], - $type: json['runtimeType'] as String?, - ); - -Map _$MessageFailedToJson(MessageFailed instance) => - { - 'state': instance.state.toJson(), - 'reason': instance.reason, - 'runtimeType': instance.$type, - }; +MessageInitial _$MessageInitialFromJson(Map json) => MessageInitial( + $type: json['runtimeType'] as String?, +); + +Map _$MessageInitialToJson(MessageInitial instance) => { + 'runtimeType': instance.$type, +}; + +MessageOutgoing _$MessageOutgoingFromJson(Map json) => MessageOutgoing( + state: OutgoingState.fromJson(json['state'] as Map), + $type: json['runtimeType'] as String?, +); + +Map _$MessageOutgoingToJson(MessageOutgoing instance) => { + 'state': instance.state.toJson(), + 'runtimeType': instance.$type, +}; + +MessageCompleted _$MessageCompletedFromJson(Map json) => MessageCompleted( + state: CompletedState.fromJson(json['state'] as Map), + $type: json['runtimeType'] as String?, +); + +Map _$MessageCompletedToJson(MessageCompleted instance) => { + 'state': instance.state.toJson(), + 'runtimeType': instance.$type, +}; + +MessageFailed _$MessageFailedFromJson(Map json) => MessageFailed( + state: FailedState.fromJson(json['state'] as Map), + reason: json['reason'], + $type: json['runtimeType'] as String?, +); + +Map _$MessageFailedToJson(MessageFailed instance) => { + 'state': instance.state.toJson(), + 'reason': instance.reason, + 'runtimeType': instance.$type, +}; Sending _$SendingFromJson(Map json) => Sending( - $type: json['runtimeType'] as String?, - ); + $type: json['runtimeType'] as String?, +); Map _$SendingToJson(Sending instance) => { - 'runtimeType': instance.$type, - }; + 'runtimeType': instance.$type, +}; Updating _$UpdatingFromJson(Map json) => Updating( - $type: json['runtimeType'] as String?, - ); + $type: json['runtimeType'] as String?, +); Map _$UpdatingToJson(Updating instance) => { - 'runtimeType': instance.$type, - }; + 'runtimeType': instance.$type, +}; Deleting _$DeletingFromJson(Map json) => Deleting( - scope: json['scope'] == null - ? MessageDeleteScope.softDeleteForAll - : MessageDeleteScope.fromJson(json['scope'] as Map), - $type: json['runtimeType'] as String?, - ); + scope: json['scope'] == null + ? MessageDeleteScope.softDeleteForAll + : MessageDeleteScope.fromJson(json['scope'] as Map), + $type: json['runtimeType'] as String?, +); Map _$DeletingToJson(Deleting instance) => { - 'scope': instance.scope.toJson(), - 'runtimeType': instance.$type, - }; + 'scope': instance.scope.toJson(), + 'runtimeType': instance.$type, +}; Sent _$SentFromJson(Map json) => Sent( - $type: json['runtimeType'] as String?, - ); + $type: json['runtimeType'] as String?, +); Map _$SentToJson(Sent instance) => { - 'runtimeType': instance.$type, - }; + 'runtimeType': instance.$type, +}; Updated _$UpdatedFromJson(Map json) => Updated( - $type: json['runtimeType'] as String?, - ); + $type: json['runtimeType'] as String?, +); Map _$UpdatedToJson(Updated instance) => { - 'runtimeType': instance.$type, - }; + 'runtimeType': instance.$type, +}; Deleted _$DeletedFromJson(Map json) => Deleted( - scope: json['scope'] == null - ? MessageDeleteScope.softDeleteForAll - : MessageDeleteScope.fromJson(json['scope'] as Map), - $type: json['runtimeType'] as String?, - ); + scope: json['scope'] == null + ? MessageDeleteScope.softDeleteForAll + : MessageDeleteScope.fromJson(json['scope'] as Map), + $type: json['runtimeType'] as String?, +); Map _$DeletedToJson(Deleted instance) => { - 'scope': instance.scope.toJson(), - 'runtimeType': instance.$type, - }; - -SendingFailed _$SendingFailedFromJson(Map json) => - SendingFailed( - skipPush: json['skip_push'] as bool? ?? false, - skipEnrichUrl: json['skip_enrich_url'] as bool? ?? false, - $type: json['runtimeType'] as String?, - ); - -Map _$SendingFailedToJson(SendingFailed instance) => - { - 'skip_push': instance.skipPush, - 'skip_enrich_url': instance.skipEnrichUrl, - 'runtimeType': instance.$type, - }; - -UpdatingFailed _$UpdatingFailedFromJson(Map json) => - UpdatingFailed( - skipPush: json['skip_push'] as bool? ?? false, - skipEnrichUrl: json['skip_enrich_url'] as bool? ?? false, - $type: json['runtimeType'] as String?, - ); - -Map _$UpdatingFailedToJson(UpdatingFailed instance) => - { - 'skip_push': instance.skipPush, - 'skip_enrich_url': instance.skipEnrichUrl, - 'runtimeType': instance.$type, - }; - -PartialUpdatingFailed _$PartialUpdatingFailedFromJson( - Map json) => - PartialUpdatingFailed( - set: json['set'] as Map?, - unset: - (json['unset'] as List?)?.map((e) => e as String).toList(), - skipEnrichUrl: json['skip_enrich_url'] as bool? ?? false, - $type: json['runtimeType'] as String?, - ); - -Map _$PartialUpdatingFailedToJson( - PartialUpdatingFailed instance) => - { - 'set': instance.set, - 'unset': instance.unset, - 'skip_enrich_url': instance.skipEnrichUrl, - 'runtimeType': instance.$type, - }; - -DeletingFailed _$DeletingFailedFromJson(Map json) => - DeletingFailed( - scope: json['scope'] == null - ? MessageDeleteScope.softDeleteForAll - : MessageDeleteScope.fromJson(json['scope'] as Map), - $type: json['runtimeType'] as String?, - ); - -Map _$DeletingFailedToJson(DeletingFailed instance) => - { - 'scope': instance.scope.toJson(), - 'runtimeType': instance.$type, - }; + 'scope': instance.scope.toJson(), + 'runtimeType': instance.$type, +}; + +SendingFailed _$SendingFailedFromJson(Map json) => SendingFailed( + skipPush: json['skip_push'] as bool? ?? false, + skipEnrichUrl: json['skip_enrich_url'] as bool? ?? false, + $type: json['runtimeType'] as String?, +); + +Map _$SendingFailedToJson(SendingFailed instance) => { + 'skip_push': instance.skipPush, + 'skip_enrich_url': instance.skipEnrichUrl, + 'runtimeType': instance.$type, +}; + +UpdatingFailed _$UpdatingFailedFromJson(Map json) => UpdatingFailed( + skipPush: json['skip_push'] as bool? ?? false, + skipEnrichUrl: json['skip_enrich_url'] as bool? ?? false, + $type: json['runtimeType'] as String?, +); + +Map _$UpdatingFailedToJson(UpdatingFailed instance) => { + 'skip_push': instance.skipPush, + 'skip_enrich_url': instance.skipEnrichUrl, + 'runtimeType': instance.$type, +}; + +PartialUpdatingFailed _$PartialUpdatingFailedFromJson(Map json) => PartialUpdatingFailed( + set: json['set'] as Map?, + unset: (json['unset'] as List?)?.map((e) => e as String).toList(), + skipEnrichUrl: json['skip_enrich_url'] as bool? ?? false, + $type: json['runtimeType'] as String?, +); + +Map _$PartialUpdatingFailedToJson(PartialUpdatingFailed instance) => { + 'set': instance.set, + 'unset': instance.unset, + 'skip_enrich_url': instance.skipEnrichUrl, + 'runtimeType': instance.$type, +}; + +DeletingFailed _$DeletingFailedFromJson(Map json) => DeletingFailed( + scope: json['scope'] == null + ? MessageDeleteScope.softDeleteForAll + : MessageDeleteScope.fromJson(json['scope'] as Map), + $type: json['runtimeType'] as String?, +); + +Map _$DeletingFailedToJson(DeletingFailed instance) => { + 'scope': instance.scope.toJson(), + 'runtimeType': instance.$type, +}; diff --git a/packages/stream_chat/lib/src/core/models/moderation.dart b/packages/stream_chat/lib/src/core/models/moderation.dart index e267afc7de..f3133a39a6 100644 --- a/packages/stream_chat/lib/src/core/models/moderation.dart +++ b/packages/stream_chat/lib/src/core/models/moderation.dart @@ -20,8 +20,7 @@ class Moderation extends Equatable { }); /// Create a new instance from a json - factory Moderation.fromJson(Map json) => - _$ModerationFromJson(json); + factory Moderation.fromJson(Map json) => _$ModerationFromJson(json); /// The action taken by the moderation system. @JsonKey( @@ -53,14 +52,14 @@ class Moderation extends Equatable { @override List get props => [ - action, - originalText, - textHarms, - imageHarms, - blocklistMatched, - semanticFilterMatched, - platformCircumvented, - ]; + action, + originalText, + textHarms, + imageHarms, + blocklistMatched, + semanticFilterMatched, + platformCircumvented, + ]; } /// The moderation action performed over the message. diff --git a/packages/stream_chat/lib/src/core/models/moderation.g.dart b/packages/stream_chat/lib/src/core/models/moderation.g.dart index 6f8ef841c1..f287d23d84 100644 --- a/packages/stream_chat/lib/src/core/models/moderation.g.dart +++ b/packages/stream_chat/lib/src/core/models/moderation.g.dart @@ -7,26 +7,21 @@ part of 'moderation.dart'; // ************************************************************************** Moderation _$ModerationFromJson(Map json) => Moderation( - action: ModerationAction.fromJson(json['action'] as String), - originalText: json['original_text'] as String, - textHarms: (json['text_harms'] as List?) - ?.map((e) => e as String) - .toList(), - imageHarms: (json['image_harms'] as List?) - ?.map((e) => e as String) - .toList(), - blocklistMatched: json['blocklist_matched'] as String?, - semanticFilterMatched: json['semantic_filter_matched'] as String?, - platformCircumvented: json['platform_circumvented'] as bool? ?? false, - ); + action: ModerationAction.fromJson(json['action'] as String), + originalText: json['original_text'] as String, + textHarms: (json['text_harms'] as List?)?.map((e) => e as String).toList(), + imageHarms: (json['image_harms'] as List?)?.map((e) => e as String).toList(), + blocklistMatched: json['blocklist_matched'] as String?, + semanticFilterMatched: json['semantic_filter_matched'] as String?, + platformCircumvented: json['platform_circumvented'] as bool? ?? false, +); -Map _$ModerationToJson(Moderation instance) => - { - 'action': ModerationAction.toJson(instance.action), - 'original_text': instance.originalText, - 'text_harms': instance.textHarms, - 'image_harms': instance.imageHarms, - 'blocklist_matched': instance.blocklistMatched, - 'semantic_filter_matched': instance.semanticFilterMatched, - 'platform_circumvented': instance.platformCircumvented, - }; +Map _$ModerationToJson(Moderation instance) => { + 'action': ModerationAction.toJson(instance.action), + 'original_text': instance.originalText, + 'text_harms': instance.textHarms, + 'image_harms': instance.imageHarms, + 'blocklist_matched': instance.blocklistMatched, + 'semantic_filter_matched': instance.semanticFilterMatched, + 'platform_circumvented': instance.platformCircumvented, +}; diff --git a/packages/stream_chat/lib/src/core/models/mute.g.dart b/packages/stream_chat/lib/src/core/models/mute.g.dart index 624df9cb70..b1e3dc7aac 100644 --- a/packages/stream_chat/lib/src/core/models/mute.g.dart +++ b/packages/stream_chat/lib/src/core/models/mute.g.dart @@ -7,19 +7,17 @@ part of 'mute.dart'; // ************************************************************************** Mute _$MuteFromJson(Map json) => Mute( - user: User.fromJson(json['user'] as Map), - target: User.fromJson(json['target'] as Map), - createdAt: DateTime.parse(json['created_at'] as String), - updatedAt: DateTime.parse(json['updated_at'] as String), - expires: json['expires'] == null - ? null - : DateTime.parse(json['expires'] as String), - ); + user: User.fromJson(json['user'] as Map), + target: User.fromJson(json['target'] as Map), + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + expires: json['expires'] == null ? null : DateTime.parse(json['expires'] as String), +); Map _$MuteToJson(Mute instance) => { - 'user': instance.user.toJson(), - 'target': instance.target.toJson(), - 'created_at': instance.createdAt.toIso8601String(), - 'updated_at': instance.updatedAt.toIso8601String(), - 'expires': instance.expires?.toIso8601String(), - }; + 'user': instance.user.toJson(), + 'target': instance.target.toJson(), + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'expires': instance.expires?.toIso8601String(), +}; diff --git a/packages/stream_chat/lib/src/core/models/own_user.dart b/packages/stream_chat/lib/src/core/models/own_user.dart index c685761937..e3e8b83b9b 100644 --- a/packages/stream_chat/lib/src/core/models/own_user.dart +++ b/packages/stream_chat/lib/src/core/models/own_user.dart @@ -40,8 +40,8 @@ class OwnUser extends User { /// Create a new instance from json. factory OwnUser.fromJson(Map json) => _$OwnUserFromJson( - Serializer.moveToExtraDataFromRoot(json, topLevelFields), - ); + Serializer.moveToExtraDataFromRoot(json, topLevelFields), + ); /// Create a new instance from [User] object. factory OwnUser.fromUser(User user) => OwnUser.fromJson(user.toJson()); @@ -74,37 +74,37 @@ class OwnUser extends User { int? avgResponseTime, PushPreference? pushPreferences, PrivacySettings? privacySettings, - }) => - OwnUser( - id: id ?? this.id, - role: role ?? this.role, - name: name ?? - extraData?['name'] as String? ?? - // Using extraData value in order to not use id as name. - this.extraData['name'] as String?, - image: image ?? extraData?['image'] as String? ?? this.image, - banned: banned ?? this.banned, - banExpires: banExpires ?? this.banExpires, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - lastActive: lastActive ?? this.lastActive, - online: online ?? this.online, - extraData: extraData ?? this.extraData, - teams: teams ?? this.teams, - channelMutes: channelMutes ?? this.channelMutes, - devices: devices ?? this.devices, - mutes: mutes ?? this.mutes, - totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount, - unreadChannels: unreadChannels ?? this.unreadChannels, - unreadThreads: unreadThreads ?? this.unreadThreads, - blockedUserIds: blockedUserIds ?? this.blockedUserIds, - language: language ?? this.language, - invisible: invisible ?? this.invisible, - teamsRole: teamsRole ?? this.teamsRole, - avgResponseTime: avgResponseTime ?? this.avgResponseTime, - pushPreferences: pushPreferences ?? this.pushPreferences, - privacySettings: privacySettings ?? this.privacySettings, - ); + }) => OwnUser( + id: id ?? this.id, + role: role ?? this.role, + name: + name ?? + extraData?['name'] as String? ?? + // Using extraData value in order to not use id as name. + this.extraData['name'] as String?, + image: image ?? extraData?['image'] as String? ?? this.image, + banned: banned ?? this.banned, + banExpires: banExpires ?? this.banExpires, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + lastActive: lastActive ?? this.lastActive, + online: online ?? this.online, + extraData: extraData ?? this.extraData, + teams: teams ?? this.teams, + channelMutes: channelMutes ?? this.channelMutes, + devices: devices ?? this.devices, + mutes: mutes ?? this.mutes, + totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount, + unreadChannels: unreadChannels ?? this.unreadChannels, + unreadThreads: unreadThreads ?? this.unreadThreads, + blockedUserIds: blockedUserIds ?? this.blockedUserIds, + language: language ?? this.language, + invisible: invisible ?? this.invisible, + teamsRole: teamsRole ?? this.teamsRole, + avgResponseTime: avgResponseTime ?? this.avgResponseTime, + pushPreferences: pushPreferences ?? this.pushPreferences, + privacySettings: privacySettings ?? this.privacySettings, + ); /// Returns a new [OwnUser] that is a combination of this ownUser /// and the given [other] ownUser. diff --git a/packages/stream_chat/lib/src/core/models/own_user.g.dart b/packages/stream_chat/lib/src/core/models/own_user.g.dart index 21fc564e29..d8e69df5dd 100644 --- a/packages/stream_chat/lib/src/core/models/own_user.g.dart +++ b/packages/stream_chat/lib/src/core/models/own_user.g.dart @@ -7,90 +7,62 @@ part of 'own_user.dart'; // ************************************************************************** OwnUser _$OwnUserFromJson(Map json) => OwnUser( - devices: (json['devices'] as List?) - ?.map((e) => Device.fromJson(e as Map)) - .toList() ?? - const [], - mutes: (json['mutes'] as List?) - ?.map((e) => Mute.fromJson(e as Map)) - .toList() ?? - const [], - totalUnreadCount: (json['total_unread_count'] as num?)?.toInt() ?? 0, - unreadChannels: (json['unread_channels'] as num?)?.toInt() ?? 0, - channelMutes: (json['channel_mutes'] as List?) - ?.map((e) => ChannelMute.fromJson(e as Map)) - .toList() ?? - const [], - unreadThreads: (json['unread_threads'] as num?)?.toInt() ?? 0, - blockedUserIds: (json['blocked_user_ids'] as List?) - ?.map((e) => e as String) - .toList() ?? - const [], - pushPreferences: json['push_preferences'] == null - ? null - : PushPreference.fromJson( - json['push_preferences'] as Map), - privacySettings: json['privacy_settings'] == null - ? null - : PrivacySettings.fromJson( - json['privacy_settings'] as Map), - id: json['id'] as String, - role: json['role'] as String?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - lastActive: json['last_active'] == null - ? null - : DateTime.parse(json['last_active'] as String), - online: json['online'] as bool? ?? false, - extraData: json['extra_data'] as Map? ?? const {}, - banned: json['banned'] as bool? ?? false, - banExpires: json['ban_expires'] == null - ? null - : DateTime.parse(json['ban_expires'] as String), - teams: - (json['teams'] as List?)?.map((e) => e as String).toList() ?? - const [], - language: json['language'] as String?, - invisible: json['invisible'] as bool?, - teamsRole: (json['teams_role'] as Map?)?.map( - (k, e) => MapEntry(k, e as String), - ), - avgResponseTime: (json['avg_response_time'] as num?)?.toInt(), - ); + devices: + (json['devices'] as List?)?.map((e) => Device.fromJson(e as Map)).toList() ?? const [], + mutes: (json['mutes'] as List?)?.map((e) => Mute.fromJson(e as Map)).toList() ?? const [], + totalUnreadCount: (json['total_unread_count'] as num?)?.toInt() ?? 0, + unreadChannels: (json['unread_channels'] as num?)?.toInt() ?? 0, + channelMutes: + (json['channel_mutes'] as List?)?.map((e) => ChannelMute.fromJson(e as Map)).toList() ?? + const [], + unreadThreads: (json['unread_threads'] as num?)?.toInt() ?? 0, + blockedUserIds: (json['blocked_user_ids'] as List?)?.map((e) => e as String).toList() ?? const [], + pushPreferences: json['push_preferences'] == null + ? null + : PushPreference.fromJson(json['push_preferences'] as Map), + privacySettings: json['privacy_settings'] == null + ? null + : PrivacySettings.fromJson(json['privacy_settings'] as Map), + id: json['id'] as String, + role: json['role'] as String?, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + lastActive: json['last_active'] == null ? null : DateTime.parse(json['last_active'] as String), + online: json['online'] as bool? ?? false, + extraData: json['extra_data'] as Map? ?? const {}, + banned: json['banned'] as bool? ?? false, + banExpires: json['ban_expires'] == null ? null : DateTime.parse(json['ban_expires'] as String), + teams: (json['teams'] as List?)?.map((e) => e as String).toList() ?? const [], + language: json['language'] as String?, + invisible: json['invisible'] as bool?, + teamsRole: (json['teams_role'] as Map?)?.map( + (k, e) => MapEntry(k, e as String), + ), + avgResponseTime: (json['avg_response_time'] as num?)?.toInt(), +); Map _$OwnUserToJson(OwnUser instance) => { - 'id': instance.id, - if (instance.role case final value?) 'role': value, - 'teams': instance.teams, - if (instance.createdAt?.toIso8601String() case final value?) - 'created_at': value, - if (instance.updatedAt?.toIso8601String() case final value?) - 'updated_at': value, - if (instance.lastActive?.toIso8601String() case final value?) - 'last_active': value, - 'online': instance.online, - 'banned': instance.banned, - if (instance.banExpires?.toIso8601String() case final value?) - 'ban_expires': value, - if (instance.language case final value?) 'language': value, - if (instance.invisible case final value?) 'invisible': value, - if (instance.teamsRole case final value?) 'teams_role': value, - if (instance.avgResponseTime case final value?) - 'avg_response_time': value, - 'extra_data': instance.extraData, - 'devices': instance.devices.map((e) => e.toJson()).toList(), - 'mutes': instance.mutes.map((e) => e.toJson()).toList(), - 'channel_mutes': instance.channelMutes.map((e) => e.toJson()).toList(), - 'total_unread_count': instance.totalUnreadCount, - 'unread_channels': instance.unreadChannels, - 'unread_threads': instance.unreadThreads, - 'blocked_user_ids': instance.blockedUserIds, - if (instance.pushPreferences?.toJson() case final value?) - 'push_preferences': value, - if (instance.privacySettings?.toJson() case final value?) - 'privacy_settings': value, - }; + 'id': instance.id, + if (instance.role case final value?) 'role': value, + 'teams': instance.teams, + if (instance.createdAt?.toIso8601String() case final value?) 'created_at': value, + if (instance.updatedAt?.toIso8601String() case final value?) 'updated_at': value, + if (instance.lastActive?.toIso8601String() case final value?) 'last_active': value, + 'online': instance.online, + 'banned': instance.banned, + if (instance.banExpires?.toIso8601String() case final value?) 'ban_expires': value, + if (instance.language case final value?) 'language': value, + if (instance.invisible case final value?) 'invisible': value, + if (instance.teamsRole case final value?) 'teams_role': value, + if (instance.avgResponseTime case final value?) 'avg_response_time': value, + 'extra_data': instance.extraData, + 'devices': instance.devices.map((e) => e.toJson()).toList(), + 'mutes': instance.mutes.map((e) => e.toJson()).toList(), + 'channel_mutes': instance.channelMutes.map((e) => e.toJson()).toList(), + 'total_unread_count': instance.totalUnreadCount, + 'unread_channels': instance.unreadChannels, + 'unread_threads': instance.unreadThreads, + 'blocked_user_ids': instance.blockedUserIds, + if (instance.pushPreferences?.toJson() case final value?) 'push_preferences': value, + if (instance.privacySettings?.toJson() case final value?) 'privacy_settings': value, +}; diff --git a/packages/stream_chat/lib/src/core/models/poll.dart b/packages/stream_chat/lib/src/core/models/poll.dart index dd61feac2b..a0fb4bfc1b 100644 --- a/packages/stream_chat/lib/src/core/models/poll.dart +++ b/packages/stream_chat/lib/src/core/models/poll.dart @@ -57,9 +57,9 @@ class Poll extends Equatable implements ComparableFieldProvider { this.createdBy, this.ownVotesAndAnswers = const [], this.extraData = const {}, - }) : id = id ?? const Uuid().v4(), - createdAt = createdAt ?? DateTime.now(), - updatedAt = updatedAt ?? DateTime.now(); + }) : id = id ?? const Uuid().v4(), + createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json factory Poll.fromJson(Map json) => @@ -167,8 +167,7 @@ class Poll extends Equatable implements ComparableFieldProvider { final Map extraData; /// Serialize to json - Map toJson() => - Serializer.moveFromExtraDataToRoot(_$PollToJson(this)); + Map toJson() => Serializer.moveFromExtraDataToRoot(_$PollToJson(this)); /// Creates a copy of [Poll] with specified attributes overridden. Poll copyWith({ @@ -193,33 +192,29 @@ class Poll extends Equatable implements ComparableFieldProvider { DateTime? createdAt, DateTime? updatedAt, Map? extraData, - }) => - Poll( - id: id ?? this.id, - name: name ?? this.name, - description: description ?? this.description, - options: options ?? this.options, - votingVisibility: votingVisibility ?? this.votingVisibility, - enforceUniqueVote: enforceUniqueVote ?? this.enforceUniqueVote, - maxVotesAllowed: maxVotesAllowed == _nullConst - ? this.maxVotesAllowed - : maxVotesAllowed as int?, - allowUserSuggestedOptions: - allowUserSuggestedOptions ?? this.allowUserSuggestedOptions, - allowAnswers: allowAnswers ?? this.allowAnswers, - isClosed: isClosed ?? this.isClosed, - voteCountsByOption: voteCountsByOption ?? this.voteCountsByOption, - ownVotesAndAnswers: ownVotesAndAnswers ?? this.ownVotesAndAnswers, - voteCount: voteCount ?? this.voteCount, - answersCount: answersCount ?? this.answersCount, - latestVotesByOption: latestVotesByOption ?? this.latestVotesByOption, - latestAnswers: latestAnswers ?? this.latestAnswers, - createdById: createdById ?? this.createdById, - createdBy: createdBy ?? this.createdBy, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - extraData: extraData ?? this.extraData, - ); + }) => Poll( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + options: options ?? this.options, + votingVisibility: votingVisibility ?? this.votingVisibility, + enforceUniqueVote: enforceUniqueVote ?? this.enforceUniqueVote, + maxVotesAllowed: maxVotesAllowed == _nullConst ? this.maxVotesAllowed : maxVotesAllowed as int?, + allowUserSuggestedOptions: allowUserSuggestedOptions ?? this.allowUserSuggestedOptions, + allowAnswers: allowAnswers ?? this.allowAnswers, + isClosed: isClosed ?? this.isClosed, + voteCountsByOption: voteCountsByOption ?? this.voteCountsByOption, + ownVotesAndAnswers: ownVotesAndAnswers ?? this.ownVotesAndAnswers, + voteCount: voteCount ?? this.voteCount, + answersCount: answersCount ?? this.answersCount, + latestVotesByOption: latestVotesByOption ?? this.latestVotesByOption, + latestAnswers: latestAnswers ?? this.latestAnswers, + createdById: createdById ?? this.createdById, + createdBy: createdBy ?? this.createdBy, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + extraData: extraData ?? this.extraData, + ); /// Known top level fields. /// @@ -250,27 +245,27 @@ class Poll extends Equatable implements ComparableFieldProvider { @override List get props => [ - id, - name, - description, - options, - votingVisibility, - enforceUniqueVote, - maxVotesAllowed, - allowUserSuggestedOptions, - allowAnswers, - isClosed, - voteCountsByOption, - ownVotesAndAnswers, - voteCount, - answersCount, - latestVotesByOption, - latestAnswers, - createdById, - createdBy, - createdAt, - updatedAt, - ]; + id, + name, + description, + options, + votingVisibility, + enforceUniqueVote, + maxVotesAllowed, + allowUserSuggestedOptions, + allowAnswers, + isClosed, + voteCountsByOption, + ownVotesAndAnswers, + voteCount, + answersCount, + latestVotesByOption, + latestAnswers, + createdById, + createdBy, + createdAt, + updatedAt, + ]; @override ComparableField? getComparableField(String sortKey) { @@ -319,31 +314,28 @@ extension PollX on Poll { /// Whether the poll is already closed and the provided option is the one, /// and **the only one** with the most votes. - bool isOptionWinner(PollOption option) => - isClosed && isOptionWithMostVotes(option); + bool isOptionWinner(PollOption option) => isClosed && isOptionWithMostVotes(option); /// Whether the poll is already closed and the provided option is one of that /// has the most votes. - bool isOptionOneOfTheWinners(PollOption option) => - isClosed && isOptionWithMaximumVotes(option); + bool isOptionOneOfTheWinners(PollOption option) => isClosed && isOptionWithMaximumVotes(option); /// Whether the provided option is the one, and **the only one** with the most /// votes. bool isOptionWithMostVotes(PollOption option) { final optionsWithMostVotes = { for (final entry in voteCountsByOption.entries) - if (entry.value == currentMaximumVoteCount) entry.key: entry.value + if (entry.value == currentMaximumVoteCount) entry.key: entry.value, }; - return optionsWithMostVotes.length == 1 && - optionsWithMostVotes[option.id] != null; + return optionsWithMostVotes.length == 1 && optionsWithMostVotes[option.id] != null; } /// Whether the provided option is one of that has the most votes. bool isOptionWithMaximumVotes(PollOption option) { final optionsWithMostVotes = { for (final entry in voteCountsByOption.entries) - if (entry.value == currentMaximumVoteCount) entry.key: entry.value + if (entry.value == currentMaximumVoteCount) entry.key: entry.value, }; return optionsWithMostVotes[option.id] != null; @@ -368,6 +360,5 @@ extension PollX on Poll { /// Returns a Boolean value indicating whether the current user has voted the /// given option. - bool hasCurrentUserVotedFor(PollOption option) => - ownVotesAndAnswers.any((it) => it.optionId == option.id); + bool hasCurrentUserVotedFor(PollOption option) => ownVotesAndAnswers.any((it) => it.optionId == option.id); } diff --git a/packages/stream_chat/lib/src/core/models/poll.g.dart b/packages/stream_chat/lib/src/core/models/poll.g.dart index 475232c4cc..3a4432041e 100644 --- a/packages/stream_chat/lib/src/core/models/poll.g.dart +++ b/packages/stream_chat/lib/src/core/models/poll.g.dart @@ -7,73 +7,55 @@ part of 'poll.dart'; // ************************************************************************** Poll _$PollFromJson(Map json) => Poll( - id: json['id'] as String?, - name: json['name'] as String, - description: json['description'] as String?, - options: (json['options'] as List) - .map((e) => PollOption.fromJson(e as Map)) - .toList(), - votingVisibility: $enumDecodeNullable( - _$VotingVisibilityEnumMap, json['voting_visibility']) ?? - VotingVisibility.public, - enforceUniqueVote: json['enforce_unique_vote'] as bool? ?? true, - maxVotesAllowed: (json['max_votes_allowed'] as num?)?.toInt(), - allowAnswers: json['allow_answers'] as bool? ?? false, - latestAnswers: (json['latest_answers'] as List?) - ?.map((e) => PollVote.fromJson(e as Map)) - .toList() ?? - const [], - answersCount: (json['answers_count'] as num?)?.toInt() ?? 0, - allowUserSuggestedOptions: - json['allow_user_suggested_options'] as bool? ?? false, - isClosed: json['is_closed'] as bool? ?? false, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - voteCountsByOption: - (json['vote_counts_by_option'] as Map?)?.map( - (k, e) => MapEntry(k, (e as num).toInt()), - ) ?? - const {}, - voteCount: (json['vote_count'] as num?)?.toInt() ?? 0, - latestVotesByOption: - (json['latest_votes_by_option'] as Map?)?.map( - (k, e) => MapEntry( - k, - (e as List) - .map( - (e) => PollVote.fromJson(e as Map)) - .toList()), - ) ?? - const {}, - createdById: json['created_by_id'] as String?, - createdBy: json['created_by'] == null - ? null - : User.fromJson(json['created_by'] as Map), - ownVotesAndAnswers: (json['own_votes'] as List?) - ?.map((e) => PollVote.fromJson(e as Map)) - .toList() ?? - const [], - extraData: json['extra_data'] as Map? ?? const {}, - ); + id: json['id'] as String?, + name: json['name'] as String, + description: json['description'] as String?, + options: (json['options'] as List).map((e) => PollOption.fromJson(e as Map)).toList(), + votingVisibility: + $enumDecodeNullable(_$VotingVisibilityEnumMap, json['voting_visibility']) ?? VotingVisibility.public, + enforceUniqueVote: json['enforce_unique_vote'] as bool? ?? true, + maxVotesAllowed: (json['max_votes_allowed'] as num?)?.toInt(), + allowAnswers: json['allow_answers'] as bool? ?? false, + latestAnswers: + (json['latest_answers'] as List?)?.map((e) => PollVote.fromJson(e as Map)).toList() ?? + const [], + answersCount: (json['answers_count'] as num?)?.toInt() ?? 0, + allowUserSuggestedOptions: json['allow_user_suggested_options'] as bool? ?? false, + isClosed: json['is_closed'] as bool? ?? false, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + voteCountsByOption: + (json['vote_counts_by_option'] as Map?)?.map( + (k, e) => MapEntry(k, (e as num).toInt()), + ) ?? + const {}, + voteCount: (json['vote_count'] as num?)?.toInt() ?? 0, + latestVotesByOption: + (json['latest_votes_by_option'] as Map?)?.map( + (k, e) => MapEntry(k, (e as List).map((e) => PollVote.fromJson(e as Map)).toList()), + ) ?? + const {}, + createdById: json['created_by_id'] as String?, + createdBy: json['created_by'] == null ? null : User.fromJson(json['created_by'] as Map), + ownVotesAndAnswers: + (json['own_votes'] as List?)?.map((e) => PollVote.fromJson(e as Map)).toList() ?? + const [], + extraData: json['extra_data'] as Map? ?? const {}, +); Map _$PollToJson(Poll instance) => { - 'id': instance.id, - 'name': instance.name, - 'description': instance.description, - 'options': instance.options.map((e) => e.toJson()).toList(), - 'voting_visibility': - _$VotingVisibilityEnumMap[instance.votingVisibility]!, - 'enforce_unique_vote': instance.enforceUniqueVote, - 'max_votes_allowed': instance.maxVotesAllowed, - 'allow_user_suggested_options': instance.allowUserSuggestedOptions, - 'allow_answers': instance.allowAnswers, - 'is_closed': instance.isClosed, - 'extra_data': instance.extraData, - }; + 'id': instance.id, + 'name': instance.name, + 'description': instance.description, + 'options': instance.options.map((e) => e.toJson()).toList(), + 'voting_visibility': _$VotingVisibilityEnumMap[instance.votingVisibility]!, + 'enforce_unique_vote': instance.enforceUniqueVote, + 'max_votes_allowed': instance.maxVotesAllowed, + 'allow_user_suggested_options': instance.allowUserSuggestedOptions, + 'allow_answers': instance.allowAnswers, + 'is_closed': instance.isClosed, + 'extra_data': instance.extraData, +}; const _$VotingVisibilityEnumMap = { VotingVisibility.anonymous: 'anonymous', diff --git a/packages/stream_chat/lib/src/core/models/poll_option.dart b/packages/stream_chat/lib/src/core/models/poll_option.dart index 9ef99d07e3..fb8448af05 100644 --- a/packages/stream_chat/lib/src/core/models/poll_option.dart +++ b/packages/stream_chat/lib/src/core/models/poll_option.dart @@ -23,10 +23,9 @@ class PollOption extends Equatable { }); /// Create a new instance from a json - factory PollOption.fromJson(Map json) => - _$PollOptionFromJson( - Serializer.moveToExtraDataFromRoot(json, topLevelFields), - ); + factory PollOption.fromJson(Map json) => _$PollOptionFromJson( + Serializer.moveToExtraDataFromRoot(json, topLevelFields), + ); /// The unique identifier of the poll option. @JsonKey(includeIfNull: false) @@ -39,20 +38,18 @@ class PollOption extends Equatable { final Map extraData; /// Serialize to json - Map toJson() => - Serializer.moveFromExtraDataToRoot(_$PollOptionToJson(this)); + Map toJson() => Serializer.moveFromExtraDataToRoot(_$PollOptionToJson(this)); /// Creates a copy of [PollOption] with specified attributes overridden. PollOption copyWith({ Object? id = _nullConst, String? text, Map? extraData, - }) => - PollOption( - id: id == _nullConst ? this.id : id as String?, - text: text ?? this.text, - extraData: extraData ?? this.extraData, - ); + }) => PollOption( + id: id == _nullConst ? this.id : id as String?, + text: text ?? this.text, + extraData: extraData ?? this.extraData, + ); /// Known top level fields. /// diff --git a/packages/stream_chat/lib/src/core/models/poll_option.g.dart b/packages/stream_chat/lib/src/core/models/poll_option.g.dart index b7db997113..1f8ba16aa2 100644 --- a/packages/stream_chat/lib/src/core/models/poll_option.g.dart +++ b/packages/stream_chat/lib/src/core/models/poll_option.g.dart @@ -7,14 +7,13 @@ part of 'poll_option.dart'; // ************************************************************************** PollOption _$PollOptionFromJson(Map json) => PollOption( - id: json['id'] as String?, - text: json['text'] as String, - extraData: json['extra_data'] as Map? ?? const {}, - ); + id: json['id'] as String?, + text: json['text'] as String, + extraData: json['extra_data'] as Map? ?? const {}, +); -Map _$PollOptionToJson(PollOption instance) => - { - if (instance.id case final value?) 'id': value, - 'text': instance.text, - 'extra_data': instance.extraData, - }; +Map _$PollOptionToJson(PollOption instance) => { + if (instance.id case final value?) 'id': value, + 'text': instance.text, + 'extra_data': instance.extraData, +}; diff --git a/packages/stream_chat/lib/src/core/models/poll_vote.dart b/packages/stream_chat/lib/src/core/models/poll_vote.dart index a48bb03d9e..3f979cc13c 100644 --- a/packages/stream_chat/lib/src/core/models/poll_vote.dart +++ b/packages/stream_chat/lib/src/core/models/poll_vote.dart @@ -20,17 +20,16 @@ class PollVote extends Equatable implements ComparableFieldProvider { DateTime? updatedAt, this.userId, this.user, - }) : assert( - optionId != null || answerText != null, - 'Either optionId or answerText must be provided', - ), - isAnswer = answerText != null, - createdAt = createdAt ?? DateTime.now(), - updatedAt = updatedAt ?? DateTime.now(); + }) : assert( + optionId != null || answerText != null, + 'Either optionId or answerText must be provided', + ), + isAnswer = answerText != null, + createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json - factory PollVote.fromJson(Map json) => - _$PollVoteFromJson(json); + factory PollVote.fromJson(Map json) => _$PollVoteFromJson(json); /// The unique identifier of the poll vote. @JsonKey(includeIfNull: false) @@ -81,30 +80,29 @@ class PollVote extends Equatable implements ComparableFieldProvider { DateTime? updatedAt, String? userId, User? user, - }) => - PollVote( - id: id ?? this.id, - pollId: pollId ?? this.pollId, - optionId: optionId ?? this.optionId, - answerText: answerText ?? this.answerText, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - userId: userId ?? this.userId, - user: user ?? this.user, - ); + }) => PollVote( + id: id ?? this.id, + pollId: pollId ?? this.pollId, + optionId: optionId ?? this.optionId, + answerText: answerText ?? this.answerText, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + userId: userId ?? this.userId, + user: user ?? this.user, + ); @override List get props => [ - id, - pollId, - optionId, - isAnswer, - answerText, - createdAt, - updatedAt, - userId, - user, - ]; + id, + pollId, + optionId, + isAnswer, + answerText, + createdAt, + updatedAt, + userId, + user, + ]; @override ComparableField? getComparableField(String sortKey) { diff --git a/packages/stream_chat/lib/src/core/models/poll_vote.g.dart b/packages/stream_chat/lib/src/core/models/poll_vote.g.dart index 1ceb22c1d8..8eedbb3061 100644 --- a/packages/stream_chat/lib/src/core/models/poll_vote.g.dart +++ b/packages/stream_chat/lib/src/core/models/poll_vote.g.dart @@ -7,24 +7,18 @@ part of 'poll_vote.dart'; // ************************************************************************** PollVote _$PollVoteFromJson(Map json) => PollVote( - id: json['id'] as String?, - pollId: json['poll_id'] as String?, - optionId: json['option_id'] as String?, - answerText: json['answer_text'] as String?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - userId: json['user_id'] as String?, - user: json['user'] == null - ? null - : User.fromJson(json['user'] as Map), - ); + id: json['id'] as String?, + pollId: json['poll_id'] as String?, + optionId: json['option_id'] as String?, + answerText: json['answer_text'] as String?, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + userId: json['user_id'] as String?, + user: json['user'] == null ? null : User.fromJson(json['user'] as Map), +); Map _$PollVoteToJson(PollVote instance) => { - if (instance.id case final value?) 'id': value, - if (instance.optionId case final value?) 'option_id': value, - if (instance.answerText case final value?) 'answer_text': value, - }; + if (instance.id case final value?) 'id': value, + if (instance.optionId case final value?) 'option_id': value, + if (instance.answerText case final value?) 'answer_text': value, +}; diff --git a/packages/stream_chat/lib/src/core/models/privacy_settings.g.dart b/packages/stream_chat/lib/src/core/models/privacy_settings.g.dart index e433675833..b888255ad6 100644 --- a/packages/stream_chat/lib/src/core/models/privacy_settings.g.dart +++ b/packages/stream_chat/lib/src/core/models/privacy_settings.g.dart @@ -6,57 +6,44 @@ part of 'privacy_settings.dart'; // JsonSerializableGenerator // ************************************************************************** -PrivacySettings _$PrivacySettingsFromJson(Map json) => - PrivacySettings( - typingIndicators: json['typing_indicators'] == null - ? null - : TypingIndicators.fromJson( - json['typing_indicators'] as Map), - readReceipts: json['read_receipts'] == null - ? null - : ReadReceipts.fromJson( - json['read_receipts'] as Map), - deliveryReceipts: json['delivery_receipts'] == null - ? null - : DeliveryReceipts.fromJson( - json['delivery_receipts'] as Map), - ); - -Map _$PrivacySettingsToJson(PrivacySettings instance) => - { - if (instance.typingIndicators?.toJson() case final value?) - 'typing_indicators': value, - if (instance.readReceipts?.toJson() case final value?) - 'read_receipts': value, - if (instance.deliveryReceipts?.toJson() case final value?) - 'delivery_receipts': value, - }; - -TypingIndicators _$TypingIndicatorsFromJson(Map json) => - TypingIndicators( - enabled: json['enabled'] as bool? ?? true, - ); - -Map _$TypingIndicatorsToJson(TypingIndicators instance) => - { - 'enabled': instance.enabled, - }; +PrivacySettings _$PrivacySettingsFromJson(Map json) => PrivacySettings( + typingIndicators: json['typing_indicators'] == null + ? null + : TypingIndicators.fromJson(json['typing_indicators'] as Map), + readReceipts: json['read_receipts'] == null + ? null + : ReadReceipts.fromJson(json['read_receipts'] as Map), + deliveryReceipts: json['delivery_receipts'] == null + ? null + : DeliveryReceipts.fromJson(json['delivery_receipts'] as Map), +); + +Map _$PrivacySettingsToJson(PrivacySettings instance) => { + if (instance.typingIndicators?.toJson() case final value?) 'typing_indicators': value, + if (instance.readReceipts?.toJson() case final value?) 'read_receipts': value, + if (instance.deliveryReceipts?.toJson() case final value?) 'delivery_receipts': value, +}; + +TypingIndicators _$TypingIndicatorsFromJson(Map json) => TypingIndicators( + enabled: json['enabled'] as bool? ?? true, +); + +Map _$TypingIndicatorsToJson(TypingIndicators instance) => { + 'enabled': instance.enabled, +}; ReadReceipts _$ReadReceiptsFromJson(Map json) => ReadReceipts( - enabled: json['enabled'] as bool? ?? true, - ); - -Map _$ReadReceiptsToJson(ReadReceipts instance) => - { - 'enabled': instance.enabled, - }; - -DeliveryReceipts _$DeliveryReceiptsFromJson(Map json) => - DeliveryReceipts( - enabled: json['enabled'] as bool? ?? true, - ); - -Map _$DeliveryReceiptsToJson(DeliveryReceipts instance) => - { - 'enabled': instance.enabled, - }; + enabled: json['enabled'] as bool? ?? true, +); + +Map _$ReadReceiptsToJson(ReadReceipts instance) => { + 'enabled': instance.enabled, +}; + +DeliveryReceipts _$DeliveryReceiptsFromJson(Map json) => DeliveryReceipts( + enabled: json['enabled'] as bool? ?? true, +); + +Map _$DeliveryReceiptsToJson(DeliveryReceipts instance) => { + 'enabled': instance.enabled, +}; diff --git a/packages/stream_chat/lib/src/core/models/push_preference.dart b/packages/stream_chat/lib/src/core/models/push_preference.dart index 00ba266a1d..a3de2f8b3f 100644 --- a/packages/stream_chat/lib/src/core/models/push_preference.dart +++ b/packages/stream_chat/lib/src/core/models/push_preference.dart @@ -82,8 +82,7 @@ class PushPreference extends Equatable { }); /// Create a new instance from a json - factory PushPreference.fromJson(Map json) => - _$PushPreferenceFromJson(json); + factory PushPreference.fromJson(Map json) => _$PushPreferenceFromJson(json); /// Push preference for calls final CallLevel? callLevel; @@ -111,8 +110,7 @@ class ChannelPushPreference extends Equatable { }); /// Create a new instance from a json - factory ChannelPushPreference.fromJson(Map json) => - _$ChannelPushPreferenceFromJson(json); + factory ChannelPushPreference.fromJson(Map json) => _$ChannelPushPreferenceFromJson(json); /// Push preference for chat messages final ChatLevel? chatLevel; diff --git a/packages/stream_chat/lib/src/core/models/push_preference.g.dart b/packages/stream_chat/lib/src/core/models/push_preference.g.dart index 971c0bd1f9..1cc2cc6cc1 100644 --- a/packages/stream_chat/lib/src/core/models/push_preference.g.dart +++ b/packages/stream_chat/lib/src/core/models/push_preference.g.dart @@ -6,47 +6,32 @@ part of 'push_preference.dart'; // JsonSerializableGenerator // ************************************************************************** -Map _$PushPreferenceInputToJson( - PushPreferenceInput instance) => - { - if (instance.channelCid case final value?) 'channel_cid': value, - if (instance.callLevel case final value?) 'call_level': value, - if (instance.chatLevel case final value?) 'chat_level': value, - if (instance.disabledUntil?.toIso8601String() case final value?) - 'disabled_until': value, - if (instance.removeDisable case final value?) 'remove_disable': value, - }; +Map _$PushPreferenceInputToJson(PushPreferenceInput instance) => { + if (instance.channelCid case final value?) 'channel_cid': value, + if (instance.callLevel case final value?) 'call_level': value, + if (instance.chatLevel case final value?) 'chat_level': value, + if (instance.disabledUntil?.toIso8601String() case final value?) 'disabled_until': value, + if (instance.removeDisable case final value?) 'remove_disable': value, +}; -PushPreference _$PushPreferenceFromJson(Map json) => - PushPreference( - callLevel: json['call_level'] as CallLevel?, - chatLevel: json['chat_level'] as ChatLevel?, - disabledUntil: json['disabled_until'] == null - ? null - : DateTime.parse(json['disabled_until'] as String), - ); +PushPreference _$PushPreferenceFromJson(Map json) => PushPreference( + callLevel: json['call_level'] as CallLevel?, + chatLevel: json['chat_level'] as ChatLevel?, + disabledUntil: json['disabled_until'] == null ? null : DateTime.parse(json['disabled_until'] as String), +); -Map _$PushPreferenceToJson(PushPreference instance) => - { - if (instance.callLevel case final value?) 'call_level': value, - if (instance.chatLevel case final value?) 'chat_level': value, - if (instance.disabledUntil?.toIso8601String() case final value?) - 'disabled_until': value, - }; +Map _$PushPreferenceToJson(PushPreference instance) => { + if (instance.callLevel case final value?) 'call_level': value, + if (instance.chatLevel case final value?) 'chat_level': value, + if (instance.disabledUntil?.toIso8601String() case final value?) 'disabled_until': value, +}; -ChannelPushPreference _$ChannelPushPreferenceFromJson( - Map json) => - ChannelPushPreference( - chatLevel: json['chat_level'] as ChatLevel?, - disabledUntil: json['disabled_until'] == null - ? null - : DateTime.parse(json['disabled_until'] as String), - ); +ChannelPushPreference _$ChannelPushPreferenceFromJson(Map json) => ChannelPushPreference( + chatLevel: json['chat_level'] as ChatLevel?, + disabledUntil: json['disabled_until'] == null ? null : DateTime.parse(json['disabled_until'] as String), +); -Map _$ChannelPushPreferenceToJson( - ChannelPushPreference instance) => - { - if (instance.chatLevel case final value?) 'chat_level': value, - if (instance.disabledUntil?.toIso8601String() case final value?) - 'disabled_until': value, - }; +Map _$ChannelPushPreferenceToJson(ChannelPushPreference instance) => { + if (instance.chatLevel case final value?) 'chat_level': value, + if (instance.disabledUntil?.toIso8601String() case final value?) 'disabled_until': value, +}; diff --git a/packages/stream_chat/lib/src/core/models/reaction.dart b/packages/stream_chat/lib/src/core/models/reaction.dart index dc62fd7ce7..06a1433943 100644 --- a/packages/stream_chat/lib/src/core/models/reaction.dart +++ b/packages/stream_chat/lib/src/core/models/reaction.dart @@ -19,16 +19,17 @@ class Reaction extends Equatable { DateTime? createdAt, DateTime? updatedAt, this.extraData = const {}, - }) : userId = userId ?? user?.id, - createdAt = createdAt ?? DateTime.timestamp(), - updatedAt = updatedAt ?? DateTime.timestamp(); + }) : userId = userId ?? user?.id, + createdAt = createdAt ?? DateTime.timestamp(), + updatedAt = updatedAt ?? DateTime.timestamp(); /// Create a new instance from a json - factory Reaction.fromJson(Map json) => - _$ReactionFromJson(Serializer.moveToExtraDataFromRoot( - json, - topLevelFields, - )); + factory Reaction.fromJson(Map json) => _$ReactionFromJson( + Serializer.moveToExtraDataFromRoot( + json, + topLevelFields, + ), + ); /// The messageId to which the reaction belongs @JsonKey(includeToJson: false) @@ -77,8 +78,8 @@ class Reaction extends Equatable { /// Serialize to json Map toJson() => Serializer.moveFromExtraDataToRoot( - _$ReactionToJson(this), - ); + _$ReactionToJson(this), + ); /// Creates a copy of [Reaction] with specified attributes overridden. Reaction copyWith({ @@ -91,43 +92,42 @@ class Reaction extends Equatable { DateTime? createdAt, DateTime? updatedAt, Map? extraData, - }) => - Reaction( - messageId: messageId ?? this.messageId, - type: type ?? this.type, - user: user ?? this.user, - userId: userId ?? this.userId, - score: score ?? this.score, - emojiCode: emojiCode ?? this.emojiCode, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - extraData: extraData ?? this.extraData, - ); + }) => Reaction( + messageId: messageId ?? this.messageId, + type: type ?? this.type, + user: user ?? this.user, + userId: userId ?? this.userId, + score: score ?? this.score, + emojiCode: emojiCode ?? this.emojiCode, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + extraData: extraData ?? this.extraData, + ); /// Returns a new [Reaction] that is a combination of this reaction and the /// given [other] reaction. Reaction merge(Reaction other) => copyWith( - messageId: other.messageId, - type: other.type, - user: other.user, - userId: other.userId, - score: other.score, - emojiCode: other.emojiCode, - createdAt: other.createdAt, - updatedAt: other.updatedAt, - extraData: other.extraData, - ); + messageId: other.messageId, + type: other.type, + user: other.user, + userId: other.userId, + score: other.score, + emojiCode: other.emojiCode, + createdAt: other.createdAt, + updatedAt: other.updatedAt, + extraData: other.extraData, + ); @override List get props => [ - messageId, - type, - user, - userId, - score, - emojiCode, - createdAt, - updatedAt, - extraData, - ]; + messageId, + type, + user, + userId, + score, + emojiCode, + createdAt, + updatedAt, + extraData, + ]; } diff --git a/packages/stream_chat/lib/src/core/models/reaction.g.dart b/packages/stream_chat/lib/src/core/models/reaction.g.dart index f29c4e0931..56436be596 100644 --- a/packages/stream_chat/lib/src/core/models/reaction.g.dart +++ b/packages/stream_chat/lib/src/core/models/reaction.g.dart @@ -7,26 +7,20 @@ part of 'reaction.dart'; // ************************************************************************** Reaction _$ReactionFromJson(Map json) => Reaction( - messageId: json['message_id'] as String?, - type: json['type'] as String, - user: json['user'] == null - ? null - : User.fromJson(json['user'] as Map), - userId: json['user_id'] as String?, - score: (json['score'] as num?)?.toInt() ?? 1, - emojiCode: json['emoji_code'] as String?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - extraData: json['extra_data'] as Map? ?? const {}, - ); + messageId: json['message_id'] as String?, + type: json['type'] as String, + user: json['user'] == null ? null : User.fromJson(json['user'] as Map), + userId: json['user_id'] as String?, + score: (json['score'] as num?)?.toInt() ?? 1, + emojiCode: json['emoji_code'] as String?, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + extraData: json['extra_data'] as Map? ?? const {}, +); Map _$ReactionToJson(Reaction instance) => { - 'type': instance.type, - 'score': instance.score, - if (instance.emojiCode case final value?) 'emoji_code': value, - 'extra_data': instance.extraData, - }; + 'type': instance.type, + 'score': instance.score, + if (instance.emojiCode case final value?) 'emoji_code': value, + 'extra_data': instance.extraData, +}; diff --git a/packages/stream_chat/lib/src/core/models/reaction_group.dart b/packages/stream_chat/lib/src/core/models/reaction_group.dart index 480156de42..9be6a5150b 100644 --- a/packages/stream_chat/lib/src/core/models/reaction_group.dart +++ b/packages/stream_chat/lib/src/core/models/reaction_group.dart @@ -12,12 +12,11 @@ class ReactionGroup extends Equatable { this.sumScores = 0, DateTime? firstReactionAt, DateTime? lastReactionAt, - }) : firstReactionAt = firstReactionAt ?? DateTime.timestamp(), - lastReactionAt = lastReactionAt ?? DateTime.timestamp(); + }) : firstReactionAt = firstReactionAt ?? DateTime.timestamp(), + lastReactionAt = lastReactionAt ?? DateTime.timestamp(); /// Create a new instance from a json - factory ReactionGroup.fromJson(Map json) => - _$ReactionGroupFromJson(json); + factory ReactionGroup.fromJson(Map json) => _$ReactionGroupFromJson(json); /// The number of users that reacted with this reaction. final int count; @@ -51,11 +50,11 @@ class ReactionGroup extends Equatable { @override List get props => [ - count, - sumScores, - firstReactionAt, - lastReactionAt, - ]; + count, + sumScores, + firstReactionAt, + lastReactionAt, + ]; } /// A group of comparators for sorting [ReactionGroup]s. diff --git a/packages/stream_chat/lib/src/core/models/reaction_group.g.dart b/packages/stream_chat/lib/src/core/models/reaction_group.g.dart index 65e1ceedd7..9c686e7f0a 100644 --- a/packages/stream_chat/lib/src/core/models/reaction_group.g.dart +++ b/packages/stream_chat/lib/src/core/models/reaction_group.g.dart @@ -6,22 +6,16 @@ part of 'reaction_group.dart'; // JsonSerializableGenerator // ************************************************************************** -ReactionGroup _$ReactionGroupFromJson(Map json) => - ReactionGroup( - count: (json['count'] as num?)?.toInt() ?? 0, - sumScores: (json['sum_scores'] as num?)?.toInt() ?? 0, - firstReactionAt: json['first_reaction_at'] == null - ? null - : DateTime.parse(json['first_reaction_at'] as String), - lastReactionAt: json['last_reaction_at'] == null - ? null - : DateTime.parse(json['last_reaction_at'] as String), - ); +ReactionGroup _$ReactionGroupFromJson(Map json) => ReactionGroup( + count: (json['count'] as num?)?.toInt() ?? 0, + sumScores: (json['sum_scores'] as num?)?.toInt() ?? 0, + firstReactionAt: json['first_reaction_at'] == null ? null : DateTime.parse(json['first_reaction_at'] as String), + lastReactionAt: json['last_reaction_at'] == null ? null : DateTime.parse(json['last_reaction_at'] as String), +); -Map _$ReactionGroupToJson(ReactionGroup instance) => - { - 'count': instance.count, - 'sum_scores': instance.sumScores, - 'first_reaction_at': instance.firstReactionAt.toIso8601String(), - 'last_reaction_at': instance.lastReactionAt.toIso8601String(), - }; +Map _$ReactionGroupToJson(ReactionGroup instance) => { + 'count': instance.count, + 'sum_scores': instance.sumScores, + 'first_reaction_at': instance.firstReactionAt.toIso8601String(), + 'last_reaction_at': instance.lastReactionAt.toIso8601String(), +}; diff --git a/packages/stream_chat/lib/src/core/models/read.dart b/packages/stream_chat/lib/src/core/models/read.dart index 165528946f..5952ad4b5c 100644 --- a/packages/stream_chat/lib/src/core/models/read.dart +++ b/packages/stream_chat/lib/src/core/models/read.dart @@ -58,8 +58,7 @@ class Read extends Equatable { user: user ?? this.user, unreadMessages: unreadMessages ?? this.unreadMessages, lastDeliveredAt: lastDeliveredAt ?? this.lastDeliveredAt, - lastDeliveredMessageId: - lastDeliveredMessageId ?? this.lastDeliveredMessageId, + lastDeliveredMessageId: lastDeliveredMessageId ?? this.lastDeliveredMessageId, ); } @@ -79,13 +78,13 @@ class Read extends Equatable { @override List get props => [ - lastRead, - lastReadMessageId, - user, - unreadMessages, - lastDeliveredAt, - lastDeliveredMessageId, - ]; + lastRead, + lastReadMessageId, + user, + unreadMessages, + lastDeliveredAt, + lastDeliveredMessageId, + ]; } /// Helper extension methods for [Iterable]<[Read]>. diff --git a/packages/stream_chat/lib/src/core/models/read.g.dart b/packages/stream_chat/lib/src/core/models/read.g.dart index 3e9d03fb0e..9f4358fcf5 100644 --- a/packages/stream_chat/lib/src/core/models/read.g.dart +++ b/packages/stream_chat/lib/src/core/models/read.g.dart @@ -7,21 +7,19 @@ part of 'read.dart'; // ************************************************************************** Read _$ReadFromJson(Map json) => Read( - lastRead: DateTime.parse(json['last_read'] as String), - user: User.fromJson(json['user'] as Map), - lastReadMessageId: json['last_read_message_id'] as String?, - unreadMessages: (json['unread_messages'] as num?)?.toInt(), - lastDeliveredAt: json['last_delivered_at'] == null - ? null - : DateTime.parse(json['last_delivered_at'] as String), - lastDeliveredMessageId: json['last_delivered_message_id'] as String?, - ); + lastRead: DateTime.parse(json['last_read'] as String), + user: User.fromJson(json['user'] as Map), + lastReadMessageId: json['last_read_message_id'] as String?, + unreadMessages: (json['unread_messages'] as num?)?.toInt(), + lastDeliveredAt: json['last_delivered_at'] == null ? null : DateTime.parse(json['last_delivered_at'] as String), + lastDeliveredMessageId: json['last_delivered_message_id'] as String?, +); Map _$ReadToJson(Read instance) => { - 'last_read': instance.lastRead.toIso8601String(), - 'user': instance.user.toJson(), - 'unread_messages': instance.unreadMessages, - 'last_read_message_id': instance.lastReadMessageId, - 'last_delivered_at': instance.lastDeliveredAt?.toIso8601String(), - 'last_delivered_message_id': instance.lastDeliveredMessageId, - }; + 'last_read': instance.lastRead.toIso8601String(), + 'user': instance.user.toJson(), + 'unread_messages': instance.unreadMessages, + 'last_read_message_id': instance.lastReadMessageId, + 'last_delivered_at': instance.lastDeliveredAt?.toIso8601String(), + 'last_delivered_message_id': instance.lastDeliveredMessageId, +}; diff --git a/packages/stream_chat/lib/src/core/models/thread.dart b/packages/stream_chat/lib/src/core/models/thread.dart index 3a26e15230..4074ca0158 100644 --- a/packages/stream_chat/lib/src/core/models/thread.dart +++ b/packages/stream_chat/lib/src/core/models/thread.dart @@ -44,12 +44,12 @@ class Thread extends Equatable implements ComparableFieldProvider { this.read = const [], this.draft, this.extraData = const {}, - }) : createdAt = createdAt ?? DateTime.now(), - updatedAt = updatedAt ?? DateTime.now(); + }) : createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json - factory Thread.fromJson(Map json) => _$ThreadFromJson( - Serializer.moveToExtraDataFromRoot(json, topLevelFields)); + factory Thread.fromJson(Map json) => + _$ThreadFromJson(Serializer.moveToExtraDataFromRoot(json, topLevelFields)); /// The active participant count in the thread. final int? activeParticipantCount; @@ -109,8 +109,7 @@ class Thread extends Equatable implements ComparableFieldProvider { final Map extraData; /// Serialize to json - Map toJson() => - Serializer.moveFromExtraDataToRoot(_$ThreadToJson(this)); + Map toJson() => Serializer.moveFromExtraDataToRoot(_$ThreadToJson(this)); /// Creates a copy of [Thread] with specified attributes overridden. Thread copyWith({ @@ -133,29 +132,27 @@ class Thread extends Equatable implements ComparableFieldProvider { List? read, Object? draft = _nullConst, Map? extraData, - }) => - Thread( - activeParticipantCount: - activeParticipantCount ?? this.activeParticipantCount, - channel: channel ?? this.channel, - channelCid: channelCid ?? this.channelCid, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - deletedAt: deletedAt ?? this.deletedAt, - createdByUserId: createdByUserId ?? this.createdByUserId, - createdBy: createdBy ?? this.createdBy, - title: title ?? this.title, - parentMessageId: parentMessageId ?? this.parentMessageId, - parentMessage: parentMessage ?? this.parentMessage, - replyCount: replyCount ?? this.replyCount, - participantCount: participantCount ?? this.participantCount, - threadParticipants: threadParticipants ?? this.threadParticipants, - lastMessageAt: lastMessageAt ?? this.lastMessageAt, - latestReplies: latestReplies ?? this.latestReplies, - read: read ?? this.read, - draft: draft == _nullConst ? this.draft : draft as Draft?, - extraData: extraData ?? this.extraData, - ); + }) => Thread( + activeParticipantCount: activeParticipantCount ?? this.activeParticipantCount, + channel: channel ?? this.channel, + channelCid: channelCid ?? this.channelCid, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + createdByUserId: createdByUserId ?? this.createdByUserId, + createdBy: createdBy ?? this.createdBy, + title: title ?? this.title, + parentMessageId: parentMessageId ?? this.parentMessageId, + parentMessage: parentMessage ?? this.parentMessage, + replyCount: replyCount ?? this.replyCount, + participantCount: participantCount ?? this.participantCount, + threadParticipants: threadParticipants ?? this.threadParticipants, + lastMessageAt: lastMessageAt ?? this.lastMessageAt, + latestReplies: latestReplies ?? this.latestReplies, + read: read ?? this.read, + draft: draft == _nullConst ? this.draft : draft as Draft?, + extraData: extraData ?? this.extraData, + ); /// Merge this thread with the [other] thread. Thread merge(Thread? other) { @@ -209,25 +206,25 @@ class Thread extends Equatable implements ComparableFieldProvider { @override List get props => [ - activeParticipantCount, - channelCid, - channel, - createdAt, - updatedAt, - deletedAt, - createdByUserId, - createdBy, - title, - parentMessageId, - parentMessage, - replyCount, - participantCount, - threadParticipants, - lastMessageAt, - latestReplies, - read, - draft, - ]; + activeParticipantCount, + channelCid, + channel, + createdAt, + updatedAt, + deletedAt, + createdByUserId, + createdBy, + title, + parentMessageId, + parentMessage, + replyCount, + participantCount, + threadParticipants, + lastMessageAt, + latestReplies, + read, + draft, + ]; @override ComparableField? getComparableField(String sortKey) { @@ -269,8 +266,7 @@ extension type const ThreadSortKey(String key) implements String { static const participantCount = ThreadSortKey('participant_count'); /// Sort threads by their active participant count. - static const activeParticipantCount = - ThreadSortKey('active_participant_count'); + static const activeParticipantCount = ThreadSortKey('active_participant_count'); /// Sort threads by their parent message id. static const parentMessageId = ThreadSortKey('parent_message_id'); diff --git a/packages/stream_chat/lib/src/core/models/thread.g.dart b/packages/stream_chat/lib/src/core/models/thread.g.dart index ed5b27b28b..7d5d46cd30 100644 --- a/packages/stream_chat/lib/src/core/models/thread.g.dart +++ b/packages/stream_chat/lib/src/core/models/thread.g.dart @@ -7,73 +7,53 @@ part of 'thread.dart'; // ************************************************************************** Thread _$ThreadFromJson(Map json) => Thread( - activeParticipantCount: - (json['active_participant_count'] as num?)?.toInt(), - channel: json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map), - channelCid: json['channel_cid'] as String, - parentMessageId: json['parent_message_id'] as String, - parentMessage: json['parent_message'] == null - ? null - : Message.fromJson(json['parent_message'] as Map), - createdByUserId: json['created_by_user_id'] as String, - createdBy: json['created_by'] == null - ? null - : User.fromJson(json['created_by'] as Map), - replyCount: (json['reply_count'] as num).toInt(), - participantCount: (json['participant_count'] as num).toInt(), - threadParticipants: (json['thread_participants'] as List?) - ?.map( - (e) => ThreadParticipant.fromJson(e as Map)) - .toList() ?? - const [], - lastMessageAt: json['last_message_at'] == null - ? null - : DateTime.parse(json['last_message_at'] as String), - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - deletedAt: json['deleted_at'] == null - ? null - : DateTime.parse(json['deleted_at'] as String), - title: json['title'] as String?, - latestReplies: (json['latest_replies'] as List?) - ?.map((e) => Message.fromJson(e as Map)) - .toList() ?? - const [], - read: (json['read'] as List?) - ?.map((e) => Read.fromJson(e as Map)) - .toList() ?? - const [], - draft: json['draft'] == null - ? null - : Draft.fromJson(json['draft'] as Map), - extraData: json['extra_data'] as Map? ?? const {}, - ); + activeParticipantCount: (json['active_participant_count'] as num?)?.toInt(), + channel: json['channel'] == null ? null : ChannelModel.fromJson(json['channel'] as Map), + channelCid: json['channel_cid'] as String, + parentMessageId: json['parent_message_id'] as String, + parentMessage: json['parent_message'] == null + ? null + : Message.fromJson(json['parent_message'] as Map), + createdByUserId: json['created_by_user_id'] as String, + createdBy: json['created_by'] == null ? null : User.fromJson(json['created_by'] as Map), + replyCount: (json['reply_count'] as num).toInt(), + participantCount: (json['participant_count'] as num).toInt(), + threadParticipants: + (json['thread_participants'] as List?) + ?.map((e) => ThreadParticipant.fromJson(e as Map)) + .toList() ?? + const [], + lastMessageAt: json['last_message_at'] == null ? null : DateTime.parse(json['last_message_at'] as String), + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + deletedAt: json['deleted_at'] == null ? null : DateTime.parse(json['deleted_at'] as String), + title: json['title'] as String?, + latestReplies: + (json['latest_replies'] as List?)?.map((e) => Message.fromJson(e as Map)).toList() ?? + const [], + read: (json['read'] as List?)?.map((e) => Read.fromJson(e as Map)).toList() ?? const [], + draft: json['draft'] == null ? null : Draft.fromJson(json['draft'] as Map), + extraData: json['extra_data'] as Map? ?? const {}, +); Map _$ThreadToJson(Thread instance) => { - 'active_participant_count': instance.activeParticipantCount, - 'channel_cid': instance.channelCid, - 'channel': instance.channel?.toJson(), - 'created_at': instance.createdAt.toIso8601String(), - 'updated_at': instance.updatedAt.toIso8601String(), - 'deleted_at': instance.deletedAt?.toIso8601String(), - 'created_by_user_id': instance.createdByUserId, - 'created_by': instance.createdBy?.toJson(), - 'title': instance.title, - 'parent_message_id': instance.parentMessageId, - 'parent_message': instance.parentMessage?.toJson(), - 'reply_count': instance.replyCount, - 'participant_count': instance.participantCount, - 'thread_participants': - instance.threadParticipants.map((e) => e.toJson()).toList(), - 'last_message_at': instance.lastMessageAt?.toIso8601String(), - 'latest_replies': instance.latestReplies.map((e) => e.toJson()).toList(), - 'read': instance.read?.map((e) => e.toJson()).toList(), - 'draft': instance.draft?.toJson(), - 'extra_data': instance.extraData, - }; + 'active_participant_count': instance.activeParticipantCount, + 'channel_cid': instance.channelCid, + 'channel': instance.channel?.toJson(), + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'deleted_at': instance.deletedAt?.toIso8601String(), + 'created_by_user_id': instance.createdByUserId, + 'created_by': instance.createdBy?.toJson(), + 'title': instance.title, + 'parent_message_id': instance.parentMessageId, + 'parent_message': instance.parentMessage?.toJson(), + 'reply_count': instance.replyCount, + 'participant_count': instance.participantCount, + 'thread_participants': instance.threadParticipants.map((e) => e.toJson()).toList(), + 'last_message_at': instance.lastMessageAt?.toIso8601String(), + 'latest_replies': instance.latestReplies.map((e) => e.toJson()).toList(), + 'read': instance.read?.map((e) => e.toJson()).toList(), + 'draft': instance.draft?.toJson(), + 'extra_data': instance.extraData, +}; diff --git a/packages/stream_chat/lib/src/core/models/thread_participant.dart b/packages/stream_chat/lib/src/core/models/thread_participant.dart index fece374619..96d5a7276c 100644 --- a/packages/stream_chat/lib/src/core/models/thread_participant.dart +++ b/packages/stream_chat/lib/src/core/models/thread_participant.dart @@ -22,8 +22,7 @@ class ThreadParticipant extends Equatable { }); /// Create a new instance from a json - factory ThreadParticipant.fromJson(Map json) => - _$ThreadParticipantFromJson(json); + factory ThreadParticipant.fromJson(Map json) => _$ThreadParticipantFromJson(json); /// The channel cid this thread participant belongs to. final String channelCid; @@ -63,27 +62,26 @@ class ThreadParticipant extends Equatable { String? threadId, String? userId, User? user, - }) => - ThreadParticipant( - channelCid: channelCid ?? this.channelCid, - createdAt: createdAt ?? this.createdAt, - lastReadAt: lastReadAt ?? this.lastReadAt, - lastThreadMessageAt: lastThreadMessageAt ?? this.lastThreadMessageAt, - leftThreadAt: leftThreadAt ?? this.leftThreadAt, - threadId: threadId ?? this.threadId, - userId: userId ?? this.userId, - user: user ?? this.user, - ); + }) => ThreadParticipant( + channelCid: channelCid ?? this.channelCid, + createdAt: createdAt ?? this.createdAt, + lastReadAt: lastReadAt ?? this.lastReadAt, + lastThreadMessageAt: lastThreadMessageAt ?? this.lastThreadMessageAt, + leftThreadAt: leftThreadAt ?? this.leftThreadAt, + threadId: threadId ?? this.threadId, + userId: userId ?? this.userId, + user: user ?? this.user, + ); @override List get props => [ - channelCid, - createdAt, - lastReadAt, - lastThreadMessageAt, - leftThreadAt, - threadId, - userId, - user, - ]; + channelCid, + createdAt, + lastReadAt, + lastThreadMessageAt, + leftThreadAt, + threadId, + userId, + user, + ]; } diff --git a/packages/stream_chat/lib/src/core/models/thread_participant.g.dart b/packages/stream_chat/lib/src/core/models/thread_participant.g.dart index 4a410c404b..ec1d860052 100644 --- a/packages/stream_chat/lib/src/core/models/thread_participant.g.dart +++ b/packages/stream_chat/lib/src/core/models/thread_participant.g.dart @@ -6,32 +6,26 @@ part of 'thread_participant.dart'; // JsonSerializableGenerator // ************************************************************************** -ThreadParticipant _$ThreadParticipantFromJson(Map json) => - ThreadParticipant( - channelCid: json['channel_cid'] as String, - createdAt: DateTime.parse(json['created_at'] as String), - lastReadAt: DateTime.parse(json['last_read_at'] as String), - lastThreadMessageAt: json['last_thread_message_at'] == null - ? null - : DateTime.parse(json['last_thread_message_at'] as String), - leftThreadAt: json['left_thread_at'] == null - ? null - : DateTime.parse(json['left_thread_at'] as String), - threadId: json['thread_id'] as String?, - userId: json['user_id'] as String?, - user: json['user'] == null - ? null - : User.fromJson(json['user'] as Map), - ); +ThreadParticipant _$ThreadParticipantFromJson(Map json) => ThreadParticipant( + channelCid: json['channel_cid'] as String, + createdAt: DateTime.parse(json['created_at'] as String), + lastReadAt: DateTime.parse(json['last_read_at'] as String), + lastThreadMessageAt: json['last_thread_message_at'] == null + ? null + : DateTime.parse(json['last_thread_message_at'] as String), + leftThreadAt: json['left_thread_at'] == null ? null : DateTime.parse(json['left_thread_at'] as String), + threadId: json['thread_id'] as String?, + userId: json['user_id'] as String?, + user: json['user'] == null ? null : User.fromJson(json['user'] as Map), +); -Map _$ThreadParticipantToJson(ThreadParticipant instance) => - { - 'channel_cid': instance.channelCid, - 'created_at': instance.createdAt.toIso8601String(), - 'last_read_at': instance.lastReadAt.toIso8601String(), - 'last_thread_message_at': instance.lastThreadMessageAt?.toIso8601String(), - 'left_thread_at': instance.leftThreadAt?.toIso8601String(), - 'thread_id': instance.threadId, - 'user_id': instance.userId, - 'user': instance.user?.toJson(), - }; +Map _$ThreadParticipantToJson(ThreadParticipant instance) => { + 'channel_cid': instance.channelCid, + 'created_at': instance.createdAt.toIso8601String(), + 'last_read_at': instance.lastReadAt.toIso8601String(), + 'last_thread_message_at': instance.lastThreadMessageAt?.toIso8601String(), + 'left_thread_at': instance.leftThreadAt?.toIso8601String(), + 'thread_id': instance.threadId, + 'user_id': instance.userId, + 'user': instance.user?.toJson(), +}; diff --git a/packages/stream_chat/lib/src/core/models/unread_counts.dart b/packages/stream_chat/lib/src/core/models/unread_counts.dart index d3e265fa76..91a0b7dfe5 100644 --- a/packages/stream_chat/lib/src/core/models/unread_counts.dart +++ b/packages/stream_chat/lib/src/core/models/unread_counts.dart @@ -15,8 +15,7 @@ class UnreadCountsChannel { }); /// Create a new instance from a json. - factory UnreadCountsChannel.fromJson(Map json) => - _$UnreadCountsChannelFromJson(json); + factory UnreadCountsChannel.fromJson(Map json) => _$UnreadCountsChannelFromJson(json); /// The unique identifier of the channel (format: "type:id"). final String channelId; @@ -45,8 +44,7 @@ class UnreadCountsThread { }); /// Create a new instance from a json. - factory UnreadCountsThread.fromJson(Map json) => - _$UnreadCountsThreadFromJson(json); + factory UnreadCountsThread.fromJson(Map json) => _$UnreadCountsThreadFromJson(json); /// Number of unread messages in this thread. final int unreadCount; @@ -78,8 +76,7 @@ class UnreadCountsChannelType { }); /// Create a new instance from a json. - factory UnreadCountsChannelType.fromJson(Map json) => - _$UnreadCountsChannelTypeFromJson(json); + factory UnreadCountsChannelType.fromJson(Map json) => _$UnreadCountsChannelTypeFromJson(json); /// The type of channel (e.g., "messaging", "livestream", "team"). final String channelType; diff --git a/packages/stream_chat/lib/src/core/models/unread_counts.g.dart b/packages/stream_chat/lib/src/core/models/unread_counts.g.dart index bd149b5a75..95ff1053b9 100644 --- a/packages/stream_chat/lib/src/core/models/unread_counts.g.dart +++ b/packages/stream_chat/lib/src/core/models/unread_counts.g.dart @@ -6,49 +6,40 @@ part of 'unread_counts.dart'; // JsonSerializableGenerator // ************************************************************************** -UnreadCountsChannel _$UnreadCountsChannelFromJson(Map json) => - UnreadCountsChannel( - channelId: json['channel_id'] as String, - unreadCount: (json['unread_count'] as num).toInt(), - lastRead: DateTime.parse(json['last_read'] as String), - ); - -Map _$UnreadCountsChannelToJson( - UnreadCountsChannel instance) => - { - 'channel_id': instance.channelId, - 'unread_count': instance.unreadCount, - 'last_read': instance.lastRead.toIso8601String(), - }; - -UnreadCountsThread _$UnreadCountsThreadFromJson(Map json) => - UnreadCountsThread( - unreadCount: (json['unread_count'] as num).toInt(), - lastRead: DateTime.parse(json['last_read'] as String), - lastReadMessageId: json['last_read_message_id'] as String, - parentMessageId: json['parent_message_id'] as String, - ); - -Map _$UnreadCountsThreadToJson(UnreadCountsThread instance) => - { - 'unread_count': instance.unreadCount, - 'last_read': instance.lastRead.toIso8601String(), - 'last_read_message_id': instance.lastReadMessageId, - 'parent_message_id': instance.parentMessageId, - }; - -UnreadCountsChannelType _$UnreadCountsChannelTypeFromJson( - Map json) => - UnreadCountsChannelType( - channelType: json['channel_type'] as String, - channelCount: (json['channel_count'] as num).toInt(), - unreadCount: (json['unread_count'] as num).toInt(), - ); - -Map _$UnreadCountsChannelTypeToJson( - UnreadCountsChannelType instance) => - { - 'channel_type': instance.channelType, - 'channel_count': instance.channelCount, - 'unread_count': instance.unreadCount, - }; +UnreadCountsChannel _$UnreadCountsChannelFromJson(Map json) => UnreadCountsChannel( + channelId: json['channel_id'] as String, + unreadCount: (json['unread_count'] as num).toInt(), + lastRead: DateTime.parse(json['last_read'] as String), +); + +Map _$UnreadCountsChannelToJson(UnreadCountsChannel instance) => { + 'channel_id': instance.channelId, + 'unread_count': instance.unreadCount, + 'last_read': instance.lastRead.toIso8601String(), +}; + +UnreadCountsThread _$UnreadCountsThreadFromJson(Map json) => UnreadCountsThread( + unreadCount: (json['unread_count'] as num).toInt(), + lastRead: DateTime.parse(json['last_read'] as String), + lastReadMessageId: json['last_read_message_id'] as String, + parentMessageId: json['parent_message_id'] as String, +); + +Map _$UnreadCountsThreadToJson(UnreadCountsThread instance) => { + 'unread_count': instance.unreadCount, + 'last_read': instance.lastRead.toIso8601String(), + 'last_read_message_id': instance.lastReadMessageId, + 'parent_message_id': instance.parentMessageId, +}; + +UnreadCountsChannelType _$UnreadCountsChannelTypeFromJson(Map json) => UnreadCountsChannelType( + channelType: json['channel_type'] as String, + channelCount: (json['channel_count'] as num).toInt(), + unreadCount: (json['unread_count'] as num).toInt(), +); + +Map _$UnreadCountsChannelTypeToJson(UnreadCountsChannelType instance) => { + 'channel_type': instance.channelType, + 'channel_count': instance.channelCount, + 'unread_count': instance.unreadCount, +}; diff --git a/packages/stream_chat/lib/src/core/models/user.dart b/packages/stream_chat/lib/src/core/models/user.dart index 3c45ee5a86..9d6e6e3207 100644 --- a/packages/stream_chat/lib/src/core/models/user.dart +++ b/packages/stream_chat/lib/src/core/models/user.dart @@ -49,13 +49,12 @@ class User extends Equatable implements ComparableFieldProvider { this.teamsRole, this.avgResponseTime, Map extraData = const {}, - }) : - // For backwards compatibility, set 'name', 'image' in [extraData]. - extraData = { - ...extraData, - if (name != null) 'name': name, - if (image != null) 'image': image, - }; + }) : // For backwards compatibility, set 'name', 'image' in [extraData]. + extraData = { + ...extraData, + if (name != null) 'name': name, + if (image != null) 'image': image, + }; /// Create a new instance from json. factory User.fromJson(Map json) => @@ -138,7 +137,7 @@ class User extends Equatable implements ComparableFieldProvider { /// The roles for the user in the teams. /// /// eg: `{'teamId': 'role', 'teamId2': 'role2'}` - final Map< /*Team*/ String, /*Role*/ String>? teamsRole; + final Map? teamsRole; /// The average response time of the user in seconds. final int? avgResponseTime; @@ -147,13 +146,12 @@ class User extends Equatable implements ComparableFieldProvider { final Map extraData; /// List of users to list of userIds. - static List? toIds(List? users) => - users?.map((u) => u.id).toList(); + static List? toIds(List? users) => users?.map((u) => u.id).toList(); /// Serialize to json. Map toJson() => Serializer.moveFromExtraDataToRoot( - _$UserToJson(this), - ); + _$UserToJson(this), + ); /// Creates a copy of [User] with specified attributes overridden. User copyWith({ @@ -173,44 +171,44 @@ class User extends Equatable implements ComparableFieldProvider { bool? invisible, Map? teamsRole, int? avgResponseTime, - }) => - User( - id: id ?? this.id, - role: role ?? this.role, - name: name ?? - extraData?['name'] as String? ?? - // Using extraData value in order to not use id as name. - this.extraData['name'] as String?, - image: image ?? extraData?['image'] as String? ?? this.image, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - lastActive: lastActive ?? this.lastActive, - online: online ?? this.online, - extraData: extraData ?? this.extraData, - banned: banned ?? this.banned, - banExpires: banExpires ?? this.banExpires, - teams: teams ?? this.teams, - language: language ?? this.language, - invisible: invisible ?? this.invisible, - teamsRole: teamsRole ?? this.teamsRole, - avgResponseTime: avgResponseTime ?? this.avgResponseTime, - ); + }) => User( + id: id ?? this.id, + role: role ?? this.role, + name: + name ?? + extraData?['name'] as String? ?? + // Using extraData value in order to not use id as name. + this.extraData['name'] as String?, + image: image ?? extraData?['image'] as String? ?? this.image, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + lastActive: lastActive ?? this.lastActive, + online: online ?? this.online, + extraData: extraData ?? this.extraData, + banned: banned ?? this.banned, + banExpires: banExpires ?? this.banExpires, + teams: teams ?? this.teams, + language: language ?? this.language, + invisible: invisible ?? this.invisible, + teamsRole: teamsRole ?? this.teamsRole, + avgResponseTime: avgResponseTime ?? this.avgResponseTime, + ); @override List get props => [ - id, - role, - lastActive, - online, - extraData, - banned, - banExpires, - teams, - language, - invisible, - teamsRole, - avgResponseTime, - ]; + id, + role, + lastActive, + online, + extraData, + banned, + banExpires, + teams, + language, + invisible, + teamsRole, + avgResponseTime, + ]; @override ComparableField? getComparableField(String sortKey) { diff --git a/packages/stream_chat/lib/src/core/models/user.g.dart b/packages/stream_chat/lib/src/core/models/user.g.dart index f3a8e5868d..03cb7553d0 100644 --- a/packages/stream_chat/lib/src/core/models/user.g.dart +++ b/packages/stream_chat/lib/src/core/models/user.g.dart @@ -7,52 +7,37 @@ part of 'user.dart'; // ************************************************************************** User _$UserFromJson(Map json) => User( - id: json['id'] as String, - role: json['role'] as String?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - lastActive: json['last_active'] == null - ? null - : DateTime.parse(json['last_active'] as String), - online: json['online'] as bool? ?? false, - banned: json['banned'] as bool? ?? false, - banExpires: json['ban_expires'] == null - ? null - : DateTime.parse(json['ban_expires'] as String), - teams: - (json['teams'] as List?)?.map((e) => e as String).toList() ?? - const [], - language: json['language'] as String?, - invisible: json['invisible'] as bool?, - teamsRole: (json['teams_role'] as Map?)?.map( - (k, e) => MapEntry(k, e as String), - ), - avgResponseTime: (json['avg_response_time'] as num?)?.toInt(), - extraData: json['extra_data'] as Map? ?? const {}, - ); + id: json['id'] as String, + role: json['role'] as String?, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + lastActive: json['last_active'] == null ? null : DateTime.parse(json['last_active'] as String), + online: json['online'] as bool? ?? false, + banned: json['banned'] as bool? ?? false, + banExpires: json['ban_expires'] == null ? null : DateTime.parse(json['ban_expires'] as String), + teams: (json['teams'] as List?)?.map((e) => e as String).toList() ?? const [], + language: json['language'] as String?, + invisible: json['invisible'] as bool?, + teamsRole: (json['teams_role'] as Map?)?.map( + (k, e) => MapEntry(k, e as String), + ), + avgResponseTime: (json['avg_response_time'] as num?)?.toInt(), + extraData: json['extra_data'] as Map? ?? const {}, +); Map _$UserToJson(User instance) => { - 'id': instance.id, - if (instance.role case final value?) 'role': value, - 'teams': instance.teams, - if (instance.createdAt?.toIso8601String() case final value?) - 'created_at': value, - if (instance.updatedAt?.toIso8601String() case final value?) - 'updated_at': value, - if (instance.lastActive?.toIso8601String() case final value?) - 'last_active': value, - 'online': instance.online, - 'banned': instance.banned, - if (instance.banExpires?.toIso8601String() case final value?) - 'ban_expires': value, - if (instance.language case final value?) 'language': value, - if (instance.invisible case final value?) 'invisible': value, - if (instance.teamsRole case final value?) 'teams_role': value, - if (instance.avgResponseTime case final value?) - 'avg_response_time': value, - 'extra_data': instance.extraData, - }; + 'id': instance.id, + if (instance.role case final value?) 'role': value, + 'teams': instance.teams, + if (instance.createdAt?.toIso8601String() case final value?) 'created_at': value, + if (instance.updatedAt?.toIso8601String() case final value?) 'updated_at': value, + if (instance.lastActive?.toIso8601String() case final value?) 'last_active': value, + 'online': instance.online, + 'banned': instance.banned, + if (instance.banExpires?.toIso8601String() case final value?) 'ban_expires': value, + if (instance.language case final value?) 'language': value, + if (instance.invisible case final value?) 'invisible': value, + if (instance.teamsRole case final value?) 'teams_role': value, + if (instance.avgResponseTime case final value?) 'avg_response_time': value, + 'extra_data': instance.extraData, +}; diff --git a/packages/stream_chat/lib/src/core/models/user_block.dart b/packages/stream_chat/lib/src/core/models/user_block.dart index 247de32bb8..77cbfe04f4 100644 --- a/packages/stream_chat/lib/src/core/models/user_block.dart +++ b/packages/stream_chat/lib/src/core/models/user_block.dart @@ -17,8 +17,7 @@ class UserBlock extends Equatable { }); /// Create a new instance from a json - factory UserBlock.fromJson(Map json) => - _$UserBlockFromJson(json); + factory UserBlock.fromJson(Map json) => _$UserBlockFromJson(json); /// User that blocked the [blockedUser]. final User user; @@ -45,21 +44,20 @@ class UserBlock extends Equatable { String? userId, String? blockedUserId, DateTime? createdAt, - }) => - UserBlock( - user: user ?? this.user, - blockedUser: blockedUser ?? this.blockedUser, - userId: userId ?? this.userId, - blockedUserId: blockedUserId ?? this.blockedUserId, - createdAt: createdAt ?? this.createdAt, - ); + }) => UserBlock( + user: user ?? this.user, + blockedUser: blockedUser ?? this.blockedUser, + userId: userId ?? this.userId, + blockedUserId: blockedUserId ?? this.blockedUserId, + createdAt: createdAt ?? this.createdAt, + ); @override List get props => [ - user, - blockedUser, - userId, - blockedUserId, - createdAt, - ]; + user, + blockedUser, + userId, + blockedUserId, + createdAt, + ]; } diff --git a/packages/stream_chat/lib/src/core/models/user_block.g.dart b/packages/stream_chat/lib/src/core/models/user_block.g.dart index 00e138211b..6e36cb806f 100644 --- a/packages/stream_chat/lib/src/core/models/user_block.g.dart +++ b/packages/stream_chat/lib/src/core/models/user_block.g.dart @@ -7,21 +7,17 @@ part of 'user_block.dart'; // ************************************************************************** UserBlock _$UserBlockFromJson(Map json) => UserBlock( - user: User.fromJson(json['user'] as Map), - blockedUser: json['blocked_user'] == null - ? null - : User.fromJson(json['blocked_user'] as Map), - userId: json['user_id'] as String?, - blockedUserId: json['blocked_user_id'] as String?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - ); + user: User.fromJson(json['user'] as Map), + blockedUser: json['blocked_user'] == null ? null : User.fromJson(json['blocked_user'] as Map), + userId: json['user_id'] as String?, + blockedUserId: json['blocked_user_id'] as String?, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), +); Map _$UserBlockToJson(UserBlock instance) => { - 'user': instance.user.toJson(), - 'blocked_user': instance.blockedUser?.toJson(), - 'user_id': instance.userId, - 'blocked_user_id': instance.blockedUserId, - 'created_at': instance.createdAt?.toIso8601String(), - }; + 'user': instance.user.toJson(), + 'blocked_user': instance.blockedUser?.toJson(), + 'user_id': instance.userId, + 'blocked_user_id': instance.blockedUserId, + 'created_at': instance.createdAt?.toIso8601String(), +}; diff --git a/packages/stream_chat/lib/src/core/util/extension.dart b/packages/stream_chat/lib/src/core/util/extension.dart index 0c756c0fae..51bfe84639 100644 --- a/packages/stream_chat/lib/src/core/util/extension.dart +++ b/packages/stream_chat/lib/src/core/util/extension.dart @@ -14,8 +14,7 @@ extension IterableX on Iterable { extension MapX on Map { /// Returns a new map with null keys or values removed Map get nullProtected { - final nullProtected = {...this} - ..removeWhere((key, value) => key == null || value == null); + final nullProtected = {...this}..removeWhere((key, value) => key == null || value == null); return nullProtected.cast(); } } diff --git a/packages/stream_chat/lib/src/core/util/serializer.dart b/packages/stream_chat/lib/src/core/util/serializer.dart index 9c1cb71f67..5cd992bc11 100644 --- a/packages/stream_chat/lib/src/core/util/serializer.dart +++ b/packages/stream_chat/lib/src/core/util/serializer.dart @@ -11,12 +11,10 @@ class Serializer { ..removeWhere( (key, value) => topLevelFields.contains(key), ); - final rootFields = jsonClone - ..removeWhere((key, value) => extraDataMap.keys.contains(key)); - return rootFields - ..addAll({ - 'extra_data': extraDataMap, - }); + final rootFields = jsonClone..removeWhere((key, value) => extraDataMap.keys.contains(key)); + return rootFields..addAll({ + 'extra_data': extraDataMap, + }); } /// Takes values in `extra_data` key and puts them on the root level of diff --git a/packages/stream_chat/lib/src/core/util/utils.dart b/packages/stream_chat/lib/src/core/util/utils.dart index c220499182..23c8bc8923 100644 --- a/packages/stream_chat/lib/src/core/util/utils.dart +++ b/packages/stream_chat/lib/src/core/util/utils.dart @@ -3,8 +3,7 @@ import 'dart:math' as math; // This alphabet uses `A-Za-z0-9_-` symbols. The genetic algorithm helped // optimize the gzip compression for this alphabet. -const _alphabet = - 'ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW'; +const _alphabet = 'ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW'; /// Generates a random String id /// Adopted from: https://github.com/ai/nanoid/blob/main/non-secure/index.js diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index b9092b2b36..4daf3f87f6 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -147,12 +147,10 @@ abstract class ChatPersistenceClient { }); /// Remove a message by [messageId] - Future deleteMessageById(String messageId) => - deleteMessageByIds([messageId]); + Future deleteMessageById(String messageId) => deleteMessageByIds([messageId]); /// Remove a pinned message by [messageId] - Future deletePinnedMessageById(String messageId) => - deletePinnedMessageByIds([messageId]); + Future deletePinnedMessageById(String messageId) => deletePinnedMessageByIds([messageId]); /// Remove a message by [messageIds] Future deleteMessageByIds(List messageIds); @@ -164,8 +162,7 @@ abstract class ChatPersistenceClient { Future deleteMessageByCid(String cid) => deleteMessageByCids([cid]); /// Remove a pinned message by channel [cid] - Future deletePinnedMessageByCid(String cid) async => - deletePinnedMessageByCids([cid]); + Future deletePinnedMessageByCid(String cid) async => deletePinnedMessageByCids([cid]); /// Remove a message by message [cids] Future deleteMessageByCids(List cids); @@ -206,16 +203,14 @@ abstract class ChatPersistenceClient { /// Updates the message data of a particular channel [cid] with /// the new [messages] data - Future updateMessages(String cid, List messages) => - bulkUpdateMessages({cid: messages}); + Future updateMessages(String cid, List messages) => bulkUpdateMessages({cid: messages}); /// Bulk updates the message data of multiple channels. Future bulkUpdateMessages(Map?> messages); /// Updates the pinned message data of a particular channel [cid] with /// the new [messages] data - Future updatePinnedMessages(String cid, List messages) => - bulkUpdatePinnedMessages({cid: messages}); + Future updatePinnedMessages(String cid, List messages) => bulkUpdatePinnedMessages({cid: messages}); /// Bulk updates the message data of multiple channels. Future bulkUpdatePinnedMessages(Map?> messages); @@ -235,16 +230,14 @@ abstract class ChatPersistenceClient { /// Updates all the members of a particular channle [cid] /// with the new [members] data - Future updateMembers(String cid, List members) => - bulkUpdateMembers({cid: members}); + Future updateMembers(String cid, List members) => bulkUpdateMembers({cid: members}); /// Bulk updates the members data of multiple channels. Future bulkUpdateMembers(Map?> members); /// Updates the read data of a particular channel [cid] with /// the new [reads] data - Future updateReads(String cid, List reads) => - bulkUpdateReads({cid: reads}); + Future updateReads(String cid, List reads) => bulkUpdateReads({cid: reads}); /// Bulk updates the read data of multiple channels. Future bulkUpdateReads(Map?> reads); @@ -314,8 +307,7 @@ abstract class ChatPersistenceClient { } /// Update the channel state data using [channelState] - Future updateChannelState(ChannelState channelState) => - updateChannelStates([channelState]); + Future updateChannelState(ChannelState channelState) => updateChannelStates([channelState]); /// Update list of channel states Future updateChannelStates(List channelStates) async { @@ -355,10 +347,10 @@ abstract class ChatPersistenceClient { final members = state.members; final messages = switch (CurrentPlatform.isWeb) { true => state.messages?.where( - (it) => !it.attachments.any( - (it) => it.uploadState != const UploadState.success(), - ), + (it) => !it.attachments.any( + (it) => it.uploadState != const UploadState.success(), ), + ), _ => state.messages, }; @@ -379,37 +371,45 @@ abstract class ChatPersistenceClient { reactions.addAll(messages?.expand(_expandReactions) ?? []); pinnedReactions.addAll(pinnedMessages?.expand(_expandReactions) ?? []); - polls.addAll([ - ...?messages?.map((it) => it.poll), - ...?pinnedMessages?.map((it) => it.poll), - ].withNullifyer); + polls.addAll( + [ + ...?messages?.map((it) => it.poll), + ...?pinnedMessages?.map((it) => it.poll), + ].withNullifyer, + ); pollVotesToDelete.addAll(polls.map((it) => it.id)); pollVotes.addAll(polls.expand(_expandPollVotes)); - drafts.addAll([ - state.draft, - ...?messages?.map((it) => it.draft), - ...?pinnedMessages?.map((it) => it.draft), - ].nonNulls); - - locations.addAll([ - ...?messages?.map((it) => it.sharedLocation), - ...?pinnedMessages?.map((it) => it.sharedLocation), - ].nonNulls); - - users.addAll([ - channel.createdBy, - ...?messages?.map((it) => it.user), - ...?pinnedMessages?.map((it) => it.user), - ...?reads?.map((it) => it.user), - ...?members?.map((it) => it.user), - ...reactions.map((it) => it.user), - ...pinnedReactions.map((it) => it.user), - ...polls.map((it) => it.createdBy), - ...pollVotes.map((it) => it.user), - ].withNullifyer); + drafts.addAll( + [ + state.draft, + ...?messages?.map((it) => it.draft), + ...?pinnedMessages?.map((it) => it.draft), + ].nonNulls, + ); + + locations.addAll( + [ + ...?messages?.map((it) => it.sharedLocation), + ...?pinnedMessages?.map((it) => it.sharedLocation), + ].nonNulls, + ); + + users.addAll( + [ + channel.createdBy, + ...?messages?.map((it) => it.user), + ...?pinnedMessages?.map((it) => it.user), + ...?reads?.map((it) => it.user), + ...?members?.map((it) => it.user), + ...reactions.map((it) => it.user), + ...pinnedReactions.map((it) => it.user), + ...polls.map((it) => it.createdBy), + ...pollVotes.map((it) => it.user), + ].withNullifyer, + ); } // Removing old members and reactions data as they may have diff --git a/packages/stream_chat/lib/src/event_type.dart b/packages/stream_chat/lib/src/event_type.dart index 3fb40be256..7941395501 100644 --- a/packages/stream_chat/lib/src/event_type.dart +++ b/packages/stream_chat/lib/src/event_type.dart @@ -52,23 +52,19 @@ class EventType { static const String channelDeleted = 'channel.deleted'; /// Event sent when a channel is deleted - static const String notificationChannelDeleted = - 'notification.channel_deleted'; + static const String notificationChannelDeleted = 'notification.channel_deleted'; /// Event sent when a channel is truncated static const String channelTruncated = 'channel.truncated'; /// Event sent when a channel is truncated - static const String notificationChannelTruncated = - 'notification.channel_truncated'; + static const String notificationChannelTruncated = 'notification.channel_truncated'; /// Event sent when the user is added to a channel - static const String notificationAddedToChannel = - 'notification.added_to_channel'; + static const String notificationAddedToChannel = 'notification.added_to_channel'; /// Event sent when the user is removed from a channel - static const String notificationRemovedFromChannel = - 'notification.removed_from_channel'; + static const String notificationRemovedFromChannel = 'notification.removed_from_channel'; /// Event sent when a channel is updated static const String channelUpdated = 'channel.updated'; @@ -104,8 +100,7 @@ class EventType { static const String connectionRecovered = 'connection.recovered'; /// Event sent when the user is accepts an invite - static const String notificationInviteAccepted = - 'notification.invite_accepted'; + static const String notificationInviteAccepted = 'notification.invite_accepted'; /// Event sent when the user is invited static const String notificationInvited = 'notification.invited'; @@ -153,8 +148,7 @@ class EventType { static const String threadUpdated = 'thread.updated'; /// Event sent when a new message is added to a thread. - static const String notificationThreadMessageNew = - 'notification.thread_message_new'; + static const String notificationThreadMessageNew = 'notification.thread_message_new'; /// Event sent when a draft message is either created or updated. static const String draftUpdated = 'draft.updated'; @@ -187,8 +181,7 @@ class EventType { static const String pushPreferenceUpdated = 'push_preference.updated'; /// Local event sent when channel push notification preference is updated. - static const String channelPushPreferenceUpdated = - 'channel.push_preference.updated'; + static const String channelPushPreferenceUpdated = 'channel.push_preference.updated'; /// Event sent when a message is marked as delivered. static const String messageDelivered = 'message.delivered'; diff --git a/packages/stream_chat/lib/src/ws/connect_user_details.g.dart b/packages/stream_chat/lib/src/ws/connect_user_details.g.dart index 435c4febfc..2a77459610 100644 --- a/packages/stream_chat/lib/src/ws/connect_user_details.g.dart +++ b/packages/stream_chat/lib/src/ws/connect_user_details.g.dart @@ -6,14 +6,12 @@ part of 'connect_user_details.dart'; // JsonSerializableGenerator // ************************************************************************** -Map _$ConnectUserDetailsToJson(ConnectUserDetails instance) => - { - 'id': instance.id, - if (instance.name case final value?) 'name': value, - if (instance.image case final value?) 'image': value, - if (instance.language case final value?) 'language': value, - if (instance.invisible case final value?) 'invisible': value, - if (instance.privacySettings?.toJson() case final value?) - 'privacy_settings': value, - if (instance.extraData case final value?) 'extra_data': value, - }; +Map _$ConnectUserDetailsToJson(ConnectUserDetails instance) => { + 'id': instance.id, + if (instance.name case final value?) 'name': value, + if (instance.image case final value?) 'image': value, + if (instance.language case final value?) 'language': value, + if (instance.invisible case final value?) 'invisible': value, + if (instance.privacySettings?.toJson() case final value?) 'privacy_settings': value, + if (instance.extraData case final value?) 'extra_data': value, +}; diff --git a/packages/stream_chat/lib/src/ws/websocket.dart b/packages/stream_chat/lib/src/ws/websocket.dart index d6cb82a716..40f18756e4 100644 --- a/packages/stream_chat/lib/src/ws/websocket.dart +++ b/packages/stream_chat/lib/src/ws/websocket.dart @@ -24,10 +24,11 @@ typedef EventHandler = void Function(Event); /// Typedef used for connecting to a websocket. Method returns a /// [WebSocketChannel] and accepts a connection [url] and an optional /// [Iterable] of `protocols`. -typedef WebSocketChannelProvider = WebSocketChannel Function( - Uri uri, { - Iterable? protocols, -}); +typedef WebSocketChannelProvider = + WebSocketChannel Function( + Uri uri, { + Iterable? protocols, + }); /// A WebSocket connection that reconnects upon failure. class WebSocket with TimerHelper { @@ -132,8 +133,7 @@ class WebSocket with TimerHelper { if (_webSocketChannel != null) { _closeWebSocketChannel(); } - _webSocketChannel = - webSocketChannelProvider?.call(uri) ?? WebSocketChannel.connect(uri); + _webSocketChannel = webSocketChannelProvider?.call(uri) ?? WebSocketChannel.connect(uri); _subscribeToWebSocketChannel(); } @@ -409,8 +409,7 @@ class WebSocket with TimerHelper { } void _onConnectionError(error, [stacktrace]) { - _logger?.warning( - '[onConnectionError] #ws; error occurred', error, stacktrace); + _logger?.warning('[onConnectionError] #ws; error occurred', error, stacktrace); StreamWebSocketError wsError; if (error is WebSocketChannelException) { diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 068cfd24e5..13ff93f99e 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -18,7 +18,7 @@ issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 + sdk: ^3.10.0 dependencies: async: ^2.11.0 diff --git a/packages/stream_chat/test/src/client/channel_delivery_reporter_test.dart b/packages/stream_chat/test/src/client/channel_delivery_reporter_test.dart index 424e00cec9..7f0a529f3e 100644 --- a/packages/stream_chat/test/src/client/channel_delivery_reporter_test.dart +++ b/packages/stream_chat/test/src/client/channel_delivery_reporter_test.dart @@ -59,15 +59,13 @@ void main() { expect(capturedDeliveries, hasLength(2)); expect( capturedDeliveries.any( - (d) => - d.channelCid == 'test:channel-1' && d.messageId == 'message-1', + (d) => d.channelCid == 'test:channel-1' && d.messageId == 'message-1', ), isTrue, ); expect( capturedDeliveries.any( - (d) => - d.channelCid == 'test:channel-2' && d.messageId == 'message-2', + (d) => d.channelCid == 'test:channel-2' && d.messageId == 'message-2', ), isTrue, ); diff --git a/packages/stream_chat/test/src/client/channel_test.dart b/packages/stream_chat/test/src/client/channel_test.dart index 46c20f8387..2ea1f896ed 100644 --- a/packages/stream_chat/test/src/client/channel_test.dart +++ b/packages/stream_chat/test/src/client/channel_test.dart @@ -91,8 +91,7 @@ void main() { expect(channel.extraData['image'], imageUrl); const newImage = 'https://getstream.io/new-image'; - final newChannelInstance = - Channel(client, channelType, channelId, image: newImage); + final newChannelInstance = Channel(client, channelType, channelId, image: newImage); expect(newChannelInstance.image, newImage); expect(newChannelInstance.extraData['image'], newImage); @@ -108,8 +107,7 @@ void main() { expect(channel.extraData['name'], name); const newName = 'New channel name'; - final newChannelInstance = - Channel(client, channelType, channelId, name: newName); + final newChannelInstance = Channel(client, channelType, channelId, name: newName); expect(newChannelInstance.name, newName); expect(newChannelInstance.extraData['name'], newName); @@ -147,13 +145,10 @@ void main() { // mock persistence client final channelThreads = >{}; - when(() => client.chatPersistenceClient.getChannelThreads(channelCid)) - .thenAnswer((_) async => channelThreads); + when(() => client.chatPersistenceClient.getChannelThreads(channelCid)).thenAnswer((_) async => channelThreads); final channelState = _generateChannelState(channelId, channelType); - when(() => client.chatPersistenceClient.getChannelStateByCid(channelCid)) - .thenAnswer((_) async => channelState); - when(() => client.chatPersistenceClient.updateMessages(channelCid, any())) - .thenAnswer((_) => Future.value()); + when(() => client.chatPersistenceClient.getChannelStateByCid(channelCid)).thenAnswer((_) async => channelState); + when(() => client.chatPersistenceClient.updateMessages(channelCid, any())).thenAnswer((_) => Future.value()); // client logger when(() => client.logger).thenReturn(_createLogger('mock-client-logger')); @@ -256,14 +251,15 @@ void main() { user: client.state.currentUser, ); - final sendMessageResponse = SendMessageResponse() - ..message = message.copyWith(state: MessageState.sent); + final sendMessageResponse = SendMessageResponse()..message = message.copyWith(state: MessageState.sent); - when(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - )).thenAnswer((_) async => sendMessageResponse); + when( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + ), + ).thenAnswer((_) async => sendMessageResponse); expectLater( // skipping first seed message list -> [] messages @@ -289,245 +285,262 @@ void main() { expect(res, isNotNull); expect(res.message.id, message.id); - verify(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - )).called(1); + verify( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + ), + ).called(1); }); test( - 'should handle StreamChatNetworkError by adding message to retry queue with skipPush: true, skipEnrichUrl: false', - () async { - final message = Message( - id: 'test-message-id', - text: 'Hello world!', - user: client.state.currentUser, - ); + 'should handle StreamChatNetworkError by adding message to retry queue with skipPush: true, skipEnrichUrl: false', + () async { + final message = Message( + id: 'test-message-id', + text: 'Hello world!', + user: client.state.currentUser, + ); - when(() => client.sendMessage( + when( + () => client.sendMessage( any(that: isSameMessageAs(message)), channelId, channelType, skipPush: true, - )).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); + ), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); - expectLater( - // skipping first seed message list -> [] messages - channel.state?.messagesStream.skip(1), - emitsInOrder([ - [ - isSameMessageAs( - message.copyWith(state: MessageState.sending), - matchMessageState: true, - ), - ], - [ - isSameMessageAs( - message.copyWith( - state: MessageState.sendingFailed( - skipPush: true, - skipEnrichUrl: false, + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(state: MessageState.sending), + matchMessageState: true, + ), + ], + [ + isSameMessageAs( + message.copyWith( + state: MessageState.sendingFailed( + skipPush: true, + skipEnrichUrl: false, + ), ), + matchMessageState: true, ), - matchMessageState: true, - ), - ], - ]), - ); - - try { - await channel.sendMessage( - message, - skipPush: true, + ], + ]), ); - } catch (e) { - expect(e, isA()); - final networkError = e as StreamChatNetworkError; - expect(networkError.code, equals(ChatErrorCode.notAllowed.code)); - } - }); + try { + await channel.sendMessage( + message, + skipPush: true, + ); + } catch (e) { + expect(e, isA()); + + final networkError = e as StreamChatNetworkError; + expect(networkError.code, equals(ChatErrorCode.notAllowed.code)); + } + }, + ); test( - 'should handle StreamChatNetworkError by adding message to retry queue with skipPush: true, skipEnrichUrl: true', - () async { - final message = Message( - id: 'test-message-id-2', - text: 'Hello world!', - user: client.state.currentUser, - ); + 'should handle StreamChatNetworkError by adding message to retry queue with skipPush: true, skipEnrichUrl: true', + () async { + final message = Message( + id: 'test-message-id-2', + text: 'Hello world!', + user: client.state.currentUser, + ); - when(() => client.sendMessage( + when( + () => client.sendMessage( any(that: isSameMessageAs(message)), channelId, channelType, skipPush: true, skipEnrichUrl: true, - )).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); + ), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); - expectLater( - // skipping first seed message list -> [] messages - channel.state?.messagesStream.skip(1), - emitsInOrder([ - [ - isSameMessageAs( - message.copyWith(state: MessageState.sending), - matchMessageState: true, - ), - ], - [ - isSameMessageAs( - message.copyWith( - state: MessageState.sendingFailed( - skipPush: true, - skipEnrichUrl: true, + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(state: MessageState.sending), + matchMessageState: true, + ), + ], + [ + isSameMessageAs( + message.copyWith( + state: MessageState.sendingFailed( + skipPush: true, + skipEnrichUrl: true, + ), ), + matchMessageState: true, ), - matchMessageState: true, - ), - ], - ]), - ); - - try { - await channel.sendMessage( - message, - skipPush: true, - skipEnrichUrl: true, + ], + ]), ); - } catch (e) { - expect(e, isA()); - final networkError = e as StreamChatNetworkError; - expect(networkError.code, equals(ChatErrorCode.notAllowed.code)); - } - }); + try { + await channel.sendMessage( + message, + skipPush: true, + skipEnrichUrl: true, + ); + } catch (e) { + expect(e, isA()); + + final networkError = e as StreamChatNetworkError; + expect(networkError.code, equals(ChatErrorCode.notAllowed.code)); + } + }, + ); test( - 'should handle StreamChatNetworkError by adding message to retry queue with skipPush: false, skipEnrichUrl: true', - () async { - final message = Message( - id: 'test-message-id-3', - text: 'Hello world!', - user: client.state.currentUser, - ); + 'should handle StreamChatNetworkError by adding message to retry queue with skipPush: false, skipEnrichUrl: true', + () async { + final message = Message( + id: 'test-message-id-3', + text: 'Hello world!', + user: client.state.currentUser, + ); - when(() => client.sendMessage( + when( + () => client.sendMessage( any(that: isSameMessageAs(message)), channelId, channelType, skipEnrichUrl: true, - )).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); + ), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); - expectLater( - // skipping first seed message list -> [] messages - channel.state?.messagesStream.skip(1), - emitsInOrder([ - [ - isSameMessageAs( - message.copyWith(state: MessageState.sending), - matchMessageState: true, - ), - ], - [ - isSameMessageAs( - message.copyWith( - state: MessageState.sendingFailed( - skipPush: false, - skipEnrichUrl: true, + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(state: MessageState.sending), + matchMessageState: true, + ), + ], + [ + isSameMessageAs( + message.copyWith( + state: MessageState.sendingFailed( + skipPush: false, + skipEnrichUrl: true, + ), ), + matchMessageState: true, ), - matchMessageState: true, - ), - ], - ]), - ); - - try { - await channel.sendMessage( - message, - skipEnrichUrl: true, + ], + ]), ); - } catch (e) { - expect(e, isA()); - final networkError = e as StreamChatNetworkError; - expect(networkError.code, equals(ChatErrorCode.notAllowed.code)); - } - }); + try { + await channel.sendMessage( + message, + skipEnrichUrl: true, + ); + } catch (e) { + expect(e, isA()); + + final networkError = e as StreamChatNetworkError; + expect(networkError.code, equals(ChatErrorCode.notAllowed.code)); + } + }, + ); test( - 'should handle StreamChatNetworkError by adding message to retry queue with skipPush: false, skipEnrichUrl: false', - () async { - final message = Message( - id: 'test-message-id-4', - text: 'Hello world!', - user: client.state.currentUser, - ); + 'should handle StreamChatNetworkError by adding message to retry queue with skipPush: false, skipEnrichUrl: false', + () async { + final message = Message( + id: 'test-message-id-4', + text: 'Hello world!', + user: client.state.currentUser, + ); - when(() => client.sendMessage( + when( + () => client.sendMessage( any(that: isSameMessageAs(message)), channelId, channelType, - )).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); + ), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); - expectLater( - // skipping first seed message list -> [] messages - channel.state?.messagesStream.skip(1), - emitsInOrder([ - [ - isSameMessageAs( - message.copyWith(state: MessageState.sending), - matchMessageState: true, - ), - ], - [ - isSameMessageAs( - message.copyWith( - state: MessageState.sendingFailed( - skipPush: false, - skipEnrichUrl: false, + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(state: MessageState.sending), + matchMessageState: true, + ), + ], + [ + isSameMessageAs( + message.copyWith( + state: MessageState.sendingFailed( + skipPush: false, + skipEnrichUrl: false, + ), ), + matchMessageState: true, ), - matchMessageState: true, - ), - ], - ]), - ); - - try { - await channel.sendMessage( - message, + ], + ]), ); - } catch (e) { - expect(e, isA()); - final networkError = e as StreamChatNetworkError; - expect(networkError.code, equals(ChatErrorCode.notAllowed.code)); - } - }); + try { + await channel.sendMessage( + message, + ); + } catch (e) { + expect(e, isA()); - test('should update message state even when non-retriable error occurs', - () async { + final networkError = e as StreamChatNetworkError; + expect(networkError.code, equals(ChatErrorCode.notAllowed.code)); + } + }, + ); + + test('should update message state even when non-retriable error occurs', () async { final message = Message( id: 'test-message-id', text: 'Hello world!', user: client.state.currentUser, ); - when(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - )).thenThrow(StreamChatNetworkError.raw( - code: ChatErrorCode.inputError.code, - message: 'Input error', - data: ErrorResponse() - ..code = ChatErrorCode.inputError.code - ..message = 'Input error' - ..statusCode = 400, - )); + when( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + ), + ).thenThrow( + StreamChatNetworkError.raw( + code: ChatErrorCode.inputError.code, + message: 'Input error', + data: ErrorResponse() + ..code = ChatErrorCode.inputError.code + ..message = 'Input error' + ..statusCode = 400, + ), + ); expectLater( // skipping first seed message list -> [] messages @@ -549,7 +562,7 @@ void main() { ), matchMessageState: true, ), - ] + ], ]), ); @@ -578,36 +591,43 @@ void main() { final sendImageResponse = SendImageResponse()..file = 'test-image-url'; final sendFileResponse = SendFileResponse()..file = 'test-file-url'; - when(() => client.sendImage( - any(), - channelId, - channelType, - onSendProgress: any(named: 'onSendProgress'), - cancelToken: any(named: 'cancelToken'), - extraData: any(named: 'extraData'), - )).thenAnswer((_) async => sendImageResponse); + when( + () => client.sendImage( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + extraData: any(named: 'extraData'), + ), + ).thenAnswer((_) async => sendImageResponse); - when(() => client.sendFile( - any(), - channelId, - channelType, - onSendProgress: any(named: 'onSendProgress'), - cancelToken: any(named: 'cancelToken'), - extraData: any(named: 'extraData'), - )).thenAnswer((_) async => sendFileResponse); + when( + () => client.sendFile( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + extraData: any(named: 'extraData'), + ), + ).thenAnswer((_) async => sendFileResponse); - when(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - )).thenAnswer((_) async => SendMessageResponse() - ..message = message.copyWith( - attachments: attachments - .map((it) => - it.copyWith(uploadState: const UploadState.success())) - .toList(growable: false), - state: MessageState.sent, - )); + when( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + ), + ).thenAnswer( + (_) async => SendMessageResponse() + ..message = message.copyWith( + attachments: attachments + .map((it) => it.copyWith(uploadState: const UploadState.success())) + .toList(growable: false), + state: MessageState.sent, + ), + ); expectLater( // skipping first seed message list -> [] messages @@ -619,10 +639,7 @@ void main() { isSameMessageAs( message.copyWith( state: MessageState.sending, - attachments: [ - ...attachments.map((it) => it.copyWith( - uploadState: const UploadState.preparing())) - ], + attachments: [...attachments.map((it) => it.copyWith(uploadState: const UploadState.preparing()))], ), matchMessageState: true, matchAttachments: true, @@ -634,8 +651,8 @@ void main() { isSameMessageAs( message.copyWith( state: MessageState.sending, - attachments: [...attachments]..[0] = - attachments[0].copyWith( + attachments: [...attachments] + ..[0] = attachments[0].copyWith( uploadState: const UploadState.success(), ), ), @@ -667,10 +684,7 @@ void main() { isSameMessageAs( message.copyWith( state: MessageState.sending, - attachments: [ - ...attachments.map((it) => - it.copyWith(uploadState: const UploadState.success())) - ], + attachments: [...attachments.map((it) => it.copyWith(uploadState: const UploadState.success()))], ), matchMessageState: true, matchAttachments: true, @@ -681,10 +695,7 @@ void main() { isSameMessageAs( message.copyWith( state: MessageState.sent, - attachments: [ - ...attachments.map((it) => - it.copyWith(uploadState: const UploadState.success())) - ], + attachments: [...attachments.map((it) => it.copyWith(uploadState: const UploadState.success()))], ), matchMessageState: true, matchAttachments: true, @@ -707,29 +718,35 @@ void main() { isTrue, ); - verify(() => client.sendImage( - any(), - channelId, - channelType, - onSendProgress: any(named: 'onSendProgress'), - cancelToken: any(named: 'cancelToken'), - extraData: any(named: 'extraData'), - )).called(2); + verify( + () => client.sendImage( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + extraData: any(named: 'extraData'), + ), + ).called(2); - verify(() => client.sendFile( - any(), - channelId, - channelType, - onSendProgress: any(named: 'onSendProgress'), - cancelToken: any(named: 'cancelToken'), - extraData: any(named: 'extraData'), - )).called(1); + verify( + () => client.sendFile( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + extraData: any(named: 'extraData'), + ), + ).called(1); - verify(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - )).called(1); + verify( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + ), + ).called(1); }); test('should not send if the message is invalid', () async { @@ -1127,11 +1144,13 @@ void main() { final draftMessage = DraftMessage(text: 'Draft message text'); setUp(() { - when(() => client.createDraft( - draftMessage, - channelId, - channelType, - )).thenAnswer( + when( + () => client.createDraft( + draftMessage, + channelId, + channelType, + ), + ).thenAnswer( (_) async => CreateDraftResponse() ..draft = Draft( channelCid: channelCid, @@ -1147,11 +1166,13 @@ void main() { expect(res, isNotNull); expect(res.draft.message, draftMessage); - verify(() => channel.client.createDraft( - draftMessage, - channelId, - channelType, - )).called(1); + verify( + () => channel.client.createDraft( + draftMessage, + channelId, + channelType, + ), + ).called(1); }); }); @@ -1159,11 +1180,13 @@ void main() { final draftMessage = DraftMessage(text: 'Draft message text'); setUp(() { - when(() => client.getDraft( - channelId, - channelType, - parentId: any(named: 'parentId'), - )).thenAnswer( + when( + () => client.getDraft( + channelId, + channelType, + parentId: any(named: 'parentId'), + ), + ).thenAnswer( (_) async => GetDraftResponse() ..draft = Draft( channelCid: channelCid, @@ -1179,10 +1202,12 @@ void main() { expect(res, isNotNull); expect(res.draft.message, draftMessage); - verify(() => channel.client.getDraft( - channelId, - channelType, - )).called(1); + verify( + () => channel.client.getDraft( + channelId, + channelType, + ), + ).called(1); }); test('with parentId should pass parentId to client', () async { @@ -1192,21 +1217,25 @@ void main() { expect(res, isNotNull); expect(res.draft.message, draftMessage); - verify(() => channel.client.getDraft( - channelId, - channelType, - parentId: parentId, - )).called(1); + verify( + () => channel.client.getDraft( + channelId, + channelType, + parentId: parentId, + ), + ).called(1); }); }); group('`.deleteDraft`', () { setUp(() { - when(() => client.deleteDraft( - channelId, - channelType, - parentId: any(named: 'parentId'), - )).thenAnswer((_) async => EmptyResponse()); + when( + () => client.deleteDraft( + channelId, + channelType, + parentId: any(named: 'parentId'), + ), + ).thenAnswer((_) async => EmptyResponse()); }); test('should call client.deleteDraft', () async { @@ -1214,10 +1243,12 @@ void main() { expect(res, isNotNull); - verify(() => channel.client.deleteDraft( - channelId, - channelType, - )).called(1); + verify( + () => channel.client.deleteDraft( + channelId, + channelType, + ), + ).called(1); }); test('with parentId should pass parentId to client', () async { @@ -1226,11 +1257,13 @@ void main() { expect(res, isNotNull); - verify(() => channel.client.deleteDraft( - channelId, - channelType, - parentId: parentId, - )).called(1); + verify( + () => channel.client.deleteDraft( + channelId, + channelType, + parentId: parentId, + ), + ).called(1); }); }); @@ -1238,10 +1271,12 @@ void main() { const messageId = 'test-message-id'; setUp(() { - when(() => client.createReminder( - messageId, - remindAt: any(named: 'remindAt'), - )).thenAnswer( + when( + () => client.createReminder( + messageId, + remindAt: any(named: 'remindAt'), + ), + ).thenAnswer( (_) async => CreateReminderResponse() ..reminder = MessageReminder( messageId: messageId, @@ -1271,10 +1306,12 @@ void main() { expect(res.reminder.messageId, messageId); expect(res.reminder.remindAt, remindAt); - verify(() => channel.client.createReminder( - messageId, - remindAt: remindAt, - )).called(1); + verify( + () => channel.client.createReminder( + messageId, + remindAt: remindAt, + ), + ).called(1); }); }); @@ -1282,10 +1319,12 @@ void main() { const messageId = 'test-message-id'; setUp(() { - when(() => client.updateReminder( - messageId, - remindAt: any(named: 'remindAt'), - )).thenAnswer( + when( + () => client.updateReminder( + messageId, + remindAt: any(named: 'remindAt'), + ), + ).thenAnswer( (_) async => UpdateReminderResponse() ..reminder = MessageReminder( messageId: messageId, @@ -1315,10 +1354,12 @@ void main() { expect(res.reminder.messageId, messageId); expect(res.reminder.remindAt, remindAt); - verify(() => channel.client.updateReminder( - messageId, - remindAt: remindAt, - )).called(1); + verify( + () => channel.client.updateReminder( + messageId, + remindAt: remindAt, + ), + ).called(1); }); }); @@ -1347,11 +1388,11 @@ void main() { state: MessageState.sent, ); - final updateMessageResponse = UpdateMessageResponse() - ..message = message; + final updateMessageResponse = UpdateMessageResponse()..message = message; - when(() => client.updateMessage(any(that: isSameMessageAs(message)))) - .thenAnswer((_) async => updateMessageResponse); + when( + () => client.updateMessage(any(that: isSameMessageAs(message))), + ).thenAnswer((_) async => updateMessageResponse); expectLater( // skipping first seed message list -> [] messages @@ -1377,9 +1418,11 @@ void main() { expect(res, isNotNull); expect(res.message.id, message.id); - verify(() => client.updateMessage( - any(that: isSameMessageAs(message)), - )).called(1); + verify( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + ), + ).called(1); }); test('with attachments should work just fine', () async { @@ -1400,34 +1443,41 @@ void main() { final sendImageResponse = SendImageResponse()..file = 'test-image-url'; final sendFileResponse = SendFileResponse()..file = 'test-file-url'; - when(() => client.sendImage( - any(), - channelId, - channelType, - onSendProgress: any(named: 'onSendProgress'), - cancelToken: any(named: 'cancelToken'), - extraData: any(named: 'extraData'), - )).thenAnswer((_) async => sendImageResponse); + when( + () => client.sendImage( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + extraData: any(named: 'extraData'), + ), + ).thenAnswer((_) async => sendImageResponse); - when(() => client.sendFile( - any(), - channelId, - channelType, - onSendProgress: any(named: 'onSendProgress'), - cancelToken: any(named: 'cancelToken'), - extraData: any(named: 'extraData'), - )).thenAnswer((_) async => sendFileResponse); + when( + () => client.sendFile( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + extraData: any(named: 'extraData'), + ), + ).thenAnswer((_) async => sendFileResponse); - when(() => client.updateMessage( - any(that: isSameMessageAs(message)), - )).thenAnswer((_) async => UpdateMessageResponse() - ..message = message.copyWith( - state: MessageState.sent, - attachments: attachments - .map((it) => - it.copyWith(uploadState: const UploadState.success())) - .toList(growable: false), - )); + when( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + ), + ).thenAnswer( + (_) async => UpdateMessageResponse() + ..message = message.copyWith( + state: MessageState.sent, + attachments: attachments + .map((it) => it.copyWith(uploadState: const UploadState.success())) + .toList(growable: false), + ), + ); expectLater( // skipping first seed message list -> [] messages @@ -1439,10 +1489,7 @@ void main() { isSameMessageAs( message.copyWith( state: MessageState.updating, - attachments: [ - ...attachments.map((it) => it.copyWith( - uploadState: const UploadState.preparing())) - ], + attachments: [...attachments.map((it) => it.copyWith(uploadState: const UploadState.preparing()))], ), matchMessageState: true, matchAttachments: true, @@ -1454,8 +1501,8 @@ void main() { isSameMessageAs( message.copyWith( state: MessageState.updating, - attachments: [...attachments]..[0] = - attachments[0].copyWith( + attachments: [...attachments] + ..[0] = attachments[0].copyWith( uploadState: const UploadState.success(), ), ), @@ -1487,10 +1534,7 @@ void main() { isSameMessageAs( message.copyWith( state: MessageState.updating, - attachments: [ - ...attachments.map((it) => - it.copyWith(uploadState: const UploadState.success())) - ], + attachments: [...attachments.map((it) => it.copyWith(uploadState: const UploadState.success()))], ), matchMessageState: true, matchAttachments: true, @@ -1501,10 +1545,7 @@ void main() { isSameMessageAs( message.copyWith( state: MessageState.updated, - attachments: [ - ...attachments.map((it) => - it.copyWith(uploadState: const UploadState.success())) - ], + attachments: [...attachments.map((it) => it.copyWith(uploadState: const UploadState.success()))], ), matchMessageState: true, matchAttachments: true, @@ -1527,41 +1568,47 @@ void main() { isTrue, ); - verify(() => client.sendImage( - any(), - channelId, - channelType, - onSendProgress: any(named: 'onSendProgress'), - cancelToken: any(named: 'cancelToken'), - extraData: any(named: 'extraData'), - )).called(2); + verify( + () => client.sendImage( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + extraData: any(named: 'extraData'), + ), + ).called(2); - verify(() => client.sendFile( - any(), - channelId, - channelType, - onSendProgress: any(named: 'onSendProgress'), - cancelToken: any(named: 'cancelToken'), - extraData: any(named: 'extraData'), - )).called(1); + verify( + () => client.sendFile( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + extraData: any(named: 'extraData'), + ), + ).called(1); - verify(() => client.updateMessage( - any(that: isSameMessageAs(message)), - )).called(1); + verify( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + ), + ).called(1); }); - test( - 'should update message state even when error is not StreamChatNetworkError', - () async { + test('should update message state even when error is not StreamChatNetworkError', () async { final message = Message( id: 'test-message-id-error-1', state: MessageState.sent, ); - when(() => client.updateMessage( - any(that: isSameMessageAs(message)), - skipEnrichUrl: true, - )).thenThrow(ArgumentError('Invalid argument')); + when( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + skipEnrichUrl: true, + ), + ).thenThrow(ArgumentError('Invalid argument')); expectLater( // skipping first seed message list -> [] messages @@ -1583,7 +1630,7 @@ void main() { ), matchMessageState: true, ), - ] + ], ]), ); @@ -1595,123 +1642,132 @@ void main() { }); test( - 'should add message to retry queue when retriable StreamChatNetworkError occurs with skipPush: false, skipEnrichUrl: true', - () async { - final message = Message( - id: 'test-message-id-retry-1', - state: MessageState.sent, - ); + 'should add message to retry queue when retriable StreamChatNetworkError occurs with skipPush: false, skipEnrichUrl: true', + () async { + final message = Message( + id: 'test-message-id-retry-1', + state: MessageState.sent, + ); - // Create a retriable error (data == null) - when(() => client.updateMessage( + // Create a retriable error (data == null) + when( + () => client.updateMessage( any(that: isSameMessageAs(message)), skipEnrichUrl: true, - )).thenThrow(StreamChatNetworkError.raw( - code: ChatErrorCode.requestTimeout.code, - message: 'Request timed out', - )); + ), + ).thenThrow( + StreamChatNetworkError.raw( + code: ChatErrorCode.requestTimeout.code, + message: 'Request timed out', + ), + ); - expectLater( - // skipping first seed message list -> [] messages - channel.state?.messagesStream.skip(1), - emitsInOrder([ - [ - isSameMessageAs( - message.copyWith(state: MessageState.updating), - matchMessageState: true, - ), - ], - [ - isSameMessageAs( - message.copyWith( - state: MessageState.updatingFailed( - skipPush: false, - skipEnrichUrl: true, + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(state: MessageState.updating), + matchMessageState: true, + ), + ], + [ + isSameMessageAs( + message.copyWith( + state: MessageState.updatingFailed( + skipPush: false, + skipEnrichUrl: true, + ), ), + matchMessageState: true, ), - matchMessageState: true, - ), - ], - ]), - ); + ], + ]), + ); - try { - await channel.updateMessage(message, skipEnrichUrl: true); - } catch (e) { - expect(e, isA()); + try { + await channel.updateMessage(message, skipEnrichUrl: true); + } catch (e) { + expect(e, isA()); - final networkError = e as StreamChatNetworkError; - expect(networkError.code, equals(ChatErrorCode.requestTimeout.code)); - expect(networkError.isRetriable, isTrue); - } - }); + final networkError = e as StreamChatNetworkError; + expect(networkError.code, equals(ChatErrorCode.requestTimeout.code)); + expect(networkError.isRetriable, isTrue); + } + }, + ); test( - 'should add message to retry queue when retriable StreamChatNetworkError occurs with skipPush: true, skipEnrichUrl: false', - () async { - final message = Message( - id: 'test-message-id-retry-2', - state: MessageState.sent, - ); + 'should add message to retry queue when retriable StreamChatNetworkError occurs with skipPush: true, skipEnrichUrl: false', + () async { + final message = Message( + id: 'test-message-id-retry-2', + state: MessageState.sent, + ); - // Create a retriable error (data == null) - when(() => client.updateMessage( + // Create a retriable error (data == null) + when( + () => client.updateMessage( any(that: isSameMessageAs(message)), skipPush: true, - )).thenThrow(StreamChatNetworkError.raw( - code: ChatErrorCode.internalSystemError.code, - message: 'Internal system error', - )); + ), + ).thenThrow( + StreamChatNetworkError.raw( + code: ChatErrorCode.internalSystemError.code, + message: 'Internal system error', + ), + ); - expectLater( - // skipping first seed message list -> [] messages - channel.state?.messagesStream.skip(1), - emitsInOrder([ - [ - isSameMessageAs( - message.copyWith(state: MessageState.updating), - matchMessageState: true, - ), - ], - [ - isSameMessageAs( - message.copyWith( - state: MessageState.updatingFailed( - skipPush: true, - skipEnrichUrl: false, + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(state: MessageState.updating), + matchMessageState: true, + ), + ], + [ + isSameMessageAs( + message.copyWith( + state: MessageState.updatingFailed( + skipPush: true, + skipEnrichUrl: false, + ), ), + matchMessageState: true, ), - matchMessageState: true, - ), - ], - ]), - ); + ], + ]), + ); - try { - await channel.updateMessage(message, skipPush: true); - } catch (e) { - expect(e, isA()); + try { + await channel.updateMessage(message, skipPush: true); + } catch (e) { + expect(e, isA()); - final networkError = e as StreamChatNetworkError; - expect(networkError.code, - equals(ChatErrorCode.internalSystemError.code)); - expect(networkError.isRetriable, isTrue); - } - }); + final networkError = e as StreamChatNetworkError; + expect(networkError.code, equals(ChatErrorCode.internalSystemError.code)); + expect(networkError.isRetriable, isTrue); + } + }, + ); - test( - 'should handle non-retriable StreamChatNetworkError with skipPush: true, skipEnrichUrl: true', - () async { + test('should handle non-retriable StreamChatNetworkError with skipPush: true, skipEnrichUrl: true', () async { final message = Message( id: 'test-message-id-error-2', state: MessageState.sent, ); - when(() => client.updateMessage( - any(that: isSameMessageAs(message)), - skipPush: true, - skipEnrichUrl: true, - )).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); + when( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + skipPush: true, + skipEnrichUrl: true, + ), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); expectLater( // skipping first seed message list -> [] messages @@ -1751,17 +1807,17 @@ void main() { } }); - test( - 'should handle non-retriable StreamChatNetworkError with skipPush: false, skipEnrichUrl: false', - () async { + test('should handle non-retriable StreamChatNetworkError with skipPush: false, skipEnrichUrl: false', () async { final message = Message( id: 'test-message-id-error-3', state: MessageState.sent, ); - when(() => client.updateMessage( - any(that: isSameMessageAs(message)), - )).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); + when( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + ), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); expectLater( // skipping first seed message list -> [] messages @@ -1857,9 +1913,7 @@ void main() { }); group('`.partialUpdateMessage` error handling', () { - test( - 'should update message state even when error is not StreamChatNetworkError', - () async { + test('should update message state even when error is not StreamChatNetworkError', () async { final message = Message( id: 'test-message-id-error-partial-1', state: MessageState.sent, @@ -1904,7 +1958,7 @@ void main() { matchText: true, matchMessageState: true, ), - ] + ], ]), ); @@ -1920,151 +1974,154 @@ void main() { }); test( - 'should add message to retry queue when retriable StreamChatNetworkError occurs with skipEnrichUrl: true', - () async { - final message = Message( - id: 'test-message-id-retry-partial-1', - state: MessageState.sent, - ); + 'should add message to retry queue when retriable StreamChatNetworkError occurs with skipEnrichUrl: true', + () async { + final message = Message( + id: 'test-message-id-retry-partial-1', + state: MessageState.sent, + ); - // Add message to channel state first - channel.state?.updateMessage(message); + // Add message to channel state first + channel.state?.updateMessage(message); - const set = {'text': 'Update Message text'}; - const unset = ['pinExpires']; + const set = {'text': 'Update Message text'}; + const unset = ['pinExpires']; - // Create a retriable error (data == null) - when( - () => client.partialUpdateMessage( - message.id, - set: set, - unset: unset, - skipEnrichUrl: true, - ), - ).thenThrow(StreamChatNetworkError.raw( - code: ChatErrorCode.requestTimeout.code, - message: 'Request timed out', - )); + // Create a retriable error (data == null) + when( + () => client.partialUpdateMessage( + message.id, + set: set, + unset: unset, + skipEnrichUrl: true, + ), + ).thenThrow( + StreamChatNetworkError.raw( + code: ChatErrorCode.requestTimeout.code, + message: 'Request timed out', + ), + ); - expectLater( - // skipping first seed message list -> [] messages - channel.state?.messagesStream.skip(1), - emitsInOrder([ - [ - isSameMessageAs( - message.copyWith( - state: MessageState.updating, + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith( + state: MessageState.updating, + ), + matchText: true, + matchMessageState: true, ), - matchText: true, - matchMessageState: true, - ), - ], - [ - isSameMessageAs( - message.copyWith( - state: MessageState.partialUpdatingFailed( - set: set, - unset: unset, - skipEnrichUrl: true, + ], + [ + isSameMessageAs( + message.copyWith( + state: MessageState.partialUpdatingFailed( + set: set, + unset: unset, + skipEnrichUrl: true, + ), ), + matchText: true, + matchMessageState: true, ), - matchText: true, - matchMessageState: true, - ), - ], - ]), - ); - - try { - await channel.partialUpdateMessage( - message, - set: set, - unset: unset, - skipEnrichUrl: true, + ], + ]), ); - } catch (e) { - expect(e, isA()); - final networkError = e as StreamChatNetworkError; - expect(networkError.code, equals(ChatErrorCode.requestTimeout.code)); - expect(networkError.isRetriable, isTrue); - } - }); + try { + await channel.partialUpdateMessage( + message, + set: set, + unset: unset, + skipEnrichUrl: true, + ); + } catch (e) { + expect(e, isA()); + + final networkError = e as StreamChatNetworkError; + expect(networkError.code, equals(ChatErrorCode.requestTimeout.code)); + expect(networkError.isRetriable, isTrue); + } + }, + ); test( - 'should add message to retry queue when retriable StreamChatNetworkError occurs with skipEnrichUrl: false', - () async { - final message = Message( - id: 'test-message-id-retry-partial-2', - state: MessageState.sent, - ); + 'should add message to retry queue when retriable StreamChatNetworkError occurs with skipEnrichUrl: false', + () async { + final message = Message( + id: 'test-message-id-retry-partial-2', + state: MessageState.sent, + ); - // Add message to channel state first - channel.state?.updateMessage(message); + // Add message to channel state first + channel.state?.updateMessage(message); - const set = {'text': 'Update Message text'}; - const unset = ['pinExpires']; + const set = {'text': 'Update Message text'}; + const unset = ['pinExpires']; - // Create a retriable error (data == null) - when( - () => client.partialUpdateMessage( - message.id, - set: set, - unset: unset, - ), - ).thenThrow(StreamChatNetworkError.raw( - code: ChatErrorCode.internalSystemError.code, - message: 'Internal system error', - )); + // Create a retriable error (data == null) + when( + () => client.partialUpdateMessage( + message.id, + set: set, + unset: unset, + ), + ).thenThrow( + StreamChatNetworkError.raw( + code: ChatErrorCode.internalSystemError.code, + message: 'Internal system error', + ), + ); - expectLater( - // skipping first seed message list -> [] messages - channel.state?.messagesStream.skip(1), - emitsInOrder([ - [ - isSameMessageAs( - message.copyWith( - state: MessageState.updating, + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith( + state: MessageState.updating, + ), + matchText: true, + matchMessageState: true, ), - matchText: true, - matchMessageState: true, - ), - ], - [ - isSameMessageAs( - message.copyWith( - state: MessageState.partialUpdatingFailed( - set: set, - unset: unset, - skipEnrichUrl: false, + ], + [ + isSameMessageAs( + message.copyWith( + state: MessageState.partialUpdatingFailed( + set: set, + unset: unset, + skipEnrichUrl: false, + ), ), + matchText: true, + matchMessageState: true, ), - matchText: true, - matchMessageState: true, - ), - ], - ]), - ); - - try { - await channel.partialUpdateMessage( - message, - set: set, - unset: unset, + ], + ]), ); - } catch (e) { - expect(e, isA()); - final networkError = e as StreamChatNetworkError; - expect(networkError.code, - equals(ChatErrorCode.internalSystemError.code)); - expect(networkError.isRetriable, isTrue); - } - }); + try { + await channel.partialUpdateMessage( + message, + set: set, + unset: unset, + ); + } catch (e) { + expect(e, isA()); + + final networkError = e as StreamChatNetworkError; + expect(networkError.code, equals(ChatErrorCode.internalSystemError.code)); + expect(networkError.isRetriable, isTrue); + } + }, + ); - test( - 'should handle non-retriable StreamChatNetworkError with skipEnrichUrl: true', - () async { + test('should handle non-retriable StreamChatNetworkError with skipEnrichUrl: true', () async { final message = Message( id: 'test-message-id-error-partial-2', state: MessageState.sent, @@ -2129,9 +2186,7 @@ void main() { } }); - test( - 'should handle non-retriable StreamChatNetworkError with skipEnrichUrl: false', - () async { + test('should handle non-retriable StreamChatNetworkError with skipEnrichUrl: false', () async { final message = Message( id: 'test-message-id-error-partial-3', state: MessageState.sent, @@ -2204,8 +2259,7 @@ void main() { state: MessageState.sent, ); - when(() => client.deleteMessage(messageId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.deleteMessage(messageId)).thenAnswer((_) async => EmptyResponse()); expectLater( // skipping first seed message list -> [] messages @@ -2271,11 +2325,9 @@ void main() { verify(() => client.deleteMessage(messageId, hard: true)).called(1); - verify(() => client.deleteImage(any(), channelId, channelType)) - .called(2); + verify(() => client.deleteImage(any(), channelId, channelType)).called(2); - verify(() => client.deleteFile(any(), channelId, channelType)) - .called(1); + verify(() => client.deleteFile(any(), channelId, channelType)).called(1); }); test( @@ -2322,8 +2374,7 @@ void main() { state: MessageState.sent, ); - when(() => client.deleteMessageForMe(messageId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.deleteMessageForMe(messageId)).thenAnswer((_) async => EmptyResponse()); expectLater( // skipping first seed message list -> [] messages @@ -2387,19 +2438,22 @@ void main() { }); group('`.pinMessage`', () { - test('should work fine without passing timeoutOrExpirationDate', - () async { + test('should work fine without passing timeoutOrExpirationDate', () async { final message = Message(id: 'test-message-id'); - when(() => client.partialUpdateMessage( - message.id, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).thenAnswer((_) async => UpdateMessageResponse() - ..message = message.copyWith( - pinned: true, - pinExpires: null, - )); + when( + () => client.partialUpdateMessage( + message.id, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).thenAnswer( + (_) async => UpdateMessageResponse() + ..message = message.copyWith( + pinned: true, + pinExpires: null, + ), + ); expectLater( // skipping first seed message list -> [] messages @@ -2426,11 +2480,13 @@ void main() { expect(res.message.pinned, isTrue); expect(res.message.pinExpires, isNull); - verify(() => client.partialUpdateMessage( - message.id, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).called(1); + verify( + () => client.partialUpdateMessage( + message.id, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).called(1); }); test( @@ -2439,17 +2495,21 @@ void main() { final message = Message(id: 'test-message-id'); const timeoutOrExpirationDate = 300; // 300 seconds - when(() => client.partialUpdateMessage( - message.id, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).thenAnswer((_) async => UpdateMessageResponse() - ..message = message.copyWith( - pinned: true, - pinExpires: DateTime.now().add( - const Duration(seconds: timeoutOrExpirationDate), + when( + () => client.partialUpdateMessage( + message.id, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).thenAnswer( + (_) async => UpdateMessageResponse() + ..message = message.copyWith( + pinned: true, + pinExpires: DateTime.now().add( + const Duration(seconds: timeoutOrExpirationDate), + ), ), - )); + ); expectLater( // skipping first seed message list -> [] messages @@ -2479,11 +2539,13 @@ void main() { expect(res.message.pinned, isTrue); expect(res.message.pinExpires, isNotNull); - verify(() => client.partialUpdateMessage( - message.id, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).called(1); + verify( + () => client.partialUpdateMessage( + message.id, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).called(1); }, ); @@ -2491,18 +2553,21 @@ void main() { 'should work fine if passed timeoutOrExpirationDate as DateTime', () async { final message = Message(id: 'test-message-id'); - final timeoutOrExpirationDate = - DateTime.now().add(const Duration(days: 3)); // 3 days - - when(() => client.partialUpdateMessage( - message.id, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).thenAnswer((_) async => UpdateMessageResponse() - ..message = message.copyWith( - pinned: true, - pinExpires: timeoutOrExpirationDate, - )); + final timeoutOrExpirationDate = DateTime.now().add(const Duration(days: 3)); // 3 days + + when( + () => client.partialUpdateMessage( + message.id, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).thenAnswer( + (_) async => UpdateMessageResponse() + ..message = message.copyWith( + pinned: true, + pinExpires: timeoutOrExpirationDate, + ), + ); expectLater( // skipping first seed message list -> [] messages @@ -2533,11 +2598,13 @@ void main() { expect(res.message.pinExpires, isNotNull); expect(res.message.pinExpires, timeoutOrExpirationDate.toUtc()); - verify(() => client.partialUpdateMessage( - message.id, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).called(1); + verify( + () => client.partialUpdateMessage( + message.id, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).called(1); }, ); @@ -2562,11 +2629,12 @@ void main() { test('`.unpinMessage`', () async { final message = Message(id: 'test-message-id', pinned: true); - when(() => client.partialUpdateMessage( - message.id, - set: {'pinned': false}, - )).thenAnswer((_) async => UpdateMessageResponse() - ..message = message.copyWith(pinned: false)); + when( + () => client.partialUpdateMessage( + message.id, + set: {'pinned': false}, + ), + ).thenAnswer((_) async => UpdateMessageResponse()..message = message.copyWith(pinned: false)); expectLater( // skipping first seed message list -> [] messages @@ -2592,10 +2660,12 @@ void main() { expect(res, isNotNull); expect(res.message.pinned, isFalse); - verify(() => client.partialUpdateMessage( - message.id, - set: {'pinned': false}, - )).called(1); + verify( + () => client.partialUpdateMessage( + message.id, + set: {'pinned': false}, + ), + ).called(1); }); group('`.search`', () { @@ -2608,12 +2678,14 @@ void main() { final results = List.generate(3, (index) => GetMessageResponse()); - when(() => client.search( - filter, - query: query, - sort: any(named: 'sort'), - paginationParams: any(named: 'paginationParams'), - )).thenAnswer( + when( + () => client.search( + filter, + query: query, + sort: any(named: 'sort'), + paginationParams: any(named: 'paginationParams'), + ), + ).thenAnswer( (_) async => SearchMessagesResponse()..results = results, ); @@ -2626,12 +2698,14 @@ void main() { expect(res, isNotNull); expect(res.results.length, results.length); - verify(() => client.search( - filter, - query: query, - sort: any(named: 'sort'), - paginationParams: any(named: 'paginationParams'), - )).called(1); + verify( + () => client.search( + filter, + query: query, + sort: any(named: 'sort'), + paginationParams: any(named: 'paginationParams'), + ), + ).called(1); }); test('should work fine with `messageFilters`', () async { @@ -2641,12 +2715,14 @@ void main() { final results = List.generate(3, (index) => GetMessageResponse()); - when(() => client.search( - filter, - messageFilters: messageFilters, - sort: any(named: 'sort'), - paginationParams: any(named: 'paginationParams'), - )).thenAnswer( + when( + () => client.search( + filter, + messageFilters: messageFilters, + sort: any(named: 'sort'), + paginationParams: any(named: 'paginationParams'), + ), + ).thenAnswer( (_) async => SearchMessagesResponse()..results = results, ); @@ -2659,70 +2735,73 @@ void main() { expect(res, isNotNull); expect(res.results.length, results.length); - verify(() => client.search( - filter, - messageFilters: messageFilters, - sort: any(named: 'sort'), - paginationParams: any(named: 'paginationParams'), - )).called(1); + verify( + () => client.search( + filter, + messageFilters: messageFilters, + sort: any(named: 'sort'), + paginationParams: any(named: 'paginationParams'), + ), + ).called(1); }); }); test('`.deleteFile`', () async { const url = 'test-file-url'; - when(() => client.deleteFile(url, channelId, channelType, - cancelToken: any(named: 'cancelToken'))) - .thenAnswer((_) async => EmptyResponse()); + when( + () => client.deleteFile(url, channelId, channelType, cancelToken: any(named: 'cancelToken')), + ).thenAnswer((_) async => EmptyResponse()); final res = await channel.deleteFile(url); expect(res, isNotNull); - verify(() => client.deleteFile(url, channelId, channelType, - cancelToken: any(named: 'cancelToken'))).called(1); + verify(() => client.deleteFile(url, channelId, channelType, cancelToken: any(named: 'cancelToken'))).called(1); }); test('`.deleteImage`', () async { const url = 'test-image-url'; - when(() => client.deleteImage(url, channelId, channelType, - cancelToken: any(named: 'cancelToken'))) - .thenAnswer((_) async => EmptyResponse()); + when( + () => client.deleteImage(url, channelId, channelType, cancelToken: any(named: 'cancelToken')), + ).thenAnswer((_) async => EmptyResponse()); final res = await channel.deleteImage(url); expect(res, isNotNull); - verify(() => client.deleteImage(url, channelId, channelType, - cancelToken: any(named: 'cancelToken'))).called(1); + verify(() => client.deleteImage(url, channelId, channelType, cancelToken: any(named: 'cancelToken'))).called(1); }); test('`.stopAIResponse`', () async { final stopAIEvent = Event(type: EventType.aiIndicatorStop); - when(() => client.sendEvent( - channelId, - channelType, - any(that: isSameEventAs(stopAIEvent)), - )).thenAnswer((_) async => EmptyResponse()); + when( + () => client.sendEvent( + channelId, + channelType, + any(that: isSameEventAs(stopAIEvent)), + ), + ).thenAnswer((_) async => EmptyResponse()); final res = await channel.stopAIResponse(); expect(res, isNotNull); - verify(() => client.sendEvent( - channelId, - channelType, - any(that: isSameEventAs(stopAIEvent)), - )).called(1); + verify( + () => client.sendEvent( + channelId, + channelType, + any(that: isSameEventAs(stopAIEvent)), + ), + ).called(1); }); test('`.sendEvent`', () async { final event = Event(type: 'event.local'); - when(() => client.sendEvent(channelId, channelType, event)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.sendEvent(channelId, channelType, event)).thenAnswer((_) async => EmptyResponse()); final res = await channel.sendEvent(event); @@ -2801,8 +2880,9 @@ void main() { user: client.state.currentUser, ); - when(() => client.sendReaction(message.id, reaction)) - .thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + when( + () => client.sendReaction(message.id, reaction), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); expectLater( // skipping first seed message list -> [] messages @@ -2816,7 +2896,7 @@ void main() { type: ReactionGroup( count: 1, sumScores: 1, - ) + ), }, latestReactions: [reaction], ownReactions: [reaction], @@ -2863,7 +2943,7 @@ void main() { prevType: ReactionGroup( count: 1, sumScores: 1, - ) + ), }, state: MessageState.sent, ); @@ -2881,11 +2961,13 @@ void main() { const enforceUnique = true; - when(() => client.sendReaction( - messageId, - newReaction, - enforceUnique: enforceUnique, - )).thenAnswer( + when( + () => client.sendReaction( + messageId, + newReaction, + enforceUnique: enforceUnique, + ), + ).thenAnswer( (_) async => SendReactionResponse() ..message = newMessage ..reaction = newReaction, @@ -2915,11 +2997,13 @@ void main() { expect(res.reaction.type, type); expect(res.reaction.messageId, messageId); - verify(() => client.sendReaction( - messageId, - newReaction, - enforceUnique: enforceUnique, - )).called(1); + verify( + () => client.sendReaction( + messageId, + newReaction, + enforceUnique: enforceUnique, + ), + ).called(1); }, ); }); @@ -2959,7 +3043,7 @@ void main() { type: ReactionGroup( count: 1, sumScores: 1, - ) + ), }, latestReactions: [reaction], ownReactions: [reaction], @@ -2997,14 +3081,13 @@ void main() { user: client.state.currentUser, ); - when(() => client.sendReaction(message.id, reaction)) - .thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + when( + () => client.sendReaction(message.id, reaction), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); expectLater( // skipping first seed message list -> [] messages - channel.state?.threadsStream - .skip(1) - .map((event) => event['test-parent-id']), + channel.state?.threadsStream.skip(1).map((event) => event['test-parent-id']), emitsInOrder([ [ isSameMessageAs( @@ -3014,7 +3097,7 @@ void main() { type: ReactionGroup( count: 1, sumScores: 1, - ) + ), }, latestReactions: [reaction], ownReactions: [reaction], @@ -3065,7 +3148,7 @@ void main() { prevType: ReactionGroup( count: 1, sumScores: 1, - ) + ), }, state: MessageState.sent, ); @@ -3083,11 +3166,13 @@ void main() { const enforceUnique = true; - when(() => client.sendReaction( - messageId, - newReaction, - enforceUnique: enforceUnique, - )).thenAnswer( + when( + () => client.sendReaction( + messageId, + newReaction, + enforceUnique: enforceUnique, + ), + ).thenAnswer( (_) async => SendReactionResponse() ..message = newMessage ..reaction = newReaction, @@ -3095,9 +3180,7 @@ void main() { expectLater( // skipping first seed message list -> [] messages - channel.state?.threadsStream - .skip(1) - .map((event) => event['test-parent-id']), + channel.state?.threadsStream.skip(1).map((event) => event['test-parent-id']), emitsInOrder([ [ isSameMessageAs( @@ -3120,11 +3203,13 @@ void main() { expect(res.reaction.type, type); expect(res.reaction.messageId, messageId); - verify(() => client.sendReaction( - messageId, - newReaction, - enforceUnique: enforceUnique, - )).called(1); + verify( + () => client.sendReaction( + messageId, + newReaction, + enforceUnique: enforceUnique, + ), + ).called(1); }, ); }); @@ -3147,13 +3232,12 @@ void main() { type: ReactionGroup( count: 1, sumScores: 1, - ) + ), }, state: MessageState.sent, ); - when(() => client.deleteReaction(messageId, type)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.deleteReaction(messageId, type)).thenAnswer((_) async => EmptyResponse()); expectLater( // skipping first seed message list -> [] messages @@ -3199,13 +3283,14 @@ void main() { type: ReactionGroup( count: 1, sumScores: 1, - ) + ), }, state: MessageState.sent, ); - when(() => client.deleteReaction(messageId, type)) - .thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + when( + () => client.deleteReaction(messageId, type), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); expectLater( // skipping first seed message list -> [] messages @@ -3264,19 +3349,16 @@ void main() { type: ReactionGroup( count: 1, sumScores: 1, - ) + ), }, state: MessageState.sent, ); - when(() => client.deleteReaction(messageId, type)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.deleteReaction(messageId, type)).thenAnswer((_) async => EmptyResponse()); expectLater( // skipping first seed message list -> [] messages - channel.state?.threadsStream - .skip(1) - .map((event) => event['test-parent-id']), + channel.state?.threadsStream.skip(1).map((event) => event['test-parent-id']), emitsInOrder([ [ isSameMessageAs( @@ -3321,19 +3403,18 @@ void main() { type: ReactionGroup( count: 1, sumScores: 1, - ) + ), }, state: MessageState.sent, ); - when(() => client.deleteReaction(messageId, type)) - .thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + when( + () => client.deleteReaction(messageId, type), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); expectLater( // skipping first seed message list -> [] messages - channel.state?.threadsStream - .skip(1) - .map((event) => event['test-parent-id']), + channel.state?.threadsStream.skip(1).map((event) => event['test-parent-id']), emitsInOrder([ [ isSameMessageAs( @@ -3384,8 +3465,7 @@ void main() { extraData: channelData, ); - when(() => client.updateChannel(channelId, channelType, channelData, - message: any(named: 'message'))).thenAnswer( + when(() => client.updateChannel(channelId, channelType, channelData, message: any(named: 'message'))).thenAnswer( (_) async => UpdateChannelResponse() ..channel = channelModel ..message = updateMessage, @@ -3401,8 +3481,7 @@ void main() { expect(res.channel.extraData, channelData); expect(res.message?.id, updateMessage.id); - verify(() => client.updateChannel(channelId, channelType, channelData, - message: any(named: 'message'))).called(1); + verify(() => client.updateChannel(channelId, channelType, channelData, message: any(named: 'message'))).called(1); }); test('`.updateImage`', () async { @@ -3413,11 +3492,13 @@ void main() { extraData: {'image': image}, ); - when(() => client.updateChannelPartial( - channelId, - channelType, - set: {'image': image}, - )).thenAnswer( + when( + () => client.updateChannelPartial( + channelId, + channelType, + set: {'image': image}, + ), + ).thenAnswer( (_) async => PartialUpdateChannelResponse()..channel = channelModel, ); @@ -3426,11 +3507,13 @@ void main() { expect(res, isNotNull); expect(res.channel.extraData['image'], image); - verify(() => client.updateChannelPartial( - channelId, - channelType, - set: {'image': image}, - )).called(1); + verify( + () => client.updateChannelPartial( + channelId, + channelType, + set: {'image': image}, + ), + ).called(1); }); test('`.updateName`', () async { @@ -3441,11 +3524,13 @@ void main() { extraData: {'name': name}, ); - when(() => client.updateChannelPartial( - channelId, - channelType, - set: {'name': name}, - )).thenAnswer( + when( + () => client.updateChannelPartial( + channelId, + channelType, + set: {'name': name}, + ), + ).thenAnswer( (_) async => PartialUpdateChannelResponse()..channel = channelModel, ); @@ -3454,11 +3539,13 @@ void main() { expect(res, isNotNull); expect(res.channel.extraData['name'], name); - verify(() => client.updateChannelPartial( - channelId, - channelType, - set: {'name': name}, - )).called(1); + verify( + () => client.updateChannelPartial( + channelId, + channelType, + set: {'name': name}, + ), + ).called(1); }); test('`.updatePartial`', () async { @@ -3477,12 +3564,14 @@ void main() { }, ); - when(() => client.updateChannelPartial( - channelId, - channelType, - set: set, - unset: unset, - )).thenAnswer( + when( + () => client.updateChannelPartial( + channelId, + channelType, + set: set, + unset: unset, + ), + ).thenAnswer( (_) async => PartialUpdateChannelResponse()..channel = channelModel, ); @@ -3495,17 +3584,18 @@ void main() { {'coolness': 999, ...set}, ); - verify(() => client.updateChannelPartial( - channelId, - channelType, - set: set, - unset: unset, - )).called(1); + verify( + () => client.updateChannelPartial( + channelId, + channelType, + set: set, + unset: unset, + ), + ).called(1); }); test('`.delete`', () async { - when(() => client.deleteChannel(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.deleteChannel(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await channel.delete(); @@ -3515,8 +3605,7 @@ void main() { }); test('`.truncate`', () async { - when(() => client.truncateChannel(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.truncateChannel(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await channel.truncate(); @@ -3530,8 +3619,7 @@ void main() { final channelModel = ChannelModel(cid: channelCid); - when(() => client.acceptChannelInvite(channelId, channelType, - message: any(named: 'message'))).thenAnswer( + when(() => client.acceptChannelInvite(channelId, channelType, message: any(named: 'message'))).thenAnswer( (_) async => AcceptInviteResponse() ..channel = channelModel ..message = message, @@ -3543,8 +3631,7 @@ void main() { expect(res.channel.cid, channelModel.cid); expect(res.message?.id, message.id); - verify(() => client.acceptChannelInvite(channelId, channelType, - message: any(named: 'message'))).called(1); + verify(() => client.acceptChannelInvite(channelId, channelType, message: any(named: 'message'))).called(1); }); test('`.rejectInvite`', () async { @@ -3552,8 +3639,7 @@ void main() { final channelModel = ChannelModel(cid: channelCid); - when(() => client.rejectChannelInvite(channelId, channelType, - message: any(named: 'message'))).thenAnswer( + when(() => client.rejectChannelInvite(channelId, channelType, message: any(named: 'message'))).thenAnswer( (_) async => RejectInviteResponse() ..channel = channelModel ..message = message, @@ -3565,8 +3651,7 @@ void main() { expect(res.channel.cid, channelModel.cid); expect(res.message?.id, message.id); - verify(() => client.rejectChannelInvite(channelId, channelType, - message: any(named: 'message'))).called(1); + verify(() => client.rejectChannelInvite(channelId, channelType, message: any(named: 'message'))).called(1); }); test('`.addMembers`', () async { @@ -3574,16 +3659,14 @@ void main() { 3, (index) => Member(userId: 'test-member-id-$index'), ); - final memberIds = members - .map((it) => it.userId) - .whereType() - .toList(growable: false); + final memberIds = members.map((it) => it.userId).whereType().toList(growable: false); final message = Message(id: 'test-message-id', text: 'Members Added'); final channelModel = ChannelModel(cid: channelCid); - when(() => client.addChannelMembers(channelId, channelType, memberIds, - message: any(named: 'message'))).thenAnswer( + when( + () => client.addChannelMembers(channelId, channelType, memberIds, message: any(named: 'message')), + ).thenAnswer( (_) async => AddMembersResponse() ..channel = channelModel ..members = members @@ -3597,8 +3680,9 @@ void main() { expect(res.members.length, members.length); expect(res.message?.id, message.id); - verify(() => client.addChannelMembers(channelId, channelType, memberIds, - message: any(named: 'message'))).called(1); + verify( + () => client.addChannelMembers(channelId, channelType, memberIds, message: any(named: 'message')), + ).called(1); }); test('`.addMembers` with hideHistoryBefore', () async { @@ -3606,22 +3690,21 @@ void main() { 3, (index) => Member(userId: 'test-member-id-$index'), ); - final memberIds = members - .map((it) => it.userId) - .whereType() - .toList(growable: false); + final memberIds = members.map((it) => it.userId).whereType().toList(growable: false); final message = Message(id: 'test-message-id', text: 'Members Added'); final hideHistoryBefore = DateTime.parse('2024-01-01T00:00:00Z'); final channelModel = ChannelModel(cid: channelCid); - when(() => client.addChannelMembers( - channelId, - channelType, - memberIds, - message: message, - hideHistoryBefore: hideHistoryBefore, - )).thenAnswer( + when( + () => client.addChannelMembers( + channelId, + channelType, + memberIds, + message: message, + hideHistoryBefore: hideHistoryBefore, + ), + ).thenAnswer( (_) async => AddMembersResponse() ..channel = channelModel ..members = members @@ -3639,13 +3722,15 @@ void main() { expect(res.members.length, members.length); expect(res.message?.id, message.id); - verify(() => client.addChannelMembers( - channelId, - channelType, - memberIds, - message: message, - hideHistoryBefore: hideHistoryBefore, - )).called(1); + verify( + () => client.addChannelMembers( + channelId, + channelType, + memberIds, + message: message, + hideHistoryBefore: hideHistoryBefore, + ), + ).called(1); }); test('`.inviteMembers`', () async { @@ -3653,16 +3738,14 @@ void main() { 3, (index) => Member(userId: 'test-member-id-$index'), ); - final memberIds = members - .map((it) => it.userId) - .whereType() - .toList(growable: false); + final memberIds = members.map((it) => it.userId).whereType().toList(growable: false); final message = Message(id: 'test-message-id', text: 'Members Invited'); final channelModel = ChannelModel(cid: channelCid); - when(() => client.inviteChannelMembers(channelId, channelType, memberIds, - message: any(named: 'message'))).thenAnswer( + when( + () => client.inviteChannelMembers(channelId, channelType, memberIds, message: any(named: 'message')), + ).thenAnswer( (_) async => InviteMembersResponse() ..channel = channelModel ..members = members @@ -3676,9 +3759,9 @@ void main() { expect(res.members.length, members.length); expect(res.message?.id, message.id); - verify(() => client.inviteChannelMembers( - channelId, channelType, memberIds, - message: any(named: 'message'))).called(1); + verify( + () => client.inviteChannelMembers(channelId, channelType, memberIds, message: any(named: 'message')), + ).called(1); }); test('`.removeMembers`', () async { @@ -3686,16 +3769,14 @@ void main() { 3, (index) => Member(userId: 'test-member-id-$index'), ); - final memberIds = members - .map((it) => it.userId) - .whereType() - .toList(growable: false); + final memberIds = members.map((it) => it.userId).whereType().toList(growable: false); final message = Message(id: 'test-message-id', text: 'Members Removed'); final channelModel = ChannelModel(cid: channelCid); - when(() => client.removeChannelMembers(channelId, channelType, memberIds, - message: any(named: 'message'))).thenAnswer( + when( + () => client.removeChannelMembers(channelId, channelType, memberIds, message: any(named: 'message')), + ).thenAnswer( (_) async => RemoveMembersResponse() ..channel = channelModel ..members = members @@ -3709,9 +3790,9 @@ void main() { expect(res.members.length, members.length); expect(res.message?.id, message.id); - verify(() => client.removeChannelMembers( - channelId, channelType, memberIds, - message: any(named: 'message'))).called(1); + verify( + () => client.removeChannelMembers(channelId, channelType, memberIds, message: any(named: 'message')), + ).called(1); }); group('`.sendAction`', () { @@ -3766,15 +3847,17 @@ void main() { group('`.watch`', () { test('should work fine', () async { - when(() => client.queryChannel( - channelType, - channelId: channelId, - watch: true, - channelData: any(named: 'channelData'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).thenAnswer( + when( + () => client.queryChannel( + channelType, + channelId: channelId, + watch: true, + channelData: any(named: 'channelData'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).thenAnswer( (_) async => _generateChannelState(channelId, channelType), ); @@ -3784,27 +3867,31 @@ void main() { expect(res.channel, isNotNull); expect(res.channel?.cid, channelCid); - verify(() => client.queryChannel( - channelType, - channelId: channelId, - watch: true, - channelData: any(named: 'channelData'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).called(1); + verify( + () => client.queryChannel( + channelType, + channelId: channelId, + watch: true, + channelData: any(named: 'channelData'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).called(1); }); test('should rethrow if `.query` throws', () async { - when(() => client.queryChannel( - channelType, - channelId: channelId, - watch: true, - channelData: any(named: 'channelData'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + when( + () => client.queryChannel( + channelType, + channelId: channelId, + watch: true, + channelData: any(named: 'channelData'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); try { await channel.watch(); @@ -3812,28 +3899,28 @@ void main() { expect(e, isA()); } - verify(() => client.queryChannel( - channelType, - channelId: channelId, - watch: true, - channelData: any(named: 'channelData'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).called(1); + verify( + () => client.queryChannel( + channelType, + channelId: channelId, + watch: true, + channelData: any(named: 'channelData'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).called(1); }); }); test('`.stopWatching`', () async { - when(() => client.stopChannelWatching(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.stopChannelWatching(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await channel.stopWatching(); expect(res, isNotNull); - verify(() => client.stopChannelWatching(channelId, channelType)) - .called(1); + verify(() => client.stopChannelWatching(channelId, channelType)).called(1); }); test('`.getReplies`', () async { @@ -3892,8 +3979,7 @@ void main() { final messageIds = messages.map((it) => it.id).toList(growable: false); - when(() => client.getMessagesById(channelId, channelType, messageIds)) - .thenAnswer( + when(() => client.getMessagesById(channelId, channelType, messageIds)).thenAnswer( (_) async => GetMessagesByIdResponse()..messages = messages, ); @@ -4006,16 +4092,19 @@ void main() { channel.state!.updateChannelState(stateWithMessages); expect(channel.state!.messages, hasLength(3)); - final newState = _generateChannelState( - channelId, - channelType, - ).copyWith(messages: [ - Message(id: 'msg-before-1', text: 'Message before 1'), - Message(id: 'msg-before-2', text: 'Message before 2'), - Message(id: 'target-message-id', text: 'Target message'), - Message(id: 'msg-after-1', text: 'Message after 1'), - Message(id: 'msg-after-2', text: 'Message after 2'), - ]); + final newState = + _generateChannelState( + channelId, + channelType, + ).copyWith( + messages: [ + Message(id: 'msg-before-1', text: 'Message before 1'), + Message(id: 'msg-before-2', text: 'Message before 2'), + Message(id: 'target-message-id', text: 'Target message'), + Message(id: 'msg-after-1', text: 'Message after 1'), + Message(id: 'msg-after-2', text: 'Message after 2'), + ], + ); when( () => client.queryChannel( @@ -4064,16 +4153,19 @@ void main() { expect(channel.state!.messages, hasLength(3)); final targetDate = DateTime.now(); - final newState = _generateChannelState( - channelId, - channelType, - ).copyWith(messages: [ - Message(id: 'msg-before-1', text: 'Message before 1'), - Message(id: 'msg-before-2', text: 'Message before 2'), - Message(id: 'target-message', text: 'Target message'), - Message(id: 'msg-after-1', text: 'Message after 1'), - Message(id: 'msg-after-2', text: 'Message after 2'), - ]); + final newState = + _generateChannelState( + channelId, + channelType, + ).copyWith( + messages: [ + Message(id: 'msg-before-1', text: 'Message before 1'), + Message(id: 'msg-before-2', text: 'Message before 2'), + Message(id: 'target-message', text: 'Target message'), + Message(id: 'msg-after-1', text: 'Message after 1'), + Message(id: 'msg-after-2', text: 'Message after 2'), + ], + ); when( () => client.queryChannel( @@ -4172,28 +4264,32 @@ void main() { (index) => Member(userId: 'test-user-id-$index'), ); - when(() => client.queryMembers( - channelType, - channelId: channelId, - filter: filter, - members: any(named: 'members'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => QueryMembersResponse()..members = members); + when( + () => client.queryMembers( + channelType, + channelId: channelId, + filter: filter, + members: any(named: 'members'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => QueryMembersResponse()..members = members); final res = await channel.queryMembers(filter: filter); expect(res, isNotNull); expect(res.members.length, members.length); - verify(() => client.queryMembers( - channelType, - channelId: channelId, - filter: filter, - members: any(named: 'members'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).called(1); + verify( + () => client.queryMembers( + channelType, + channelId: channelId, + filter: filter, + members: any(named: 'members'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).called(1); }); test('`.queryBannedUsers`', () async { @@ -4207,59 +4303,70 @@ void main() { ), ); - when(() => client.queryBannedUsers( - filter: filter, - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => QueryBannedUsersResponse()..bans = bans); + when( + () => client.queryBannedUsers( + filter: filter, + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => QueryBannedUsersResponse()..bans = bans); final res = await channel.queryBannedUsers(); expect(res, isNotNull); expect(res.bans.length, bans.length); - verify(() => client.queryBannedUsers( - filter: filter, - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).called(1); + verify( + () => client.queryBannedUsers( + filter: filter, + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).called(1); }); test('`.mute`', () async { - when(() => client.muteChannel( - channelCid, - expiration: any(named: 'expiration'), - )).thenAnswer((_) async => EmptyResponse()); + when( + () => client.muteChannel( + channelCid, + expiration: any(named: 'expiration'), + ), + ).thenAnswer((_) async => EmptyResponse()); final res = await channel.mute(); expect(res, isNotNull); - verify(() => client.muteChannel( - channelCid, - expiration: any(named: 'expiration'), - )).called(1); + verify( + () => client.muteChannel( + channelCid, + expiration: any(named: 'expiration'), + ), + ).called(1); }); test('`.mute with expiration`', () async { const expiration = Duration(seconds: 3); - when(() => client.muteChannel( - channelCid, - expiration: expiration, - )).thenAnswer((_) async => EmptyResponse()); + when( + () => client.muteChannel( + channelCid, + expiration: expiration, + ), + ).thenAnswer((_) async => EmptyResponse()); - when(() => client.unmuteChannel(channelCid)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.unmuteChannel(channelCid)).thenAnswer((_) async => EmptyResponse()); final res = await channel.mute(expiration: expiration); expect(res, isNotNull); - verify(() => client.muteChannel( - channelCid, - expiration: expiration, - )).called(1); + verify( + () => client.muteChannel( + channelCid, + expiration: expiration, + ), + ).called(1); // wait for expiration await Future.delayed(expiration); @@ -4288,22 +4395,25 @@ void main() { cooldown: cooldown, ); - when(() => client.enableSlowdown( - channelId, - channelType, - cooldown, - )).thenAnswer((_) async => PartialUpdateChannelResponse() - ..channel = channelModel); + when( + () => client.enableSlowdown( + channelId, + channelType, + cooldown, + ), + ).thenAnswer((_) async => PartialUpdateChannelResponse()..channel = channelModel); final res = await channel.enableSlowMode(cooldownInterval: 10); expect(res, isNotNull); - verify(() => client.enableSlowdown( - channelId, - channelType, - cooldown, - )).called(1); + verify( + () => client.enableSlowdown( + channelId, + channelType, + cooldown, + ), + ).called(1); }); test('`.disableSlowMode`', () async { @@ -4311,11 +4421,12 @@ void main() { cid: channelCid, ); - when(() => client.disableSlowdown( - channelId, - channelType, - )).thenAnswer((_) async => PartialUpdateChannelResponse() - ..channel = channelModel); + when( + () => client.disableSlowdown( + channelId, + channelType, + ), + ).thenAnswer((_) async => PartialUpdateChannelResponse()..channel = channelModel); final res = await channel.disableSlowMode(); @@ -4328,26 +4439,29 @@ void main() { const userId = 'test-user-id'; const options = {'key': 'value'}; - when(() => client.banUser( - userId, - {'type': channelType, 'id': channelId, ...options}, - )).thenAnswer((_) async => EmptyResponse()); + when( + () => client.banUser( + userId, + {'type': channelType, 'id': channelId, ...options}, + ), + ).thenAnswer((_) async => EmptyResponse()); final res = await channel.banMember(userId, options); expect(res, isNotNull); - verify(() => client.banUser( - userId, - {'type': channelType, 'id': channelId, ...options}, - )).called(1); + verify( + () => client.banUser( + userId, + {'type': channelType, 'id': channelId, ...options}, + ), + ).called(1); }); test('`.unbanUser`', () async { const userId = 'test-user-id'; - when(() => client.unbanUser(userId, any())) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.unbanUser(userId, any())).thenAnswer((_) async => EmptyResponse()); final res = await channel.unbanMember(userId); @@ -4360,26 +4474,29 @@ void main() { const userId = 'test-user-id'; const options = {'key': 'value'}; - when(() => client.shadowBan( - userId, - {'type': channelType, 'id': channelId, ...options}, - )).thenAnswer((_) async => EmptyResponse()); + when( + () => client.shadowBan( + userId, + {'type': channelType, 'id': channelId, ...options}, + ), + ).thenAnswer((_) async => EmptyResponse()); final res = await channel.shadowBan(userId, options); expect(res, isNotNull); - verify(() => client.shadowBan( - userId, - {'type': channelType, 'id': channelId, ...options}, - )).called(1); + verify( + () => client.shadowBan( + userId, + {'type': channelType, 'id': channelId, ...options}, + ), + ).called(1); }); test('`.removeShadowBan`', () async { const userId = 'test-user-id'; - when(() => client.removeShadowBan(userId, any())) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.removeShadowBan(userId, any())).thenAnswer((_) async => EmptyResponse()); final res = await channel.removeShadowBan(userId); @@ -4391,26 +4508,29 @@ void main() { test('`.hide`', () async { const clearHistory = true; - when(() => client.hideChannel( - channelId, - channelType, - clearHistory: clearHistory, - )).thenAnswer((_) async => EmptyResponse()); + when( + () => client.hideChannel( + channelId, + channelType, + clearHistory: clearHistory, + ), + ).thenAnswer((_) async => EmptyResponse()); final res = await channel.hide(clearHistory: clearHistory); expect(res, isNotNull); - verify(() => client.hideChannel( - channelId, - channelType, - clearHistory: clearHistory, - )).called(1); + verify( + () => client.hideChannel( + channelId, + channelType, + clearHistory: clearHistory, + ), + ).called(1); }); test('`.show`', () async { - when(() => client.showChannel(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.showChannel(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await channel.show(); @@ -4421,8 +4541,7 @@ void main() { // testing archiving test('`.archive`', () async { - when(() => client.archiveChannel( - channelId: channelId, channelType: channelType)).thenAnswer( + when(() => client.archiveChannel(channelId: channelId, channelType: channelType)).thenAnswer( (_) async => FakePartialUpdateMemberResponse(), ); @@ -4430,13 +4549,11 @@ void main() { expect(res, isNotNull); - verify(() => client.archiveChannel( - channelId: channelId, channelType: channelType)).called(1); + verify(() => client.archiveChannel(channelId: channelId, channelType: channelType)).called(1); }); test('`.unarchive`', () async { - when(() => client.unarchiveChannel( - channelId: channelId, channelType: channelType)).thenAnswer( + when(() => client.unarchiveChannel(channelId: channelId, channelType: channelType)).thenAnswer( (_) async => FakePartialUpdateMemberResponse(), ); @@ -4444,36 +4561,32 @@ void main() { expect(res, isNotNull); - verify(() => client.unarchiveChannel( - channelId: channelId, channelType: channelType)).called(1); + verify(() => client.unarchiveChannel(channelId: channelId, channelType: channelType)).called(1); }); // testing pinning test('`.pin`', () async { - when(() => - client.pinChannel(channelId: channelId, channelType: channelType)) - .thenAnswer((_) async => FakePartialUpdateMemberResponse()); + when( + () => client.pinChannel(channelId: channelId, channelType: channelType), + ).thenAnswer((_) async => FakePartialUpdateMemberResponse()); final res = await channel.pin(); expect(res, isNotNull); - verify(() => - client.pinChannel(channelId: channelId, channelType: channelType)) - .called(1); + verify(() => client.pinChannel(channelId: channelId, channelType: channelType)).called(1); }); test('`.unpin`', () async { - when(() => client.unpinChannel( - channelId: channelId, channelType: channelType)) - .thenAnswer((_) async => FakePartialUpdateMemberResponse()); + when( + () => client.unpinChannel(channelId: channelId, channelType: channelType), + ).thenAnswer((_) async => FakePartialUpdateMemberResponse()); final res = await channel.unpin(); expect(res, isNotNull); - verify(() => client.unpinChannel( - channelId: channelId, channelType: channelType)).called(1); + verify(() => client.unpinChannel(channelId: channelId, channelType: channelType)).called(1); }); test('`.on`', () async { @@ -4510,9 +4623,9 @@ void main() { // Set up the mock response for sending message final newMessage = Message(text: 'New message'); - when(() => client.sendMessage(any(), channelId, channelType)) - .thenAnswer((_) async => SendMessageResponse() - ..message = newMessage.copyWith(state: MessageState.sent)); + when( + () => client.sendMessage(any(), channelId, channelType), + ).thenAnswer((_) async => SendMessageResponse()..message = newMessage.copyWith(state: MessageState.sent)); // Send a new message await channel.sendMessage(newMessage); @@ -5341,8 +5454,7 @@ void main() { }, ); - test('should update read state on notification mark unread event', - () async { + test('should update read state on notification mark unread event', () async { // Create the current read state final currentUser = User(id: 'test-user'); final currentRead = Read( @@ -5570,8 +5682,7 @@ void main() { 'should add a new read state if not exist on message delivered event', () async { final newUser = User(id: 'new-user'); - final distantPast = - DateTime.fromMillisecondsSinceEpoch(0, isUtc: true); + final distantPast = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true); // Verify initial state final read = channel.state?.read; @@ -6123,8 +6234,7 @@ void main() { expect(updatedMessage?.reminder, isNull); }); - test('should handle reminder.created event for thread messages', - () async { + test('should handle reminder.created event for thread messages', () async { const messageId = 'test-message-id'; const parentId = 'test-parent-id'; @@ -6175,8 +6285,7 @@ void main() { expect(updatedMessage?.reminder?.remindAt, reminder.remindAt); }); - test('should handle reminder.updated event for thread messages', - () async { + test('should handle reminder.updated event for thread messages', () async { const messageId = 'test-message-id'; const parentId = 'test-parent-id'; @@ -6235,8 +6344,7 @@ void main() { expect(updatedMessage?.reminder?.remindAt, updatedRemindAt); }); - test('should handle reminder.deleted event for thread messages', - () async { + test('should handle reminder.deleted event for thread messages', () async { const messageId = 'test-message-id'; const parentId = 'test-parent-id'; @@ -6868,18 +6976,17 @@ void main() { setUp(() { persistenceClient = MockPersistenceClient(); when(() => client.chatPersistenceClient).thenReturn(persistenceClient); - when(() => persistenceClient.deleteMessagesFromUser( - cid: any(named: 'cid'), - userId: any(named: 'userId'), - hardDelete: any(named: 'hardDelete'), - deletedAt: any(named: 'deletedAt'), - )).thenAnswer((_) async {}); - when(() => persistenceClient.deleteMessageByIds(any())) - .thenAnswer((_) async {}); - when(() => persistenceClient.deletePinnedMessageByIds(any())) - .thenAnswer((_) async {}); - when(() => persistenceClient.getChannelThreads(any())) - .thenAnswer((_) async => >{}); + when( + () => persistenceClient.deleteMessagesFromUser( + cid: any(named: 'cid'), + userId: any(named: 'userId'), + hardDelete: any(named: 'hardDelete'), + deletedAt: any(named: 'deletedAt'), + ), + ).thenAnswer((_) async {}); + when(() => persistenceClient.deleteMessageByIds(any())).thenAnswer((_) async {}); + when(() => persistenceClient.deletePinnedMessageByIds(any())).thenAnswer((_) async {}); + when(() => persistenceClient.getChannelThreads(any())).thenAnswer((_) async => >{}); final channelState = _generateChannelState(channelId, channelType); channel = Channel.fromState(client, channelState); @@ -6945,9 +7052,7 @@ void main() { // Verify user1's messages are soft deleted expect(channel.state?.messages.length, equals(3)); - final deletedMessages = channel.state?.messages - .where((m) => m.user?.id == 'user-1') - .toList(); + final deletedMessages = channel.state?.messages.where((m) => m.user?.id == 'user-1').toList(); expect(deletedMessages?.length, equals(2)); for (final message in deletedMessages!) { expect(message.type, equals(MessageType.deleted)); @@ -6956,8 +7061,7 @@ void main() { } // Verify user2's message is unaffected - final user2Message = - channel.state?.messages.firstWhere((m) => m.id == 'msg-3'); + final user2Message = channel.state?.messages.firstWhere((m) => m.id == 'msg-3'); expect(user2Message?.type, isNot(MessageType.deleted)); expect(user2Message?.deletedAt, isNull); }, @@ -7015,8 +7119,7 @@ void main() { ); // Verify user2's message still exists - final user2Message = - channel.state?.messages.firstWhere((m) => m.id == 'msg-3'); + final user2Message = channel.state?.messages.firstWhere((m) => m.id == 'msg-3'); expect(user2Message, isNotNull); expect(user2Message?.user?.id, equals('user-2')); }, @@ -7195,8 +7298,7 @@ void main() { () => persistenceClient.deleteMessageByIds(['msg-1', 'msg-2']), ).called(1); verify( - () => - persistenceClient.deletePinnedMessageByIds(['msg-1', 'msg-2']), + () => persistenceClient.deletePinnedMessageByIds(['msg-1', 'msg-2']), ).called(1); // Verify user1's messages are removed from state @@ -7241,8 +7343,7 @@ void main() { // Verify message is soft deleted (still in state) expect(channel.state?.messages.length, equals(1)); - expect( - channel.state?.messages.first.type, equals(MessageType.deleted)); + expect(channel.state?.messages.first.type, equals(MessageType.deleted)); }, ); @@ -7329,9 +7430,11 @@ void main() { ).called(1); // Verify in-state messages were also removed from state's persistence - final capturedIds = verify( - () => persistenceClient.deleteMessageByIds(captureAny()), - ).captured.first as List; + final capturedIds = + verify( + () => persistenceClient.deleteMessageByIds(captureAny()), + ).captured.first + as List; expect( capturedIds, @@ -7479,8 +7582,7 @@ void main() { final messageReads = channel.state!.readsOf(message: message); expect(messageReads.length, 2); - expect(messageReads.map((r) => r.user.id), - containsAll(['user-1', 'user-3'])); + expect(messageReads.map((r) => r.user.id), containsAll(['user-1', 'user-3'])); expect(messageReads.map((r) => r.user.id), isNot(contains('user-2'))); expect(messageReads.map((r) => r.user.id), isNot(contains('sender-id'))); }); @@ -7515,15 +7617,12 @@ void main() { channel.state!.updateChannelState( ChannelState( channel: channelState.channel, - read: [ - Read(user: user1, lastRead: now.add(const Duration(seconds: 1))) - ], + read: [Read(user: user1, lastRead: now.add(const Duration(seconds: 1)))], ), ); }); - test('deliveriesOf should return reads that have delivered the message', - () { + test('deliveriesOf should return reads that have delivered the message', () { final now = DateTime.now(); final sender = User(id: 'sender-id', name: 'Sender'); final user1 = User(id: 'user-1', name: 'User 1'); @@ -7579,15 +7678,13 @@ void main() { final deliveries = channel.state!.deliveriesOf(message: message); expect(deliveries.length, 2); - expect( - deliveries.map((r) => r.user.id), containsAll(['user-1', 'user-4'])); + expect(deliveries.map((r) => r.user.id), containsAll(['user-1', 'user-4'])); expect(deliveries.map((r) => r.user.id), isNot(contains('user-2'))); expect(deliveries.map((r) => r.user.id), isNot(contains('user-3'))); expect(deliveries.map((r) => r.user.id), isNot(contains('sender-id'))); }); - test('deliveriesOfStream should emit delivery updates for a message', - () async { + test('deliveriesOfStream should emit delivery updates for a message', () async { final now = DateTime.now(); final sender = User(id: 'sender-id', name: 'Sender'); final user1 = User(id: 'user-1', name: 'User 1'); @@ -7603,8 +7700,7 @@ void main() { final channel = Channel.fromState(client, channelState); addTearDown(channel.dispose); - final deliveriesStream = - channel.state!.deliveriesOfStream(message: message); + final deliveriesStream = channel.state!.deliveriesOfStream(message: message); expectLater( deliveriesStream, @@ -8976,9 +9072,7 @@ void main() { }); group('retryMessage method', () { - test( - 'should call sendMessage with preserved skipPush and skipEnrichUrl parameters', - () async { + test('should call sendMessage with preserved skipPush and skipEnrichUrl parameters', () async { final message = Message( id: 'test-message-id', text: 'Hello, World!', @@ -8988,33 +9082,35 @@ void main() { ), ); - final sendMessageResponse = SendMessageResponse() - ..message = message.copyWith(state: MessageState.sent); + final sendMessageResponse = SendMessageResponse()..message = message.copyWith(state: MessageState.sent); - when(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - skipPush: true, - skipEnrichUrl: true, - )).thenAnswer((_) async => sendMessageResponse); + when( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + skipPush: true, + skipEnrichUrl: true, + ), + ).thenAnswer((_) async => sendMessageResponse); final result = await channel.retryMessage(message); expect(result, isNotNull); expect(result, isA()); - verify(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - skipPush: true, - skipEnrichUrl: true, - )).called(1); + verify( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + skipPush: true, + skipEnrichUrl: true, + ), + ).called(1); }); - test('should call sendMessage with preserved skipPush parameter', - () async { + test('should call sendMessage with preserved skipPush parameter', () async { final message = Message( id: 'test-message-id', text: 'Hello, World!', @@ -9024,31 +9120,33 @@ void main() { ), ); - final sendMessageResponse = SendMessageResponse() - ..message = message.copyWith(state: MessageState.sent); + final sendMessageResponse = SendMessageResponse()..message = message.copyWith(state: MessageState.sent); - when(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - skipPush: true, - )).thenAnswer((_) async => sendMessageResponse); + when( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + skipPush: true, + ), + ).thenAnswer((_) async => sendMessageResponse); final result = await channel.retryMessage(message); expect(result, isNotNull); expect(result, isA()); - verify(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - skipPush: true, - )).called(1); + verify( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + skipPush: true, + ), + ).called(1); }); - test('should call sendMessage with preserved skipEnrichUrl parameter', - () async { + test('should call sendMessage with preserved skipEnrichUrl parameter', () async { final message = Message( id: 'test-message-id', text: 'Hello, World!', @@ -9058,32 +9156,33 @@ void main() { ), ); - final sendMessageResponse = SendMessageResponse() - ..message = message.copyWith(state: MessageState.sent); + final sendMessageResponse = SendMessageResponse()..message = message.copyWith(state: MessageState.sent); - when(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - skipEnrichUrl: true, - )).thenAnswer((_) async => sendMessageResponse); + when( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + skipEnrichUrl: true, + ), + ).thenAnswer((_) async => sendMessageResponse); final result = await channel.retryMessage(message); expect(result, isNotNull); expect(result, isA()); - verify(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - skipEnrichUrl: true, - )).called(1); + verify( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + skipEnrichUrl: true, + ), + ).called(1); }); - test( - 'should call sendMessage with preserved false skipPush and skipEnrichUrl parameters', - () async { + test('should call sendMessage with preserved false skipPush and skipEnrichUrl parameters', () async { final message = Message( id: 'test-message-id', text: 'Hello, World!', @@ -9093,30 +9192,31 @@ void main() { ), ); - final sendMessageResponse = SendMessageResponse() - ..message = message.copyWith(state: MessageState.sent); + final sendMessageResponse = SendMessageResponse()..message = message.copyWith(state: MessageState.sent); - when(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - )).thenAnswer((_) async => sendMessageResponse); + when( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + ), + ).thenAnswer((_) async => sendMessageResponse); final result = await channel.retryMessage(message); expect(result, isNotNull); expect(result, isA()); - verify(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - )).called(1); + verify( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + ), + ).called(1); }); - test( - 'should call updateMessage with preserved skipPush, skipEnrichUrl parameter', - () async { + test('should call updateMessage with preserved skipPush, skipEnrichUrl parameter', () async { final message = Message( id: 'test-message-id', text: 'Hello, World!', @@ -9126,30 +9226,31 @@ void main() { ), ); - final updateMessageResponse = UpdateMessageResponse() - ..message = message.copyWith(state: MessageState.updated); + final updateMessageResponse = UpdateMessageResponse()..message = message.copyWith(state: MessageState.updated); - when(() => client.updateMessage( - any(that: isSameMessageAs(message)), - skipPush: true, - skipEnrichUrl: true, - )).thenAnswer((_) async => updateMessageResponse); + when( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + skipPush: true, + skipEnrichUrl: true, + ), + ).thenAnswer((_) async => updateMessageResponse); final result = await channel.retryMessage(message); expect(result, isNotNull); expect(result, isA()); - verify(() => client.updateMessage( - any(that: isSameMessageAs(message)), - skipPush: true, - skipEnrichUrl: true, - )).called(1); + verify( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + skipPush: true, + skipEnrichUrl: true, + ), + ).called(1); }); - test( - 'should call updateMessage with preserved false skipPush, skipEnrichUrl parameter', - () async { + test('should call updateMessage with preserved false skipPush, skipEnrichUrl parameter', () async { final message = Message( id: 'test-message-id', state: MessageState.updatingFailed( @@ -9158,21 +9259,24 @@ void main() { ), ); - final updateMessageResponse = UpdateMessageResponse() - ..message = message.copyWith(state: MessageState.updated); + final updateMessageResponse = UpdateMessageResponse()..message = message.copyWith(state: MessageState.updated); - when(() => client.updateMessage( - any(that: isSameMessageAs(message)), - )).thenAnswer((_) async => updateMessageResponse); + when( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + ), + ).thenAnswer((_) async => updateMessageResponse); final result = await channel.retryMessage(message); expect(result, isNotNull); expect(result, isA()); - verify(() => client.updateMessage( - any(that: isSameMessageAs(message)), - )).called(1); + verify( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + ), + ).called(1); }); test('should call deleteMessage with preserved hard parameter', () async { @@ -9182,54 +9286,59 @@ void main() { state: MessageState.hardDeletingFailed, ); - when(() => client.deleteMessage( - message.id, - hard: true, - )).thenAnswer((_) async => EmptyResponse()); + when( + () => client.deleteMessage( + message.id, + hard: true, + ), + ).thenAnswer((_) async => EmptyResponse()); final result = await channel.retryMessage(message); expect(result, isNotNull); expect(result, isA()); - verify(() => client.deleteMessage( - message.id, - hard: true, - )).called(1); + verify( + () => client.deleteMessage( + message.id, + hard: true, + ), + ).called(1); }); - test('should call deleteMessage with preserved false hard parameter', - () async { + test('should call deleteMessage with preserved false hard parameter', () async { final message = Message( id: 'test-message-id', createdAt: DateTime.now(), state: MessageState.softDeletingFailed, ); - when(() => client.deleteMessage( - message.id, - )).thenAnswer((_) async => EmptyResponse()); + when( + () => client.deleteMessage( + message.id, + ), + ).thenAnswer((_) async => EmptyResponse()); final result = await channel.retryMessage(message); expect(result, isNotNull); expect(result, isA()); - verify(() => client.deleteMessage( - message.id, - )).called(1); + verify( + () => client.deleteMessage( + message.id, + ), + ).called(1); }); - test('should call deleteMessageForMe for deletingForMeFailed state', - () async { + test('should call deleteMessageForMe for deletingForMeFailed state', () async { final message = Message( id: 'test-message-id', createdAt: DateTime.now(), state: MessageState.deletingForMeFailed, ); - when(() => client.deleteMessageForMe(message.id)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.deleteMessageForMe(message.id)).thenAnswer((_) async => EmptyResponse()); final result = await channel.retryMessage(message); @@ -9239,15 +9348,13 @@ void main() { verify(() => client.deleteMessageForMe(message.id)).called(1); }); - test('should throw AssertionError when message state is not failed', - () async { + test('should throw AssertionError when message state is not failed', () async { final message = Message( id: 'test-message-id', state: MessageState.sent, ); - expect(() => channel.retryMessage(message), - throwsA(isA())); + expect(() => channel.retryMessage(message), throwsA(isA())); }); }); }); diff --git a/packages/stream_chat/test/src/client/client_test.dart b/packages/stream_chat/test/src/client/client_test.dart index 8a65499110..3ae52605e2 100644 --- a/packages/stream_chat/test/src/client/client_test.dart +++ b/packages/stream_chat/test/src/client/client_test.dart @@ -75,8 +75,7 @@ void main() { final user = User(id: 'test-user-id'); final token = Token.development(user.id).rawValue; - when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))) - .thenAnswer( + when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))).thenAnswer( (_) async => ConnectGuestUserResponse() ..user = user ..accessToken = token, @@ -103,8 +102,9 @@ void main() { test('should throw if `.getGuestUser` fails', () async { final user = User(id: 'test-user-id'); - when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))) - .thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + when( + () => api.guest.getGuestUser(any(that: isSameUserAs(user))), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); expectLater( client.wsConnectionStatusStream, @@ -247,8 +247,7 @@ void main() { final user = User(id: 'test-user-id'); final token = Token.development(user.id).rawValue; - when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))) - .thenAnswer( + when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))).thenAnswer( (_) async => ConnectGuestUserResponse() ..user = user ..accessToken = token, @@ -331,8 +330,7 @@ void main() { final user = User(id: 'test-user-id'); final token = Token.development(user.id).rawValue; - when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))) - .thenAnswer( + when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))).thenAnswer( (_) async => ConnectGuestUserResponse() ..user = user ..accessToken = token, @@ -377,8 +375,7 @@ void main() { setUp(() { final ws = FakeWebSocketWithConnectionError(); - client = StreamChatClient(apiKey, chatApi: api, ws: ws) - ..chatPersistenceClient = persistence; + client = StreamChatClient(apiKey, chatApi: api, ws: ws)..chatPersistenceClient = persistence; }); tearDown(() { @@ -392,9 +389,10 @@ void main() { final token = Token.development(user.id).rawValue; final event = Event( - type: EventType.healthCheck, - connectionId: 'test-connection-id', - me: OwnUser.fromUser(user)); + type: EventType.healthCheck, + connectionId: 'test-connection-id', + me: OwnUser.fromUser(user), + ); when(persistence.getConnectionInfo).thenAnswer((_) async => event); final res = await client.connectUser(user, token); @@ -416,9 +414,10 @@ void main() { } final event = Event( - type: EventType.healthCheck, - connectionId: 'test-connection-id', - me: OwnUser.fromUser(user)); + type: EventType.healthCheck, + connectionId: 'test-connection-id', + me: OwnUser.fromUser(user), + ); when(persistence.getConnectionInfo).thenAnswer((_) async => event); final res = await client.connectUserWithProvider(user, tokenProvider); @@ -437,13 +436,13 @@ void main() { final token = Token.development(user.id).rawValue; final event = Event( - type: EventType.healthCheck, - connectionId: 'test-connection-id', - me: OwnUser.fromUser(user)); + type: EventType.healthCheck, + connectionId: 'test-connection-id', + me: OwnUser.fromUser(user), + ); when(persistence.getConnectionInfo).thenAnswer((_) async => event); - when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))) - .thenAnswer( + when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))).thenAnswer( (_) async => ConnectGuestUserResponse() ..user = user ..accessToken = token, @@ -455,8 +454,7 @@ void main() { verify(persistence.getConnectionInfo).called(1); verifyNoMoreInteractions(persistence); - verify(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))) - .called(1); + verify(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))).called(1); verifyNoMoreInteractions(api.guest); }, ); @@ -501,12 +499,10 @@ void main() { }); setUp(() async { - when(() => persistence.updateLastSyncAt(any())) - .thenAnswer((_) => Future.value()); + when(() => persistence.updateLastSyncAt(any())).thenAnswer((_) => Future.value()); when(persistence.getLastSyncAt).thenAnswer((_) async => null); final ws = FakeWebSocket(); - client = StreamChatClient(apiKey, chatApi: api, ws: ws) - ..chatPersistenceClient = persistence; + client = StreamChatClient(apiKey, chatApi: api, ws: ws)..chatPersistenceClient = persistence; await client.connectUser(user, token); await delay(300); expect(client.persistenceEnabled, isTrue); @@ -527,26 +523,25 @@ void main() { reset(persistence); const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3']; final lastSyncAt = DateTime.now(); - when(() => api.general.sync(cids, lastSyncAt)) - .thenAnswer((_) async => SyncResponse() - ..events = [ - Event( - isLocal: false, - type: EventType.healthCheck, - connectionId: 'test-connection-id', - me: OwnUser.fromUser(user), - ), - Event( - isLocal: false, - type: EventType.messageDeleted, - message: Message(id: 'test-message-id'), - ), - ]); - - when(() => persistence.updateConnectionInfo(any())) - .thenAnswer((_) => Future.value()); - when(() => persistence.updateLastSyncAt(any())) - .thenAnswer((_) => Future.value()); + when(() => api.general.sync(cids, lastSyncAt)).thenAnswer( + (_) async => SyncResponse() + ..events = [ + Event( + isLocal: false, + type: EventType.healthCheck, + connectionId: 'test-connection-id', + me: OwnUser.fromUser(user), + ), + Event( + isLocal: false, + type: EventType.messageDeleted, + message: Message(id: 'test-message-id'), + ), + ], + ); + + when(() => persistence.updateConnectionInfo(any())).thenAnswer((_) => Future.value()); + when(() => persistence.updateLastSyncAt(any())).thenAnswer((_) => Future.value()); await client.sync(cids: cids, lastSyncAt: lastSyncAt); @@ -569,26 +564,25 @@ void main() { when(persistence.getChannelCids).thenAnswer((_) async => cids); when(persistence.getLastSyncAt).thenAnswer((_) async => lastSyncAt); - when(() => api.general.sync(cids, lastSyncAt)) - .thenAnswer((_) async => SyncResponse() - ..events = [ - Event( - isLocal: false, - type: EventType.healthCheck, - connectionId: 'test-connection-id', - me: OwnUser.fromUser(user), - ), - Event( - isLocal: false, - type: EventType.messageDeleted, - message: Message(id: 'test-message-id', text: 'Hey!'), - ), - ]); - - when(() => persistence.updateConnectionInfo(any())) - .thenAnswer((_) => Future.value()); - when(() => persistence.updateLastSyncAt(any())) - .thenAnswer((_) => Future.value()); + when(() => api.general.sync(cids, lastSyncAt)).thenAnswer( + (_) async => SyncResponse() + ..events = [ + Event( + isLocal: false, + type: EventType.healthCheck, + connectionId: 'test-connection-id', + me: OwnUser.fromUser(user), + ), + Event( + isLocal: false, + type: EventType.messageDeleted, + message: Message(id: 'test-message-id', text: 'Hey!'), + ), + ], + ); + + when(() => persistence.updateConnectionInfo(any())).thenAnswer((_) => Future.value()); + when(() => persistence.updateLastSyncAt(any())).thenAnswer((_) => Future.value()); await client.sync(); @@ -612,12 +606,14 @@ void main() { ), ); - when(() => persistence.getChannelStates( - filter: any(named: 'filter'), - messageLimit: any(named: 'messageLimit'), - channelStateSort: any(named: 'channelStateSort'), - paginationParams: any(named: 'paginationParams'), - )).thenAnswer((_) async => persistentChannelStates); + when( + () => persistence.getChannelStates( + filter: any(named: 'filter'), + messageLimit: any(named: 'messageLimit'), + channelStateSort: any(named: 'channelStateSort'), + paginationParams: any(named: 'paginationParams'), + ), + ).thenAnswer((_) async => persistentChannelStates); final channelStates = List.generate( 3, @@ -626,35 +622,33 @@ void main() { ), ); - when(() => api.channel.queryChannels( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - memberLimit: any(named: 'memberLimit'), - messageLimit: any(named: 'messageLimit'), - paginationParams: any(named: 'paginationParams'), - )).thenAnswer( + when( + () => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + ), + ).thenAnswer( (_) async => QueryChannelsResponse()..channels = channelStates, ); when(() => persistence.getChannelThreads(any())).thenAnswer( (_) async => >{ for (final channelState in channelStates) - channelState.channel!.cid: [ - Message(id: 'test-message-id', text: 'Test message') - ], + channelState.channel!.cid: [Message(id: 'test-message-id', text: 'Test message')], }, ); - when(() => persistence.updateChannelState(any())) - .thenAnswer((_) async {}); - when(() => persistence.updateChannelThreads(any(), any())) - .thenAnswer((_) async {}); - when(() => persistence.updateChannelQueries(any(), any(), - clearQueryCache: any(named: 'clearQueryCache'))) - .thenAnswer((_) => Future.value()); + when(() => persistence.updateChannelState(any())).thenAnswer((_) async {}); + when(() => persistence.updateChannelThreads(any(), any())).thenAnswer((_) async {}); + when( + () => persistence.updateChannelQueries(any(), any(), clearQueryCache: any(named: 'clearQueryCache')), + ).thenAnswer((_) => Future.value()); expectLater( client.queryChannels(), @@ -670,32 +664,34 @@ void main() { // before our stream starts emitting data await delay(1050); - verify(() => persistence.getChannelStates( - filter: any(named: 'filter'), - messageLimit: any(named: 'messageLimit'), - channelStateSort: any(named: 'channelStateSort'), - paginationParams: any(named: 'paginationParams'), - )).called(1); - - verify(() => api.channel.queryChannels( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - memberLimit: any(named: 'memberLimit'), - messageLimit: any(named: 'messageLimit'), - paginationParams: any(named: 'paginationParams'), - )).called(1); - - verify(() => persistence.getChannelThreads(any())) - .called(channelStates.length); - verify(() => persistence.updateChannelState(any())) - .called(channelStates.length); - verify(() => persistence.updateChannelThreads(any(), any())) - .called(channelStates.length); - verify(() => persistence.updateChannelQueries(any(), any(), - clearQueryCache: any(named: 'clearQueryCache'))).called(1); + verify( + () => persistence.getChannelStates( + filter: any(named: 'filter'), + messageLimit: any(named: 'messageLimit'), + channelStateSort: any(named: 'channelStateSort'), + paginationParams: any(named: 'paginationParams'), + ), + ).called(1); + + verify( + () => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + ), + ).called(1); + + verify(() => persistence.getChannelThreads(any())).called(channelStates.length); + verify(() => persistence.updateChannelState(any())).called(channelStates.length); + verify(() => persistence.updateChannelThreads(any(), any())).called(channelStates.length); + verify( + () => persistence.updateChannelQueries(any(), any(), clearQueryCache: any(named: 'clearQueryCache')), + ).called(1); }, ); @@ -709,37 +705,37 @@ void main() { ), ); - when(() => persistence.getChannelStates( - filter: any(named: 'filter'), - messageLimit: any(named: 'messageLimit'), - channelStateSort: any(named: 'channelStateSort'), - paginationParams: any(named: 'paginationParams'), - )).thenAnswer((_) async => persistentChannelStates); - - when(() => api.channel.queryChannels( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - memberLimit: any(named: 'memberLimit'), - messageLimit: any(named: 'messageLimit'), - paginationParams: any(named: 'paginationParams'), - )).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + when( + () => persistence.getChannelStates( + filter: any(named: 'filter'), + messageLimit: any(named: 'messageLimit'), + channelStateSort: any(named: 'channelStateSort'), + paginationParams: any(named: 'paginationParams'), + ), + ).thenAnswer((_) async => persistentChannelStates); + + when( + () => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + ), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); when(() => persistence.getChannelThreads(any())).thenAnswer( (_) async => >{ for (final channelState in persistentChannelStates) - channelState.channel!.cid: [ - Message(id: 'test-message-id', text: 'Test message') - ], + channelState.channel!.cid: [Message(id: 'test-message-id', text: 'Test message')], }, ); - when(() => persistence.updateChannelState(any())) - .thenAnswer((_) async => {}); - when(() => persistence.updateChannelThreads(any(), any())) - .thenAnswer((_) async => {}); + when(() => persistence.updateChannelState(any())).thenAnswer((_) async => {}); + when(() => persistence.updateChannelThreads(any(), any())).thenAnswer((_) async => {}); expectLater( client.queryChannels(), @@ -753,30 +749,31 @@ void main() { // before our stream starts emitting data await delay(1050); - verify(() => persistence.getChannelStates( - filter: any(named: 'filter'), - messageLimit: any(named: 'messageLimit'), - channelStateSort: any(named: 'channelStateSort'), - paginationParams: any(named: 'paginationParams'), - )).called(1); - - verify(() => api.channel.queryChannels( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - memberLimit: any(named: 'memberLimit'), - messageLimit: any(named: 'messageLimit'), - paginationParams: any(named: 'paginationParams'), - )).called(1); - - verify(() => persistence.getChannelThreads(any())) - .called(persistentChannelStates.length); - verify(() => persistence.updateChannelState(any())) - .called(persistentChannelStates.length); - verify(() => persistence.updateChannelThreads(any(), any())) - .called(persistentChannelStates.length); + verify( + () => persistence.getChannelStates( + filter: any(named: 'filter'), + messageLimit: any(named: 'messageLimit'), + channelStateSort: any(named: 'channelStateSort'), + paginationParams: any(named: 'paginationParams'), + ), + ).called(1); + + verify( + () => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + ), + ).called(1); + + verify(() => persistence.getChannelThreads(any())).called(persistentChannelStates.length); + verify(() => persistence.updateChannelState(any())).called(persistentChannelStates.length); + verify(() => persistence.updateChannelThreads(any(), any())).called(persistentChannelStates.length); }, ); }); @@ -835,21 +832,22 @@ void main() { const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3']; final lastSyncAt = DateTime.now(); - when(() => api.general.sync(cids, lastSyncAt)) - .thenAnswer((_) async => SyncResponse() - ..events = [ - Event( - isLocal: false, - type: EventType.healthCheck, - connectionId: 'test-connection-id', - me: OwnUser.fromUser(user), - ), - Event( - isLocal: false, - type: EventType.messageDeleted, - message: Message(id: 'test-message-id'), - ), - ]); + when(() => api.general.sync(cids, lastSyncAt)).thenAnswer( + (_) async => SyncResponse() + ..events = [ + Event( + isLocal: false, + type: EventType.healthCheck, + connectionId: 'test-connection-id', + me: OwnUser.fromUser(user), + ), + Event( + isLocal: false, + type: EventType.messageDeleted, + message: Message(id: 'test-message-id'), + ), + ], + ); await client.sync(cids: cids, lastSyncAt: lastSyncAt); @@ -876,16 +874,18 @@ void main() { ), ); - when(() => api.channel.queryChannels( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - memberLimit: any(named: 'memberLimit'), - messageLimit: any(named: 'messageLimit'), - paginationParams: any(named: 'paginationParams'), - )).thenAnswer( + when( + () => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + ), + ).thenAnswer( (_) async => QueryChannelsResponse()..channels = channelStates, ); @@ -898,7 +898,25 @@ void main() { // before our stream starts emitting data await delay(300); - verify(() => api.channel.queryChannels( + verify( + () => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + ), + ).called(1); + }); + + test( + '''should rethrow if `.queryChannelsOnline` throws and persistence channels are empty''', + () async { + when( + () => api.channel.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), state: any(named: 'state'), @@ -907,22 +925,8 @@ void main() { memberLimit: any(named: 'memberLimit'), messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), - )).called(1); - }); - - test( - '''should rethrow if `.queryChannelsOnline` throws and persistence channels are empty''', - () async { - when(() => api.channel.queryChannels( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - memberLimit: any(named: 'memberLimit'), - messageLimit: any(named: 'messageLimit'), - paginationParams: any(named: 'paginationParams'), - )).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + ), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); expectLater( client.queryChannels(), @@ -933,16 +937,18 @@ void main() { // before our stream starts emitting data await delay(300); - verify(() => api.channel.queryChannels( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - memberLimit: any(named: 'memberLimit'), - messageLimit: any(named: 'messageLimit'), - paginationParams: any(named: 'paginationParams'), - )).called(1); + verify( + () => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + ), + ).called(1); }, ); }); @@ -953,12 +959,14 @@ void main() { (index) => User(id: 'test-user-id-$index'), ); - when(() => api.user.queryUsers( - presence: any(named: 'presence'), - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => QueryUsersResponse()..users = users); + when( + () => api.user.queryUsers( + presence: any(named: 'presence'), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => QueryUsersResponse()..users = users); expectLater( // skipping initial seed event -> {} users @@ -972,12 +980,14 @@ void main() { expect(res, isNotNull); expect(res.users.length, users.length); - verify(() => api.user.queryUsers( - presence: any(named: 'presence'), - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).called(1); + verify( + () => api.user.queryUsers( + presence: any(named: 'presence'), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).called(1); verifyNoMoreInteractions(api.user); }); @@ -993,21 +1003,25 @@ void main() { const cid = 'message:nice-channel'; final filter = Filter.equal('channel_cid', cid); - when(() => api.moderation.queryBannedUsers( - filter: filter, - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => QueryBannedUsersResponse()..bans = bans); + when( + () => api.moderation.queryBannedUsers( + filter: filter, + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => QueryBannedUsersResponse()..bans = bans); final res = await client.queryBannedUsers(filter: filter); expect(res, isNotNull); expect(res.bans.length, bans.length); - verify(() => api.moderation.queryBannedUsers( - filter: filter, - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).called(1); + verify( + () => api.moderation.queryBannedUsers( + filter: filter, + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).called(1); verifyNoMoreInteractions(api.moderation); }); @@ -1022,23 +1036,29 @@ void main() { ..message = Message(id: 'test-message-id-$index'), ); - when(() => api.general.searchMessages(filter, - query: any(named: 'query'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - messageFilters: any(named: 'messageFilters'))) - .thenAnswer( - (_) async => SearchMessagesResponse()..results = messages); + when( + () => api.general.searchMessages( + filter, + query: any(named: 'query'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + messageFilters: any(named: 'messageFilters'), + ), + ).thenAnswer((_) async => SearchMessagesResponse()..results = messages); final res = await client.search(filter); expect(res, isNotNull); expect(res.results.length, messages.length); - verify(() => api.general.searchMessages(filter, + verify( + () => api.general.searchMessages( + filter, query: any(named: 'query'), sort: any(named: 'sort'), pagination: any(named: 'pagination'), - messageFilters: any(named: 'messageFilters'))).called(1); + messageFilters: any(named: 'messageFilters'), + ), + ).called(1); verifyNoMoreInteractions(api.general); }); @@ -1049,15 +1069,15 @@ void main() { const fileUrl = 'test-file-url'; - when(() => api.fileUploader.sendFile(file, channelId, channelType)) - .thenAnswer((_) async => SendFileResponse()..file = fileUrl); + when( + () => api.fileUploader.sendFile(file, channelId, channelType), + ).thenAnswer((_) async => SendFileResponse()..file = fileUrl); final res = await client.sendFile(file, channelId, channelType); expect(res, isNotNull); expect(res.file, fileUrl); - verify(() => api.fileUploader.sendFile(file, channelId, channelType)) - .called(1); + verify(() => api.fileUploader.sendFile(file, channelId, channelType)).called(1); verifyNoMoreInteractions(api.fileUploader); }); @@ -1068,15 +1088,15 @@ void main() { const fileUrl = 'test-image-url'; - when(() => api.fileUploader.sendImage(image, channelId, channelType)) - .thenAnswer((_) async => SendImageResponse()..file = fileUrl); + when( + () => api.fileUploader.sendImage(image, channelId, channelType), + ).thenAnswer((_) async => SendImageResponse()..file = fileUrl); final res = await client.sendImage(image, channelId, channelType); expect(res, isNotNull); expect(res.file, fileUrl); - verify(() => api.fileUploader.sendImage(image, channelId, channelType)) - .called(1); + verify(() => api.fileUploader.sendImage(image, channelId, channelType)).called(1); verifyNoMoreInteractions(api.fileUploader); }); @@ -1085,14 +1105,12 @@ void main() { const channelType = 'test-channel-type'; const fileUrl = 'test-file-url'; - when(() => api.fileUploader.deleteFile(fileUrl, channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.fileUploader.deleteFile(fileUrl, channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await client.deleteFile(fileUrl, channelId, channelType); expect(res, isNotNull); - verify(() => api.fileUploader.deleteFile(fileUrl, channelId, channelType)) - .called(1); + verify(() => api.fileUploader.deleteFile(fileUrl, channelId, channelType)).called(1); verifyNoMoreInteractions(api.fileUploader); }); @@ -1101,8 +1119,9 @@ void main() { const channelType = 'test-channel-type'; const imageUrl = 'test-image-url'; - when(() => api.fileUploader.deleteImage(imageUrl, channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when( + () => api.fileUploader.deleteImage(imageUrl, channelId, channelType), + ).thenAnswer((_) async => EmptyResponse()); final res = await client.deleteImage(imageUrl, channelId, channelType); expect(res, isNotNull); @@ -1117,8 +1136,7 @@ void main() { final image = AttachmentFile(size: 33, path: 'test-image-path'); const fileUrl = 'test-image-url'; - when(() => api.fileUploader.uploadImage(image)) - .thenAnswer((_) async => UploadImageResponse()..file = fileUrl); + when(() => api.fileUploader.uploadImage(image)).thenAnswer((_) async => UploadImageResponse()..file = fileUrl); final res = await client.uploadImage(image); expect(res, isNotNull); @@ -1132,8 +1150,7 @@ void main() { final file = AttachmentFile(size: 33, path: 'test-file-path'); const fileUrl = 'test-file-url'; - when(() => api.fileUploader.uploadFile(file)) - .thenAnswer((_) async => UploadFileResponse()..file = fileUrl); + when(() => api.fileUploader.uploadFile(file)).thenAnswer((_) async => UploadFileResponse()..file = fileUrl); final res = await client.uploadFile(file); expect(res, isNotNull); @@ -1146,8 +1163,7 @@ void main() { test('`.removeImage`', () async { const imageUrl = 'test-image-url'; - when(() => api.fileUploader.removeImage(imageUrl)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.fileUploader.removeImage(imageUrl)).thenAnswer((_) async => EmptyResponse()); final res = await client.removeImage(imageUrl); expect(res, isNotNull); @@ -1159,8 +1175,7 @@ void main() { test('`.removeFile`', () async { const fileUrl = 'test-file-url'; - when(() => api.fileUploader.removeFile(fileUrl)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.fileUploader.removeFile(fileUrl)).thenAnswer((_) async => EmptyResponse()); final res = await client.removeFile(fileUrl); expect(res, isNotNull); @@ -1174,21 +1189,21 @@ void main() { const channelType = 'test-channel-type'; const data = {'name': 'test-channel'}; - when(() => api.channel.updateChannel(channelId, channelType, data)) - .thenAnswer((invocation) async => UpdateChannelResponse() - ..channel = ChannelModel( - id: channelId, - type: channelType, - extraData: {...data}, - )); + when(() => api.channel.updateChannel(channelId, channelType, data)).thenAnswer( + (invocation) async => UpdateChannelResponse() + ..channel = ChannelModel( + id: channelId, + type: channelType, + extraData: {...data}, + ), + ); final res = await client.updateChannel(channelId, channelType, data); expect(res, isNotNull); expect(res.channel.cid, '$channelType:$channelId'); expect(res.channel.extraData['name'], 'test-channel'); - verify(() => api.channel.updateChannel(channelId, channelType, data)) - .called(1); + verify(() => api.channel.updateChannel(channelId, channelType, data)).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1201,14 +1216,14 @@ void main() { }; const unset = ['tag', 'last_name']; - when(() => api.channel.updateChannelPartial(channelId, channelType, - set: set, unset: unset)) - .thenAnswer((invocation) async => PartialUpdateChannelResponse() - ..channel = ChannelModel( - id: channelId, - type: channelType, - extraData: {...set}, - )); + when(() => api.channel.updateChannelPartial(channelId, channelType, set: set, unset: unset)).thenAnswer( + (invocation) async => PartialUpdateChannelResponse() + ..channel = ChannelModel( + id: channelId, + type: channelType, + extraData: {...set}, + ), + ); final res = await client.updateChannelPartial( channelId, @@ -1220,8 +1235,7 @@ void main() { expect(res.channel.cid, '$channelType:$channelId'); expect(res.channel.extraData, set); - verify(() => api.channel.updateChannelPartial(channelId, channelType, - set: set, unset: unset)).called(1); + verify(() => api.channel.updateChannelPartial(channelId, channelType, set: set, unset: unset)).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1229,8 +1243,7 @@ void main() { const id = 'test-device-id'; const provider = PushProvider.firebase; - when(() => api.device.addDevice(id, provider)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.device.addDevice(id, provider)).thenAnswer((_) async => EmptyResponse()); final res = await client.addDevice(id, provider); expect(res, isNotNull); @@ -1259,11 +1272,13 @@ void main() { ); expect(res, isNotNull); - verify(() => api.device.addDevice( - id, - provider, - pushProviderName: pushProviderName, - )).called(1); + verify( + () => api.device.addDevice( + id, + provider, + pushProviderName: pushProviderName, + ), + ).called(1); verifyNoMoreInteractions(api.device); }); @@ -1276,8 +1291,7 @@ void main() { ), ); - when(() => api.device.getDevices()) - .thenAnswer((_) async => ListDevicesResponse()..devices = devices); + when(() => api.device.getDevices()).thenAnswer((_) async => ListDevicesResponse()..devices = devices); final res = await client.getDevices(); expect(res, isNotNull); @@ -1290,8 +1304,7 @@ void main() { test('`.removeDevice`', () async { const deviceId = 'test-device-id'; - when(() => api.device.removeDevice(deviceId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.device.removeDevice(deviceId)).thenAnswer((_) async => EmptyResponse()); final res = await client.removeDevice(deviceId); expect(res, isNotNull); @@ -1411,8 +1424,7 @@ void main() { expect(channel.extraData, channelData); }); - test('should return back in memory channel instance if available', - () async { + test('should return back in memory channel instance if available', () async { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; const channelData = {'name': 'test-channel-name'}; @@ -1428,22 +1440,24 @@ void main() { channel: ChannelModel(cid: channelCid), ); - when(() => api.channel.queryChannel( - channelType, - channelId: channelId, - channelData: channelData, - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).thenAnswer((_) async => channelState); + when( + () => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).thenAnswer((_) async => channelState); expectLater( client.state.channelsStream.skip(1), emitsInOrder([ - {channelCid: isCorrectChannelFor(channelState)} + {channelCid: isCorrectChannelFor(channelState)}, ]), ); @@ -1452,17 +1466,19 @@ void main() { final newChannel = client.channel(channelType, id: channelId); expect(newChannel, channel); - verify(() => api.channel.queryChannel( - channelType, - channelId: channelId, - channelData: channelData, - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).called(1); + verify( + () => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).called(1); }); }); @@ -1476,17 +1492,19 @@ void main() { channel: ChannelModel(cid: channelCid, extraData: channelData), ); - when(() => api.channel.queryChannel( - channelType, - channelId: channelId, - channelData: channelData, - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).thenAnswer((_) async => channelState); + when( + () => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).thenAnswer((_) async => channelState); final res = await client.createChannel( channelType, @@ -1502,17 +1520,19 @@ void main() { expect(channel.cid, '$channelType:$channelId'); expect(channel.extraData, channelData); - verify(() => api.channel.queryChannel( - channelType, - channelId: channelId, - channelData: channelData, - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).called(1); + verify( + () => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1523,20 +1543,22 @@ void main() { const channelCid = '$channelType:$channelId'; final channelState = ChannelState( - channel: ChannelModel(cid: channelCid, extraData: channelData), - ); - - when(() => api.channel.queryChannel( - channelType, - channelId: channelId, - channelData: channelData, - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).thenAnswer((_) async => channelState); + channel: ChannelModel(cid: channelCid, extraData: channelData), + ); + + when( + () => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).thenAnswer((_) async => channelState); final res = await client.watchChannel( channelType, @@ -1552,17 +1574,19 @@ void main() { expect(channel.cid, '$channelType:$channelId'); expect(channel.extraData, channelData); - verify(() => api.channel.queryChannel( - channelType, - channelId: channelId, - channelData: channelData, - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).called(1); + verify( + () => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1576,17 +1600,19 @@ void main() { channel: ChannelModel(cid: channelCid, extraData: channelData), ); - when(() => api.channel.queryChannel( - channelType, - channelId: channelId, - channelData: channelData, - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).thenAnswer((_) async => channelState); + when( + () => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).thenAnswer((_) async => channelState); final res = await client.queryChannel( channelType, @@ -1602,17 +1628,19 @@ void main() { expect(channel.cid, '$channelType:$channelId'); expect(channel.extraData, channelData); - verify(() => api.channel.queryChannel( - channelType, - channelId: channelId, - channelData: channelData, - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).called(1); + verify( + () => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1640,8 +1668,7 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.hideChannel(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.channel.hideChannel(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await client.hideChannel(channelId, channelType); @@ -1655,8 +1682,7 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.showChannel(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.channel.showChannel(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await client.showChannel(channelId, channelType); @@ -1670,8 +1696,7 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.deleteChannel(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.channel.deleteChannel(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await client.deleteChannel(channelId, channelType); @@ -1685,8 +1710,7 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.truncateChannel(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.channel.truncateChannel(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await client.truncateChannel(channelId, channelType); @@ -1703,8 +1727,7 @@ void main() { const channelId = 'test-channel-id'; const channelCid = '$channelType:$channelId'; - when(() => api.moderation.muteChannel(channelCid)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.muteChannel(channelCid)).thenAnswer((_) async => EmptyResponse()); final res = await client.muteChannel(channelCid); @@ -1719,8 +1742,7 @@ void main() { const channelId = 'test-channel-id'; const channelCid = '$channelType:$channelId'; - when(() => api.moderation.unmuteChannel(channelCid)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.unmuteChannel(channelCid)).thenAnswer((_) async => EmptyResponse()); final res = await client.unmuteChannel(channelCid); @@ -1737,14 +1759,18 @@ void main() { const set = {'pinned': true}; const unset = ['pinned']; - when(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - set: set, - unset: unset, - )).thenAnswer((_) async => FakePartialUpdateMemberResponse( - channelMember: Member(userId: otherUserId), - )); + when( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + set: set, + unset: unset, + ), + ).thenAnswer( + (_) async => FakePartialUpdateMemberResponse( + channelMember: Member(userId: otherUserId), + ), + ); final res = await client.partialMemberUpdate( channelId: channelId, @@ -1756,12 +1782,14 @@ void main() { expect(res, isNotNull); expect(res.channelMember.userId, otherUserId); - verify(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - set: set, - unset: unset, - )).called(1); + verify( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + set: set, + unset: unset, + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1771,14 +1799,18 @@ void main() { const set = {'pinned': true}; const unset = ['pinned']; - when(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - set: set, - unset: unset, - )).thenAnswer((_) async => FakePartialUpdateMemberResponse( - channelMember: Member(userId: userId), - )); + when( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + set: set, + unset: unset, + ), + ).thenAnswer( + (_) async => FakePartialUpdateMemberResponse( + channelMember: Member(userId: userId), + ), + ); final res = await client.partialMemberUpdate( channelId: channelId, @@ -1789,12 +1821,14 @@ void main() { expect(res, isNotNull); expect(res.channelMember.userId, userId); - verify(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - set: set, - unset: unset, - )).called(1); + verify( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + set: set, + unset: unset, + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1802,13 +1836,17 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - set: const MemberUpdatePayload(pinned: true).toJson(), - )).thenAnswer((_) async => FakePartialUpdateMemberResponse( - channelMember: Member(userId: userId, pinnedAt: DateTime.now()), - )); + when( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + set: const MemberUpdatePayload(pinned: true).toJson(), + ), + ).thenAnswer( + (_) async => FakePartialUpdateMemberResponse( + channelMember: Member(userId: userId, pinnedAt: DateTime.now()), + ), + ); final res = await client.pinChannel( channelId: channelId, @@ -1817,11 +1855,13 @@ void main() { expect(res, isNotNull); - verify(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - set: const MemberUpdatePayload(pinned: true).toJson(), - )).called(1); + verify( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + set: const MemberUpdatePayload(pinned: true).toJson(), + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1829,13 +1869,17 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - unset: [MemberUpdateType.pinned.name], - )).thenAnswer((_) async => FakePartialUpdateMemberResponse( - channelMember: Member(userId: userId, pinnedAt: DateTime.now()), - )); + when( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + unset: [MemberUpdateType.pinned.name], + ), + ).thenAnswer( + (_) async => FakePartialUpdateMemberResponse( + channelMember: Member(userId: userId, pinnedAt: DateTime.now()), + ), + ); final res = await client.unpinChannel( channelId: channelId, @@ -1844,11 +1888,13 @@ void main() { expect(res, isNotNull); - verify(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - unset: [MemberUpdateType.pinned.name], - )).called(1); + verify( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + unset: [MemberUpdateType.pinned.name], + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1856,13 +1902,17 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - set: const MemberUpdatePayload(archived: true).toJson(), - )).thenAnswer((_) async => FakePartialUpdateMemberResponse( - channelMember: Member(userId: userId, archivedAt: DateTime.now()), - )); + when( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + set: const MemberUpdatePayload(archived: true).toJson(), + ), + ).thenAnswer( + (_) async => FakePartialUpdateMemberResponse( + channelMember: Member(userId: userId, archivedAt: DateTime.now()), + ), + ); final res = await client.archiveChannel( channelId: channelId, @@ -1871,11 +1921,13 @@ void main() { expect(res, isNotNull); - verify(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - set: const MemberUpdatePayload(archived: true).toJson(), - )).called(1); + verify( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + set: const MemberUpdatePayload(archived: true).toJson(), + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1883,13 +1935,17 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - unset: [MemberUpdateType.archived.name], - )).thenAnswer((_) async => FakePartialUpdateMemberResponse( - channelMember: Member(userId: userId, pinnedAt: DateTime.now()), - )); + when( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + unset: [MemberUpdateType.archived.name], + ), + ).thenAnswer( + (_) async => FakePartialUpdateMemberResponse( + channelMember: Member(userId: userId, pinnedAt: DateTime.now()), + ), + ); final res = await client.unarchiveChannel( channelId: channelId, @@ -1898,11 +1954,13 @@ void main() { expect(res, isNotNull); - verify(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - unset: [MemberUpdateType.archived.name], - )).called(1); + verify( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + unset: [MemberUpdateType.archived.name], + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1911,16 +1969,15 @@ void main() { const channelId = 'test-channel-id'; const channelCid = '$channelType:$channelId'; - when(() => api.channel.acceptChannelInvite(channelId, channelType)) - .thenAnswer((_) async => - AcceptInviteResponse()..channel = ChannelModel(cid: channelCid)); + when( + () => api.channel.acceptChannelInvite(channelId, channelType), + ).thenAnswer((_) async => AcceptInviteResponse()..channel = ChannelModel(cid: channelCid)); final res = await client.acceptChannelInvite(channelId, channelType); expect(res, isNotNull); expect(res.channel.cid, channelCid); - verify(() => api.channel.acceptChannelInvite(channelId, channelType)) - .called(1); + verify(() => api.channel.acceptChannelInvite(channelId, channelType)).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1929,16 +1986,15 @@ void main() { const channelId = 'test-channel-id'; const channelCid = '$channelType:$channelId'; - when(() => api.channel.rejectChannelInvite(channelId, channelType)) - .thenAnswer((_) async => - RejectInviteResponse()..channel = ChannelModel(cid: channelCid)); + when( + () => api.channel.rejectChannelInvite(channelId, channelType), + ).thenAnswer((_) async => RejectInviteResponse()..channel = ChannelModel(cid: channelCid)); final res = await client.rejectChannelInvite(channelId, channelType); expect(res, isNotNull); expect(res.channel.cid, channelCid); - verify(() => api.channel.rejectChannelInvite(channelId, channelType)) - .called(1); + verify(() => api.channel.rejectChannelInvite(channelId, channelType)).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1954,10 +2010,11 @@ void main() { final memberIds = members.map((e) => e.userId!).toList(growable: false); - when(() => api.channel.addMembers(channelId, channelType, memberIds)) - .thenAnswer((_) async => AddMembersResponse() - ..channel = ChannelModel(cid: channelCid) - ..members = members); + when(() => api.channel.addMembers(channelId, channelType, memberIds)).thenAnswer( + (_) async => AddMembersResponse() + ..channel = ChannelModel(cid: channelCid) + ..members = members, + ); final res = await client.addChannelMembers( channelId, @@ -1988,14 +2045,18 @@ void main() { final memberIds = members.map((e) => e.userId!).toList(growable: false); final hideHistoryBefore = DateTime.parse('2024-01-01T00:00:00Z'); - when(() => api.channel.addMembers( - channelId, - channelType, - memberIds, - hideHistoryBefore: hideHistoryBefore, - )).thenAnswer((_) async => AddMembersResponse() - ..channel = ChannelModel(cid: channelCid) - ..members = members); + when( + () => api.channel.addMembers( + channelId, + channelType, + memberIds, + hideHistoryBefore: hideHistoryBefore, + ), + ).thenAnswer( + (_) async => AddMembersResponse() + ..channel = ChannelModel(cid: channelCid) + ..members = members, + ); final res = await client.addChannelMembers( channelId, @@ -2031,10 +2092,11 @@ void main() { final memberIds = members.map((e) => e.userId!).toList(growable: false); - when(() => api.channel.removeMembers(channelId, channelType, memberIds)) - .thenAnswer((_) async => RemoveMembersResponse() - ..channel = ChannelModel(cid: channelCid) - ..members = members); + when(() => api.channel.removeMembers(channelId, channelType, memberIds)).thenAnswer( + (_) async => RemoveMembersResponse() + ..channel = ChannelModel(cid: channelCid) + ..members = members, + ); final res = await client.removeChannelMembers( channelId, @@ -2064,11 +2126,11 @@ void main() { final memberIds = members.map((e) => e.userId!).toList(growable: false); - when(() => api.channel - .inviteChannelMembers(channelId, channelType, memberIds)) - .thenAnswer((_) async => InviteMembersResponse() - ..channel = ChannelModel(cid: channelCid) - ..members = members); + when(() => api.channel.inviteChannelMembers(channelId, channelType, memberIds)).thenAnswer( + (_) async => InviteMembersResponse() + ..channel = ChannelModel(cid: channelCid) + ..members = members, + ); final res = await client.inviteChannelMembers( channelId, @@ -2080,8 +2142,7 @@ void main() { expect(res.channel.cid, channelCid); expect(res.members.length, memberIds.length); - verify(() => api.channel - .inviteChannelMembers(channelId, channelType, memberIds)).called(1); + verify(() => api.channel.inviteChannelMembers(channelId, channelType, memberIds)).called(1); verifyNoMoreInteractions(api.channel); }); @@ -2089,8 +2150,7 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.stopWatching(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.channel.stopWatching(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await client.stopChannelWatching(channelId, channelType); expect(res, isNotNull); @@ -2105,9 +2165,9 @@ void main() { const messageId = 'test-message-id'; const formData = {'key': 'value'}; - when(() => api.message - .sendAction(channelId, channelType, messageId, formData)) - .thenAnswer((_) async => SendActionResponse()); + when( + () => api.message.sendAction(channelId, channelType, messageId, formData), + ).thenAnswer((_) async => SendActionResponse()); final res = await client.sendAction( channelId, @@ -2118,8 +2178,7 @@ void main() { expect(res, isNotNull); - verify(() => api.message - .sendAction(channelId, channelType, messageId, formData)).called(1); + verify(() => api.message.sendAction(channelId, channelType, messageId, formData)).called(1); verifyNoMoreInteractions(api.message); }); @@ -2127,8 +2186,7 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.markRead(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.channel.markRead(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await client.markChannelRead(channelId, channelType); @@ -2143,8 +2201,7 @@ void main() { const channelId = 'test-channel-id'; const messageId = 'test-message-id'; - when(() => api.channel.markUnread(channelId, channelType, messageId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.channel.markUnread(channelId, channelType, messageId)).thenAnswer((_) async => EmptyResponse()); final res = await client.markChannelUnread( channelId, @@ -2154,8 +2211,7 @@ void main() { expect(res, isNotNull); - verify(() => api.channel.markUnread(channelId, channelType, messageId)) - .called(1); + verify(() => api.channel.markUnread(channelId, channelType, messageId)).called(1); verifyNoMoreInteractions(api.channel); }); @@ -2164,11 +2220,13 @@ void main() { const channelId = 'test-channel-id'; final timestamp = DateTime.parse('2024-01-01T00:00:00Z'); - when(() => api.channel.markUnreadByTimestamp( - channelId, - channelType, - timestamp, - )).thenAnswer((_) async => EmptyResponse()); + when( + () => api.channel.markUnreadByTimestamp( + channelId, + channelType, + timestamp, + ), + ).thenAnswer((_) async => EmptyResponse()); final res = await client.markChannelUnreadByTimestamp( channelId, @@ -2178,11 +2236,13 @@ void main() { expect(res, isNotNull); - verify(() => api.channel.markUnreadByTimestamp( - channelId, - channelType, - timestamp, - )).called(1); + verify( + () => api.channel.markUnreadByTimestamp( + channelId, + channelType, + timestamp, + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -2266,25 +2326,23 @@ void main() { ], ); - when(() => api.polls.partialUpdatePoll(pollId, set: set, unset: unset)) - .thenAnswer((_) async => UpdatePollResponse()..poll = poll); + when( + () => api.polls.partialUpdatePoll(pollId, set: set, unset: unset), + ).thenAnswer((_) async => UpdatePollResponse()..poll = poll); - final res = - await client.partialUpdatePoll(pollId, set: set, unset: unset); + final res = await client.partialUpdatePoll(pollId, set: set, unset: unset); expect(res, isNotNull); expect(res.poll.id, pollId); expect(res.poll.name, set['name']); - verify(() => api.polls.partialUpdatePoll(pollId, set: set, unset: unset)) - .called(1); + verify(() => api.polls.partialUpdatePoll(pollId, set: set, unset: unset)).called(1); verifyNoMoreInteractions(api.polls); }); test('`.deletePoll`', () async { const pollId = 'test-poll-id'; - when(() => api.polls.deletePoll(pollId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.polls.deletePoll(pollId)).thenAnswer((_) async => EmptyResponse()); final res = await client.deletePoll(pollId); expect(res, isNotNull); @@ -2296,15 +2354,14 @@ void main() { test('`.closePoll`', () async { const pollId = 'test-poll-id'; - when(() => api.polls.partialUpdatePoll(pollId, set: {'is_closed': true})) - .thenAnswer((_) async => UpdatePollResponse()); + when( + () => api.polls.partialUpdatePoll(pollId, set: {'is_closed': true}), + ).thenAnswer((_) async => UpdatePollResponse()); final res = await client.closePoll(pollId); expect(res, isNotNull); - verify(() => - api.polls.partialUpdatePoll(pollId, set: {'is_closed': true})) - .called(1); + verify(() => api.polls.partialUpdatePoll(pollId, set: {'is_closed': true})).called(1); verifyNoMoreInteractions(api.polls); }); @@ -2312,8 +2369,9 @@ void main() { const pollId = 'test-poll-id'; const option = PollOption(text: 'Red'); - when(() => api.polls.createPollOption(pollId, option)).thenAnswer( - (_) async => CreatePollOptionResponse()..pollOption = option); + when( + () => api.polls.createPollOption(pollId, option), + ).thenAnswer((_) async => CreatePollOptionResponse()..pollOption = option); final res = await client.createPollOption(pollId, option); expect(res, isNotNull); @@ -2328,8 +2386,9 @@ void main() { const optionId = 'test-option-id'; const option = PollOption(id: optionId, text: 'Red'); - when(() => api.polls.getPollOption(pollId, optionId)).thenAnswer( - (_) async => GetPollOptionResponse()..pollOption = option); + when( + () => api.polls.getPollOption(pollId, optionId), + ).thenAnswer((_) async => GetPollOptionResponse()..pollOption = option); final res = await client.getPollOption(pollId, optionId); expect(res, isNotNull); @@ -2343,8 +2402,9 @@ void main() { const pollId = 'test-poll-id'; const option = PollOption(id: 'test-option-id', text: 'Red'); - when(() => api.polls.updatePollOption(pollId, option)).thenAnswer( - (_) async => UpdatePollOptionResponse()..pollOption = option); + when( + () => api.polls.updatePollOption(pollId, option), + ).thenAnswer((_) async => UpdatePollOptionResponse()..pollOption = option); final res = await client.updatePollOption(pollId, option); expect(res, isNotNull); @@ -2358,8 +2418,7 @@ void main() { const pollId = 'test-poll-id'; const optionId = 'test-option-id'; - when(() => api.polls.deletePollOption(pollId, optionId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.polls.deletePollOption(pollId, optionId)).thenAnswer((_) async => EmptyResponse()); final res = await client.deletePollOption(pollId, optionId); expect(res, isNotNull); @@ -2376,21 +2435,19 @@ void main() { // Custom matcher to check if the Vote object has the specified id Matcher matchesVoteOption(String expected) => predicate( - (vote) => vote.optionId == expected, - 'Vote with option $expected', - ); + (vote) => vote.optionId == expected, + 'Vote with option $expected', + ); - when(() => api.polls.castPollVote( - messageId, pollId, any(that: matchesVoteOption(optionId)))) - .thenAnswer((_) async => CastPollVoteResponse()..vote = vote); + when( + () => api.polls.castPollVote(messageId, pollId, any(that: matchesVoteOption(optionId))), + ).thenAnswer((_) async => CastPollVoteResponse()..vote = vote); - final res = - await client.castPollVote(messageId, pollId, optionId: optionId); + final res = await client.castPollVote(messageId, pollId, optionId: optionId); expect(res, isNotNull); expect(res.vote, vote); - verify(() => api.polls.castPollVote( - messageId, pollId, any(that: matchesVoteOption(optionId)))).called(1); + verify(() => api.polls.castPollVote(messageId, pollId, any(that: matchesVoteOption(optionId)))).called(1); verifyNoMoreInteractions(api.polls); }); @@ -2402,22 +2459,19 @@ void main() { // Custom matcher to check if the Vote object has the specified id Matcher matchesVoteAnswer(String expected) => predicate( - (vote) => vote.answerText == expected, - 'Vote with answer $expected', - ); + (vote) => vote.answerText == expected, + 'Vote with answer $expected', + ); - when(() => api.polls.castPollVote( - messageId, pollId, any(that: matchesVoteAnswer(answerText)))) - .thenAnswer((_) async => CastPollVoteResponse()..vote = vote); + when( + () => api.polls.castPollVote(messageId, pollId, any(that: matchesVoteAnswer(answerText))), + ).thenAnswer((_) async => CastPollVoteResponse()..vote = vote); - final res = - await client.addPollAnswer(messageId, pollId, answerText: answerText); + final res = await client.addPollAnswer(messageId, pollId, answerText: answerText); expect(res, isNotNull); expect(res.vote, vote); - verify(() => api.polls.castPollVote( - messageId, pollId, any(that: matchesVoteAnswer(answerText)))) - .called(1); + verify(() => api.polls.castPollVote(messageId, pollId, any(that: matchesVoteAnswer(answerText)))).called(1); verifyNoMoreInteractions(api.polls); }); @@ -2426,14 +2480,12 @@ void main() { const pollId = 'test-poll-id'; const voteId = 'test-vote-id'; - when(() => api.polls.removePollVote(messageId, pollId, voteId)) - .thenAnswer((_) async => RemovePollVoteResponse()); + when(() => api.polls.removePollVote(messageId, pollId, voteId)).thenAnswer((_) async => RemovePollVoteResponse()); final res = await client.removePollVote(messageId, pollId, voteId); expect(res, isNotNull); - verify(() => api.polls.removePollVote(messageId, pollId, voteId)) - .called(1); + verify(() => api.polls.removePollVote(messageId, pollId, voteId)).called(1); verifyNoMoreInteractions(api.polls); }); @@ -2454,11 +2506,13 @@ void main() { ), ); - when(() => api.polls.queryPolls( - filter: filter, - sort: sort, - pagination: pagination, - )).thenAnswer( + when( + () => api.polls.queryPolls( + filter: filter, + sort: sort, + pagination: pagination, + ), + ).thenAnswer( (_) async => QueryPollsResponse()..polls = polls, ); @@ -2470,11 +2524,13 @@ void main() { expect(res, isNotNull); expect(res.polls.length, polls.length); - verify(() => api.polls.queryPolls( - filter: filter, - sort: sort, - pagination: pagination, - )).called(1); + verify( + () => api.polls.queryPolls( + filter: filter, + sort: sort, + pagination: pagination, + ), + ).called(1); verifyNoMoreInteractions(api.polls); }); @@ -2489,12 +2545,14 @@ void main() { (index) => PollVote(id: 'test-vote-id-$index', answerText: 'Red'), ); - when(() => api.polls.queryPollVotes( - pollId, - filter: filter, - sort: sort, - pagination: pagination, - )).thenAnswer( + when( + () => api.polls.queryPollVotes( + pollId, + filter: filter, + sort: sort, + pagination: pagination, + ), + ).thenAnswer( (_) async => QueryPollVotesResponse()..votes = votes, ); @@ -2507,12 +2565,14 @@ void main() { expect(res, isNotNull); expect(res.votes.length, votes.length); - verify(() => api.polls.queryPollVotes( - pollId, - filter: filter, - sort: sort, - pagination: pagination, - )).called(1); + verify( + () => api.polls.queryPollVotes( + pollId, + filter: filter, + sort: sort, + pagination: pagination, + ), + ).called(1); verifyNoMoreInteractions(api.polls); }); @@ -2522,8 +2582,7 @@ void main() { extraData: const {'name': 'test-user'}, ); - when(() => api.user.updateUsers([user])).thenAnswer( - (_) async => UpdateUsersResponse()..users = {user.id: user}); + when(() => api.user.updateUsers([user])).thenAnswer((_) async => UpdateUsersResponse()..users = {user.id: user}); final res = await client.updateUser(user); @@ -2551,8 +2610,7 @@ void main() { extraData: {'color': set['color']}, ); - when(() => api.user.partialUpdateUsers([partialUpdateRequest])) - .thenAnswer( + when(() => api.user.partialUpdateUsers([partialUpdateRequest])).thenAnswer( (_) async => UpdateUsersResponse() ..users = { updatedUser.id: updatedUser, @@ -2577,8 +2635,9 @@ void main() { test('`.banUser`', () async { const userId = 'test-user-id'; - when(() => api.moderation.banUser(userId, options: any(named: 'options'))) - .thenAnswer((_) async => EmptyResponse()); + when( + () => api.moderation.banUser(userId, options: any(named: 'options')), + ).thenAnswer((_) async => EmptyResponse()); final res = await client.banUser(userId); @@ -2593,9 +2652,9 @@ void main() { test('`.unbanUser`', () async { const userId = 'test-user-id'; - when(() => - api.moderation.unbanUser(userId, options: any(named: 'options'))) - .thenAnswer((_) async => EmptyResponse()); + when( + () => api.moderation.unbanUser(userId, options: any(named: 'options')), + ).thenAnswer((_) async => EmptyResponse()); final res = await client.unbanUser(userId); @@ -2631,8 +2690,7 @@ void main() { test('`.unblockUser`', () async { const userId = 'test-user-id'; - when(() => api.user.unblockUser(userId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.user.unblockUser(userId)).thenAnswer((_) async => EmptyResponse()); final res = await client.unblockUser(userId); @@ -2772,10 +2830,8 @@ void main() { await client.unblockUser(nonBlockedUserId); // Verify - should remain unchanged - expect(client.state.currentUser?.blockedUserIds, - contains(otherBlockedId)); - expect(client.state.currentUser?.blockedUserIds, - isNot(contains(nonBlockedUserId))); + expect(client.state.currentUser?.blockedUserIds, contains(otherBlockedId)); + expect(client.state.currentUser?.blockedUserIds, isNot(contains(nonBlockedUserId))); verify(() => api.user.unblockUser(nonBlockedUserId)).called(1); verifyNoMoreInteractions(api.user); }, @@ -2924,8 +2980,7 @@ void main() { test('`.shadowBan`', () async { const userId = 'test-user-id'; - when(() => api.moderation.banUser(userId, options: {'shadow': true})) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.banUser(userId, options: {'shadow': true})).thenAnswer((_) async => EmptyResponse()); final res = await client.shadowBan(userId); @@ -2940,8 +2995,7 @@ void main() { test('`.removeShadowBan`', () async { const userId = 'test-user-id'; - when(() => api.moderation.unbanUser(userId, options: {'shadow': true})) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.unbanUser(userId, options: {'shadow': true})).thenAnswer((_) async => EmptyResponse()); final res = await client.removeShadowBan(userId); @@ -2956,8 +3010,7 @@ void main() { test('`.muteUser`', () async { const userId = 'test-user-id'; - when(() => api.moderation.muteUser(userId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.muteUser(userId)).thenAnswer((_) async => EmptyResponse()); final res = await client.muteUser(userId); @@ -2970,8 +3023,7 @@ void main() { test('`.unmuteUser`', () async { const userId = 'test-user-id'; - when(() => api.moderation.unmuteUser(userId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.unmuteUser(userId)).thenAnswer((_) async => EmptyResponse()); final res = await client.unmuteUser(userId); @@ -2984,8 +3036,7 @@ void main() { test('`.flagMessage`', () async { const messageId = 'test-message-id'; - when(() => api.moderation.flagMessage(messageId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.flagMessage(messageId)).thenAnswer((_) async => EmptyResponse()); final res = await client.flagMessage(messageId); @@ -2998,8 +3049,7 @@ void main() { test('`.unflagMessage`', () async { const messageId = 'test-message-id'; - when(() => api.moderation.unflagMessage(messageId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.unflagMessage(messageId)).thenAnswer((_) async => EmptyResponse()); final res = await client.unflagMessage(messageId); @@ -3012,8 +3062,7 @@ void main() { test('`.flagUser`', () async { const userId = 'test-message-id'; - when(() => api.moderation.flagUser(userId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.flagUser(userId)).thenAnswer((_) async => EmptyResponse()); final res = await client.flagUser(userId); @@ -3026,8 +3075,7 @@ void main() { test('`.unflagUser`', () async { const userId = 'test-message-id'; - when(() => api.moderation.unflagUser(userId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.unflagUser(userId)).thenAnswer((_) async => EmptyResponse()); final res = await client.unflagUser(userId); @@ -3054,8 +3102,9 @@ void main() { ]; when(() => api.user.getActiveLiveLocations()).thenAnswer( - (_) async => GetActiveLiveLocationsResponse() // - ..activeLiveLocations = locations, + (_) async => + GetActiveLiveLocationsResponse() // + ..activeLiveLocations = locations, ); // Initial state should be empty @@ -3393,8 +3442,7 @@ void main() { }); test('`.markAllRead`', () async { - when(() => api.channel.markAllRead()) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.channel.markAllRead()).thenAnswer((_) async => EmptyResponse()); final res = await client.markAllRead(); expect(res, isNotNull); @@ -3415,8 +3463,7 @@ void main() { ), ]; - when(() => api.channel.markChannelsDelivered(deliveries)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.channel.markChannelsDelivered(deliveries)).thenAnswer((_) async => EmptyResponse()); final res = await client.markChannelsDelivered(deliveries); expect(res, isNotNull); @@ -3441,11 +3488,13 @@ void main() { final res = await client.sendEvent(channelId, channelType, event); expect(res, isNotNull); - verify(() => api.channel.sendEvent( - channelId, - channelType, - any(that: isSameEventAs(event)), - )).called(1); + verify( + () => api.channel.sendEvent( + channelId, + channelType, + any(that: isSameEventAs(event)), + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -3484,8 +3533,7 @@ void main() { const messageId = 'test-message-id'; const reactionType = 'like'; - when(() => api.message.deleteReaction(messageId, reactionType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.message.deleteReaction(messageId, reactionType)).thenAnswer((_) async => EmptyResponse()); final res = await client.deleteReaction(messageId, reactionType); expect(res, isNotNull); @@ -3501,19 +3549,21 @@ void main() { const channelId = 'test-channel-id'; const channelType = 'test-channel-type'; - when(() => api.message.sendMessage( - channelId, channelType, any(that: isSameMessageAs(message)))) - .thenAnswer((_) async => SendMessageResponse()..message = message); + when( + () => api.message.sendMessage(channelId, channelType, any(that: isSameMessageAs(message))), + ).thenAnswer((_) async => SendMessageResponse()..message = message); final res = await client.sendMessage(message, channelId, channelType); expect(res, isNotNull); expect(res.message, isSameMessageAs(message)); - verify(() => api.message.sendMessage( - channelId, - channelType, - any(that: isSameMessageAs(message)), - )).called(1); + verify( + () => api.message.sendMessage( + channelId, + channelType, + any(that: isSameMessageAs(message)), + ), + ).called(1); verifyNoMoreInteractions(api.message); }); @@ -3546,11 +3596,13 @@ void main() { expect(res, isNotNull); expect(res.draft.message, isSameDraftMessageAs(message)); - verify(() => api.message.createDraft( - channelId, - channelType, - any(that: isSameDraftMessageAs(message)), - )).called(1); + verify( + () => api.message.createDraft( + channelId, + channelType, + any(that: isSameDraftMessageAs(message)), + ), + ).called(1); verifyNoMoreInteractions(api.message); }); @@ -3559,8 +3611,7 @@ void main() { const channelId = 'test-channel-id'; const channelType = 'test-channel-type'; - when(() => api.message.deleteDraft(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.message.deleteDraft(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await client.deleteDraft(channelId, channelType); expect(res, isNotNull); @@ -3575,13 +3626,14 @@ void main() { final message = DraftMessage(id: 'test-message-id', text: 'Hello!'); - when(() => api.message.getDraft(channelId, channelType)) - .thenAnswer((_) async => GetDraftResponse() - ..draft = Draft( - channelCid: '$channelType:$channelId', - createdAt: DateTime.now(), - message: message, - )); + when(() => api.message.getDraft(channelId, channelType)).thenAnswer( + (_) async => GetDraftResponse() + ..draft = Draft( + channelCid: '$channelType:$channelId', + createdAt: DateTime.now(), + message: message, + ), + ); final res = await client.getDraft(channelId, channelType); @@ -3601,11 +3653,10 @@ void main() { channelCid: '$channelType:$channelId', createdAt: DateTime.now(), message: DraftMessage(id: 'test-message-id', text: 'Hello!'), - ) + ), ]; - when(() => api.message.queryDrafts()) - .thenAnswer((_) async => QueryDraftsResponse()..drafts = drafts); + when(() => api.message.queryDrafts()).thenAnswer((_) async => QueryDraftsResponse()..drafts = drafts); final res = await client.queryDrafts(); @@ -3624,8 +3675,7 @@ void main() { (index) => Message(id: 'test-message-id-$index'), ); - when(() => api.message.getReplies(parentId)) - .thenAnswer((_) async => QueryRepliesResponse()..messages = messages); + when(() => api.message.getReplies(parentId)).thenAnswer((_) async => QueryRepliesResponse()..messages = messages); final res = await client.getReplies(parentId); expect(res, isNotNull); @@ -3646,8 +3696,9 @@ void main() { ), ); - when(() => api.message.getReactions(messageId)).thenAnswer( - (_) async => QueryReactionsResponse()..reactions = reactions); + when( + () => api.message.getReactions(messageId), + ).thenAnswer((_) async => QueryReactionsResponse()..reactions = reactions); final res = await client.getReactions(messageId); expect(res, isNotNull); @@ -3661,8 +3712,9 @@ void main() { test('`.updateMessage`', () async { final message = Message(id: 'test-message-id', text: 'Hello!'); - when(() => api.message.updateMessage(any(that: isSameMessageAs(message)))) - .thenAnswer((_) async => UpdateMessageResponse()..message = message); + when( + () => api.message.updateMessage(any(that: isSameMessageAs(message))), + ).thenAnswer((_) async => UpdateMessageResponse()..message = message); final res = await client.updateMessage(message); expect(res, isNotNull); @@ -3677,8 +3729,7 @@ void main() { test('`.deleteMessage`', () async { const messageId = 'test-message-id'; - when(() => api.message.deleteMessage(messageId, hard: false)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.message.deleteMessage(messageId, hard: false)).thenAnswer((_) async => EmptyResponse()); final res = await client.deleteMessage(messageId); expect(res, isNotNull); @@ -3690,14 +3741,12 @@ void main() { test('`.deleteMessageForMe`', () async { const messageId = 'test-message-id'; - when(() => api.message.deleteMessage(messageId, deleteForMe: true)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.message.deleteMessage(messageId, deleteForMe: true)).thenAnswer((_) async => EmptyResponse()); final res = await client.deleteMessageForMe(messageId); expect(res, isNotNull); - verify(() => api.message.deleteMessage(messageId, deleteForMe: true)) - .called(1); + verify(() => api.message.deleteMessage(messageId, deleteForMe: true)).called(1); verifyNoMoreInteractions(api.message); }); @@ -3705,8 +3754,7 @@ void main() { const messageId = 'test-message-id'; final message = Message(id: messageId); - when(() => api.message.getMessage(messageId)) - .thenAnswer((_) async => GetMessageResponse()..message = message); + when(() => api.message.getMessage(messageId)).thenAnswer((_) async => GetMessageResponse()..message = message); final res = await client.getMessage(messageId); expect(res, isNotNull); @@ -3774,11 +3822,13 @@ void main() { final updateMessageResponse = UpdateMessageResponse() ..message = message.copyWith(text: set['text'], pinExpires: null); - when(() => api.message.partialUpdateMessage( - message.id, - set: set, - unset: unset, - )).thenAnswer((_) async => updateMessageResponse); + when( + () => api.message.partialUpdateMessage( + message.id, + set: set, + unset: unset, + ), + ).thenAnswer((_) async => updateMessageResponse); final res = await client.partialUpdateMessage( messageId, @@ -3792,30 +3842,35 @@ void main() { expect(res.message.text, set['text']); expect(res.message.pinExpires, isNull); - verify(() => api.message.partialUpdateMessage( - message.id, - set: set, - unset: unset, - )).called(1); + verify( + () => api.message.partialUpdateMessage( + message.id, + set: set, + unset: unset, + ), + ).called(1); verifyNoMoreInteractions(api.message); }); group('`.pinMessage`', () { - test('should work fine without passing timeoutOrExpirationDate', - () async { + test('should work fine without passing timeoutOrExpirationDate', () async { const messageId = 'test-message-id'; final message = Message(id: messageId); - when(() => api.message.partialUpdateMessage( - messageId, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).thenAnswer((_) async => UpdateMessageResponse() - ..message = message.copyWith( - pinned: true, - pinExpires: null, - state: MessageState.sent, - )); + when( + () => api.message.partialUpdateMessage( + messageId, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).thenAnswer( + (_) async => UpdateMessageResponse() + ..message = message.copyWith( + pinned: true, + pinExpires: null, + state: MessageState.sent, + ), + ); final res = await client.pinMessage(messageId); @@ -3823,11 +3878,13 @@ void main() { expect(res.message.pinned, isTrue); expect(res.message.pinExpires, isNull); - verify(() => api.message.partialUpdateMessage( - messageId, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).called(1); + verify( + () => api.message.partialUpdateMessage( + messageId, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).called(1); verifyNoMoreInteractions(api.message); }); @@ -3838,18 +3895,22 @@ void main() { final message = Message(id: messageId); const timeoutOrExpirationDate = 300; // 300 seconds - when(() => api.message.partialUpdateMessage( - message.id, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).thenAnswer((_) async => UpdateMessageResponse() - ..message = message.copyWith( - pinned: true, - pinExpires: DateTime.now().add( - const Duration(seconds: timeoutOrExpirationDate), + when( + () => api.message.partialUpdateMessage( + message.id, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).thenAnswer( + (_) async => UpdateMessageResponse() + ..message = message.copyWith( + pinned: true, + pinExpires: DateTime.now().add( + const Duration(seconds: timeoutOrExpirationDate), + ), + state: MessageState.sent, ), - state: MessageState.sent, - )); + ); final res = await client.pinMessage( messageId, @@ -3860,11 +3921,13 @@ void main() { expect(res.message.pinned, isTrue); expect(res.message.pinExpires, isNotNull); - verify(() => api.message.partialUpdateMessage( - messageId, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).called(1); + verify( + () => api.message.partialUpdateMessage( + messageId, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).called(1); verifyNoMoreInteractions(api.message); }, ); @@ -3874,19 +3937,22 @@ void main() { () async { const messageId = 'test-message-id'; final message = Message(id: messageId); - final timeoutOrExpirationDate = - DateTime.now().add(const Duration(days: 3)); // 3 days - - when(() => api.message.partialUpdateMessage( - messageId, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).thenAnswer((_) async => UpdateMessageResponse() - ..message = message.copyWith( - pinned: true, - pinExpires: timeoutOrExpirationDate, - state: MessageState.sent, - )); + final timeoutOrExpirationDate = DateTime.now().add(const Duration(days: 3)); // 3 days + + when( + () => api.message.partialUpdateMessage( + messageId, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).thenAnswer( + (_) async => UpdateMessageResponse() + ..message = message.copyWith( + pinned: true, + pinExpires: timeoutOrExpirationDate, + state: MessageState.sent, + ), + ); final res = await client.pinMessage( messageId, @@ -3898,11 +3964,13 @@ void main() { expect(res.message.pinExpires, isNotNull); expect(res.message.pinExpires, timeoutOrExpirationDate.toUtc()); - verify(() => api.message.partialUpdateMessage( - messageId, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).called(1); + verify( + () => api.message.partialUpdateMessage( + messageId, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).called(1); verifyNoMoreInteractions(api.message); }, ); @@ -3929,30 +3997,35 @@ void main() { const messageId = 'test-message-id'; final message = Message(id: messageId, pinned: true); - when(() => api.message.partialUpdateMessage( - messageId, - set: {'pinned': false}, - )).thenAnswer((_) async => UpdateMessageResponse() - ..message = message.copyWith( - pinned: false, - state: MessageState.sent, - )); + when( + () => api.message.partialUpdateMessage( + messageId, + set: {'pinned': false}, + ), + ).thenAnswer( + (_) async => UpdateMessageResponse() + ..message = message.copyWith( + pinned: false, + state: MessageState.sent, + ), + ); final res = await client.unpinMessage(messageId); expect(res, isNotNull); expect(res.message.pinned, isFalse); - verify(() => api.message.partialUpdateMessage( - messageId, - set: {'pinned': false}, - )).called(1); + verify( + () => api.message.partialUpdateMessage( + messageId, + set: {'pinned': false}, + ), + ).called(1); verifyNoMoreInteractions(api.message); }); test('`.enrichUrl`', () async { - const url = - 'https://www.techyourchance.com/finite-state-machine-with-unit-tests-real-world-example'; + const url = 'https://www.techyourchance.com/finite-state-machine-with-unit-tests-real-world-example'; when(() => api.general.enrichUrl(url)).thenAnswer( (_) async => OGAttachmentResponse() @@ -4335,8 +4408,7 @@ void main() { expect(channel2.state?.messages.length, equals(0)); // Verify other user's message is unaffected - final safeMessage = - channel1.state?.messages.firstWhere((m) => m.id == 'msg-3'); + final safeMessage = channel1.state?.messages.firstWhere((m) => m.id == 'msg-3'); expect(safeMessage?.user?.id, equals('other-user')); }, ); diff --git a/packages/stream_chat/test/src/core/api/attachment_file_uploader_test.dart b/packages/stream_chat/test/src/core/api/attachment_file_uploader_test.dart index 5250dbbc9a..1b319718e1 100644 --- a/packages/stream_chat/test/src/core/api/attachment_file_uploader_test.dart +++ b/packages/stream_chat/test/src/core/api/attachment_file_uploader_test.dart @@ -19,10 +19,10 @@ void main() { }); Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); test('sendImage', () async { const channelId = 'test-channel-id'; @@ -37,12 +37,19 @@ void main() { ); final multipartFile = await attachmentFile.toMultipartFile(); - when(() => client.postFile( - path, - any(that: isSameMultipartFileAs(multipartFile)), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'file': 'test-file-url', - })); + }, + ), + ); final res = await fileUploader.sendImage( attachmentFile, @@ -54,10 +61,12 @@ void main() { expect(res.file, isNotNull); expect(res.file, isNotEmpty); - verify(() => client.postFile( - path, - any(that: isSameMultipartFileAs(multipartFile)), - )).called(1); + verify( + () => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -74,12 +83,19 @@ void main() { ); final multipartFile = await attachmentFile.toMultipartFile(); - when(() => client.postFile( - path, - any(that: isSameMultipartFileAs(multipartFile)), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'file': 'test-file-url', - })); + }, + ), + ); final res = await fileUploader.sendFile( attachmentFile, @@ -91,10 +107,12 @@ void main() { expect(res.file, isNotNull); expect(res.file, isNotEmpty); - verify(() => client.postFile( - path, - any(that: isSameMultipartFileAs(multipartFile)), - )).called(1); + verify( + () => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -105,8 +123,9 @@ void main() { const url = 'test-image-url'; - when(() => client.delete(path, queryParameters: {'url': url})).thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.delete(path, queryParameters: {'url': url}), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await fileUploader.deleteImage(url, channelId, channelType); @@ -123,8 +142,9 @@ void main() { const url = 'test-file-url'; - when(() => client.delete(path, queryParameters: {'url': url})).thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.delete(path, queryParameters: {'url': url}), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await fileUploader.deleteFile(url, channelId, channelType); @@ -144,12 +164,19 @@ void main() { ); final multipartFile = await attachmentFile.toMultipartFile(); - when(() => client.postFile( - path, - any(that: isSameMultipartFileAs(multipartFile)), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'file': 'test-image-url', - })); + }, + ), + ); final res = await fileUploader.uploadImage(attachmentFile); @@ -157,10 +184,12 @@ void main() { expect(res.file, isNotNull); expect(res.file, isNotEmpty); - verify(() => client.postFile( - path, - any(that: isSameMultipartFileAs(multipartFile)), - )).called(1); + verify( + () => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -174,12 +203,19 @@ void main() { ); final multipartFile = await attachmentFile.toMultipartFile(); - when(() => client.postFile( - path, - any(that: isSameMultipartFileAs(multipartFile)), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'file': 'test-file-url', - })); + }, + ), + ); final res = await fileUploader.uploadFile(attachmentFile); @@ -187,10 +223,12 @@ void main() { expect(res.file, isNotNull); expect(res.file, isNotEmpty); - verify(() => client.postFile( - path, - any(that: isSameMultipartFileAs(multipartFile)), - )).called(1); + verify( + () => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -198,8 +236,9 @@ void main() { const path = '/uploads/image'; const url = 'test-image-url'; - when(() => client.delete(path, queryParameters: {'url': url})).thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.delete(path, queryParameters: {'url': url}), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await fileUploader.removeImage(url); @@ -213,8 +252,9 @@ void main() { const path = '/uploads/file'; const url = 'test-file-url'; - when(() => client.delete(path, queryParameters: {'url': url})).thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.delete(path, queryParameters: {'url': url}), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await fileUploader.removeFile(url); diff --git a/packages/stream_chat/test/src/core/api/channel_api_test.dart b/packages/stream_chat/test/src/core/api/channel_api_test.dart index ae5a0dba81..1c4ae95c13 100644 --- a/packages/stream_chat/test/src/core/api/channel_api_test.dart +++ b/packages/stream_chat/test/src/core/api/channel_api_test.dart @@ -9,8 +9,7 @@ import 'package:test/test.dart'; import '../../mocks.dart'; void main() { - String _getChannelUrl(String channelId, String channelType) => - '/channels/$channelType/$channelId'; + String _getChannelUrl(String channelId, String channelType) => '/channels/$channelType/$channelId'; ChannelState _generateChannelState( String channelId, @@ -53,10 +52,10 @@ void main() { } Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); late final client = MockHttpClient(); late ChannelApi channelApi; @@ -88,13 +87,17 @@ void main() { 'watchers': watchersPagination, }; - when(() => client.post( - path, - data: data, - )).thenAnswer((_) async => successResponse( - path, - data: channelState.toJson(), - )); + when( + () => client.post( + path, + data: data, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: channelState.toJson(), + ), + ); final res = await channelApi.queryChannel( channelType, @@ -143,20 +146,24 @@ void main() { 'message_limit': messageLimit, // pagination - ...const PaginationParams().toJson() + ...const PaginationParams().toJson(), }); - when(() => client.get( - path, - queryParameters: { - 'payload': payload, - }, - )).thenAnswer((_) async => successResponse( - path, - data: { - 'channels': [channelState.toJson()] - }, - )); + when( + () => client.get( + path, + queryParameters: { + 'payload': payload, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'channels': [channelState.toJson()], + }, + ), + ); final res = await channelApi.queryChannels( filter: filter, @@ -176,8 +183,7 @@ void main() { test('markAllRead', () async { const path = '/channels/read'; - when(() => client.post(path, data: {})).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.post(path, data: {})).thenAnswer((_) async => successResponse(path, data: {})); final res = await channelApi.markAllRead(); @@ -201,18 +207,23 @@ void main() { extraData: data, ); - when(() => client.post( - path, - data: any( - named: 'data', - that: wrapMatcher((Map v) => - containsPair('data', data).matches(v, {}) && - contains('message').matches(v, {})), - ), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: any( + named: 'data', + that: wrapMatcher((Map v) => containsPair('data', data).matches(v, {}) && contains('message').matches(v, {})), + ), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), 'message': message.toJson(), - })); + }, + ), + ); final res = await channelApi.updateChannel( channelId, @@ -249,9 +260,14 @@ void main() { when( () => client.patch(path, data: {'set': set, 'unset': unset}), - ).thenAnswer((_) async => successResponse(path, data: { + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), - })); + }, + ), + ); final res = await channelApi.updateChannelPartial( channelId, @@ -277,16 +293,23 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.post( - path, - data: { - 'accept_invite': true, - 'message': message, - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: { + 'accept_invite': true, + 'message': message, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), 'message': message.toJson(), - })); + }, + ), + ); final res = await channelApi.acceptChannelInvite( channelId, @@ -311,16 +334,23 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.post( - path, - data: { - 'reject_invite': true, - 'message': message, - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: { + 'reject_invite': true, + 'message': message, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), 'message': message.toJson(), - })); + }, + ), + ); final res = await channelApi.rejectChannelInvite( channelId, @@ -345,16 +375,23 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.post( - path, - data: { - 'invites': memberIds, - 'message': message, - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: { + 'invites': memberIds, + 'message': message, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), 'message': message.toJson(), - })); + }, + ), + ); final res = await channelApi.inviteChannelMembers( channelId, @@ -381,17 +418,24 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.post( - path, - data: { - 'add_members': memberIds, - 'message': message, - 'hide_history': hideHistory, - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: { + 'add_members': memberIds, + 'message': message, + 'hide_history': hideHistory, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), 'message': message.toJson(), - })); + }, + ), + ); final res = await channelApi.addMembers( channelId, @@ -419,17 +463,24 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.post( - path, - data: { - 'add_members': memberIds, - 'message': message, - 'hide_history_before': hideHistoryBefore.toUtc().toIso8601String(), - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: { + 'add_members': memberIds, + 'message': message, + 'hide_history_before': hideHistoryBefore.toUtc().toIso8601String(), + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), 'message': message.toJson(), - })); + }, + ), + ); final res = await channelApi.addMembers( channelId, @@ -447,8 +498,7 @@ void main() { verifyNoMoreInteractions(client); }); - test('addMembers with hideHistoryBefore takes precedence over hideHistory', - () async { + test('addMembers with hideHistoryBefore takes precedence over hideHistory', () async { const channelId = 'test-channel-id'; const channelType = 'test-channel-type'; const memberIds = ['test-member-id-1', 'test-member-id-2']; @@ -459,17 +509,24 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.post( - path, - data: { - 'add_members': memberIds, - 'message': message, - 'hide_history_before': hideHistoryBefore.toUtc().toIso8601String(), - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: { + 'add_members': memberIds, + 'message': message, + 'hide_history_before': hideHistoryBefore.toUtc().toIso8601String(), + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), 'message': message.toJson(), - })); + }, + ), + ); final res = await channelApi.addMembers( channelId, @@ -497,16 +554,23 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.post( - path, - data: { - 'remove_members': memberIds, - 'message': message, - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: { + 'remove_members': memberIds, + 'message': message, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), 'message': message.toJson(), - })); + }, + ), + ); final res = await channelApi.removeMembers( channelId, @@ -530,8 +594,9 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/event'; - when(() => client.post(path, data: {'event': event})).thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post(path, data: {'event': event}), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await channelApi.sendEvent(channelId, channelType, event); @@ -547,8 +612,7 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.delete(path)).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.delete(path)).thenAnswer((_) async => successResponse(path, data: {})); final res = await channelApi.deleteChannel(channelId, channelType); @@ -564,21 +628,23 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/truncate'; - when(() => client.post( - path, - data: {}, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: {}, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await channelApi.truncateChannel(channelId, channelType); expect(res, isNotNull); - verify(() => client.post( - path, - data: {}, - )).called(1); + verify( + () => client.post( + path, + data: {}, + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -638,21 +704,23 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/show'; - when(() => client.post( - path, - data: {}, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: {}, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await channelApi.showChannel(channelId, channelType); expect(res, isNotNull); - verify(() => client.post( - path, - data: {}, - )).called(1); + verify( + () => client.post( + path, + data: {}, + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -663,14 +731,14 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/read'; - when(() => client.post( - path, - data: { - 'message_id': messageId, - }, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'message_id': messageId, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await channelApi.markRead( channelId, @@ -691,12 +759,12 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/unread'; - when(() => client.post( - path, - data: {'message_id': messageId}, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: {'message_id': messageId}, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await channelApi.markUnread( channelId, @@ -717,14 +785,14 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/unread'; - when(() => client.post( - path, - data: { - 'message_timestamp': timestamp.toUtc().toIso8601String(), - }, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'message_timestamp': timestamp.toUtc().toIso8601String(), + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await channelApi.markUnreadByTimestamp( channelId, @@ -745,17 +813,22 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/member/{user_id}'; const archivedAt = '2025-04-10 10:27:03.150349'; - when(() => client.patch( - path, - data: { - 'set': {'archived': true}, + when( + () => client.patch( + path, + data: { + 'set': {'archived': true}, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'channel_member': { + 'archived_at': archivedAt, }, - )).thenAnswer( - (_) async => successResponse(path, data: { - 'channel_member': { - 'archived_at': archivedAt, - } - }), + }, + ), ); final res = await channelApi.updateMemberPartial( @@ -777,14 +850,15 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/member/{user_id}'; - when(() => client.patch( - path, - data: { - 'unset': ['archived'], - }, - )).thenAnswer( - (_) async => successResponse(path, - data: {'channel_member': {}}), + when( + () => client.patch( + path, + data: { + 'unset': ['archived'], + }, + ), + ).thenAnswer( + (_) async => successResponse(path, data: {'channel_member': {}}), ); final res = await channelApi.updateMemberPartial( @@ -807,17 +881,22 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/member/{user_id}'; const pinnedAt = '2025-04-10 10:27:03.150349'; - when(() => client.patch( - path, - data: { - 'set': {'pinned': true}, + when( + () => client.patch( + path, + data: { + 'set': {'pinned': true}, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'channel_member': { + 'pinned_at': pinnedAt, }, - )).thenAnswer( - (_) async => successResponse(path, data: { - 'channel_member': { - 'pinned_at': pinnedAt, - } - }), + }, + ), ); final res = await channelApi.updateMemberPartial( @@ -839,14 +918,15 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/member/{user_id}'; - when(() => client.patch( - path, - data: { - 'unset': ['pinned'], - }, - )).thenAnswer( - (_) async => successResponse(path, - data: {'channel_member': {}}), + when( + () => client.patch( + path, + data: { + 'unset': ['pinned'], + }, + ), + ).thenAnswer( + (_) async => successResponse(path, data: {'channel_member': {}}), ); final res = await channelApi.updateMemberPartial( @@ -868,8 +948,7 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/stop-watching'; - when(() => client.post(path, data: {})).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.post(path, data: {})).thenAnswer((_) async => successResponse(path, data: {})); final res = await channelApi.stopWatching(channelId, channelType); @@ -895,20 +974,34 @@ void main() { extraData: set, ); - when(() => client.patch(path, data: { + when( + () => client.patch( + path, + data: { 'set': set, - })).thenAnswer((_) async => successResponse(path, data: { + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), - })); + }, + ), + ); - final res = - await channelApi.enableSlowdown(channelId, channelType, cooldown); + final res = await channelApi.enableSlowdown(channelId, channelType, cooldown); expect(res, isNotNull); - verify(() => client.patch(path, data: { + verify( + () => client.patch( + path, + data: { 'set': set, - })).called(1); + }, + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -924,19 +1017,34 @@ void main() { type: channelType, ); - when(() => client.patch(path, data: { + when( + () => client.patch( + path, + data: { 'unset': unset, - })).thenAnswer((_) async => successResponse(path, data: { + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), - })); + }, + ), + ); final res = await channelApi.disableSlowdown(channelId, channelType); expect(res, isNotNull); - verify(() => client.patch(path, data: { + verify( + () => client.patch( + path, + data: { 'unset': unset, - })).called(1); + }, + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -954,24 +1062,30 @@ void main() { ), ]; - when(() => client.post( - path, - data: any(named: 'data'), - )).thenAnswer((_) async => successResponse( - path, - data: {}, - )); + when( + () => client.post( + path, + data: any(named: 'data'), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: {}, + ), + ); final res = await channelApi.markChannelsDelivered(deliveries); expect(res, isNotNull); - verify(() => client.post( - path, - data: jsonEncode({ - 'latest_delivered_messages': deliveries, - }), - )).called(1); + verify( + () => client.post( + path, + data: jsonEncode({ + 'latest_delivered_messages': deliveries, + }), + ), + ).called(1); verifyNoMoreInteractions(client); }); } diff --git a/packages/stream_chat/test/src/core/api/device_api_test.dart b/packages/stream_chat/test/src/core/api/device_api_test.dart index c9d06a843d..8318d8dda6 100644 --- a/packages/stream_chat/test/src/core/api/device_api_test.dart +++ b/packages/stream_chat/test/src/core/api/device_api_test.dart @@ -8,10 +8,10 @@ import '../../mocks.dart'; void main() { Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); late final client = MockHttpClient(); late DeviceApi deviceApi; @@ -40,19 +40,16 @@ void main() { path, data: data, ); - }).thenAnswer( - (_) async => successResponse(path, data: {})); + }).thenAnswer((_) async => successResponse(path, data: {})); - final res = - await deviceApi.addDevice(deviceId, pushProviderMapEntry.value); + final res = await deviceApi.addDevice(deviceId, pushProviderMapEntry.value); expect(res, isNotNull); verify(() => client.post(path, data: data)).called(1); } verifyNoMoreInteractions(client); - expect(pushProvidersMap.length, PushProvider.values.length, - reason: 'All PushProvider should be tested'); + expect(pushProvidersMap.length, PushProvider.values.length, reason: 'All PushProvider should be tested'); }); test('addDevice should work with pushProviderName', () async { @@ -62,16 +59,16 @@ void main() { const path = '/devices'; - when(() => client.post( - path, - data: { - 'id': deviceId, - 'push_provider': pushProvider.name, - 'push_provider_name': pushProviderName, - }, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'id': deviceId, + 'push_provider': pushProvider.name, + 'push_provider_name': pushProviderName, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await deviceApi.addDevice( deviceId, @@ -97,9 +94,12 @@ void main() { ); when(() => client.get(path)).thenAnswer( - (_) async => successResponse(path, data: { - 'devices': [...devices.map((it) => it.toJson())] - }), + (_) async => successResponse( + path, + data: { + 'devices': [...devices.map((it) => it.toJson())], + }, + ), ); final res = await deviceApi.getDevices(); @@ -141,10 +141,13 @@ void main() { ]; when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: { - 'user_preferences': {}, - 'user_channel_preferences': {}, - }), + (_) async => successResponse( + path, + data: { + 'user_preferences': {}, + 'user_channel_preferences': {}, + }, + ), ); final res = await deviceApi.setPushPreferences(preferences); @@ -170,10 +173,13 @@ void main() { ]; when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: { - 'user_preferences': {}, - 'user_channel_preferences': {}, - }), + (_) async => successResponse( + path, + data: { + 'user_preferences': {}, + 'user_channel_preferences': {}, + }, + ), ); final res = await deviceApi.setPushPreferences(preferences); @@ -195,10 +201,13 @@ void main() { ]; when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: { - 'user_preferences': {}, - 'user_channel_preferences': {}, - }), + (_) async => successResponse( + path, + data: { + 'user_preferences': {}, + 'user_channel_preferences': {}, + }, + ), ); final res = await deviceApi.setPushPreferences(preferences); diff --git a/packages/stream_chat/test/src/core/api/general_api_test.dart b/packages/stream_chat/test/src/core/api/general_api_test.dart index 52135981fe..cd41156ce1 100644 --- a/packages/stream_chat/test/src/core/api/general_api_test.dart +++ b/packages/stream_chat/test/src/core/api/general_api_test.dart @@ -10,10 +10,10 @@ import '../../mocks.dart'; void main() { Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); late final client = MockHttpClient(); late GeneralApi generalApi; @@ -28,20 +28,26 @@ void main() { const path = '/sync'; - final events = - List.generate(3, (index) => Event(type: 'test-event-type-$index')); + final events = List.generate(3, (index) => Event(type: 'test-event-type-$index')); final data = { 'channel_cids': cids, 'last_sync_at': lastSyncAt.toUtc().toIso8601String(), }; - when(() => client.post( - path, - data: data, - )).thenAnswer((_) async => successResponse(path, data: { - 'events': [...events.map((it) => it.toJson())] - })); + when( + () => client.post( + path, + data: data, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'events': [...events.map((it) => it.toJson())], + }, + ), + ); final res = await generalApi.sync(cids, lastSyncAt); @@ -204,14 +210,21 @@ void main() { ...pagination.toJson(), }); - when(() => client.get( - path, - queryParameters: { - 'payload': payload, - }, - )).thenAnswer((_) async => successResponse(path, data: { - 'members': [...members.map((it) => it.toJson())] - })); + when( + () => client.get( + path, + queryParameters: { + 'payload': payload, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'members': [...members.map((it) => it.toJson())], + }, + ), + ); final res = await generalApi.queryMembers( channelType, @@ -251,14 +264,21 @@ void main() { ...pagination.toJson(), }); - when(() => client.get( - path, - queryParameters: { - 'payload': payload, - }, - )).thenAnswer((_) async => successResponse(path, data: { - 'members': [...members.map((it) => it.toJson())] - })); + when( + () => client.get( + path, + queryParameters: { + 'payload': payload, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'members': [...members.map((it) => it.toJson())], + }, + ), + ); final res = await generalApi.queryMembers( channelType, @@ -280,18 +300,24 @@ void main() { test('enrichUrl', () async { const path = '/og'; - const url = - 'https://www.techyourchance.com/finite-state-machine-with-unit-tests-real-world-example'; + const url = 'https://www.techyourchance.com/finite-state-machine-with-unit-tests-real-world-example'; - when(() => client.get( - path, - queryParameters: {'url': url}, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.get( + path, + queryParameters: {'url': url}, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'type': 'image', 'og_scrape_url': url, 'author_name': 'TechYourChance', 'title': 'Finite State Machine with Unit Tests: Real World Example', - })); + }, + ), + ); final res = await generalApi.enrichUrl(url); diff --git a/packages/stream_chat/test/src/core/api/guest_api_test.dart b/packages/stream_chat/test/src/core/api/guest_api_test.dart index 7db74b4a31..40f83e7bee 100644 --- a/packages/stream_chat/test/src/core/api/guest_api_test.dart +++ b/packages/stream_chat/test/src/core/api/guest_api_test.dart @@ -8,10 +8,10 @@ import '../../mocks.dart'; void main() { Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); late final client = MockHttpClient(); late GuestApi guestApi; @@ -26,13 +26,20 @@ void main() { const path = '/guest'; - when(() => client.post( - path, - data: {'user': user}, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: {'user': user}, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'access_token': accessToken, 'user': user.toJson(), - })); + }, + ), + ); final res = await guestApi.getGuestUser(user); diff --git a/packages/stream_chat/test/src/core/api/message_api_test.dart b/packages/stream_chat/test/src/core/api/message_api_test.dart index bfe9bd7815..fb6410166f 100644 --- a/packages/stream_chat/test/src/core/api/message_api_test.dart +++ b/packages/stream_chat/test/src/core/api/message_api_test.dart @@ -10,10 +10,10 @@ import '../../mocks.dart'; void main() { Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); late final client = MockHttpClient(); late MessageApi messageApi; @@ -29,16 +29,23 @@ void main() { const path = '/channels/$channelType/$channelId/message'; - when(() => client.post( - path, - data: { - 'message': message, - 'skip_push': false, - 'skip_enrich_url': false, - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: { + 'message': message, + 'skip_push': false, + 'skip_enrich_url': false, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'message': message.toJson(), - })); + }, + ), + ); final res = await messageApi.sendMessage(channelId, channelType, message); @@ -56,16 +63,23 @@ void main() { const path = '/channels/$channelType/$channelId/message'; - when(() => client.post( - path, - data: { - 'message': message, - 'skip_push': true, - 'skip_enrich_url': false, - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: { + 'message': message, + 'skip_push': true, + 'skip_enrich_url': false, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'message': message.toJson(), - })); + }, + ), + ); final res = await messageApi.sendMessage( channelId, @@ -93,12 +107,19 @@ void main() { (index) => Message(id: 'test-message-id-$index'), ); - when(() => client.get( - path, - queryParameters: {'ids': messageIds.join(',')}, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.get( + path, + queryParameters: {'ids': messageIds.join(',')}, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'messages': [...messages.map((it) => it.toJson())], - })); + }, + ), + ); final res = await messageApi.getMessagesById( channelId, @@ -122,8 +143,7 @@ void main() { final message = Message(id: messageId); - when(() => client.get(path)).thenAnswer((_) async => - successResponse(path, data: {'message': message.toJson()})); + when(() => client.get(path)).thenAnswer((_) async => successResponse(path, data: {'message': message.toJson()})); final res = await messageApi.getMessage(messageId); @@ -139,14 +159,16 @@ void main() { final path = '/messages/${message.id}'; - when(() => client.post( - path, - data: { - 'message': message, - 'skip_push': false, - 'skip_enrich_url': false, - }, - )).thenAnswer( + when( + () => client.post( + path, + data: { + 'message': message, + 'skip_push': false, + 'skip_enrich_url': false, + }, + ), + ).thenAnswer( (_) async => successResponse(path, data: {'message': message.toJson()}), ); @@ -168,14 +190,16 @@ void main() { const path = '/messages/$messageId'; final message = Message(id: 'test-message-id', text: set['text']); - when(() => client.put( - path, - data: { - 'set': set, - 'unset': unset, - 'skip_enrich_url': false, - }, - )).thenAnswer( + when( + () => client.put( + path, + data: { + 'set': set, + 'unset': unset, + 'skip_enrich_url': false, + }, + ), + ).thenAnswer( (_) async => successResponse(path, data: {'message': message.toJson()}), ); @@ -190,14 +214,16 @@ void main() { expect(res.message.text, set['text']); expect(res.message.pinExpires, isNull); - verify(() => client.put( - path, - data: { - 'set': set, - 'unset': unset, - 'skip_enrich_url': false, - }, - )).called(1); + verify( + () => client.put( + path, + data: { + 'set': set, + 'unset': unset, + 'skip_enrich_url': false, + }, + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -227,17 +253,17 @@ void main() { const path = '/messages/$messageId/action'; - when(() => client.post( - path, - data: { - 'id': channelId, - 'type': channelType, - 'form_data': formData, - 'message_id': messageId, - }, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'id': channelId, + 'type': channelType, + 'form_data': formData, + 'message_id': messageId, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await messageApi.sendAction( channelId, @@ -261,17 +287,24 @@ void main() { final message = Message(id: messageId); final reaction = Reaction(type: reactionType, messageId: messageId); - when(() => client.post( - path, - data: jsonEncode({ - 'reaction': reaction.toJson(), - 'skip_push': false, - 'enforce_unique': false, - }), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: jsonEncode({ + 'reaction': reaction.toJson(), + 'skip_push': false, + 'enforce_unique': false, + }), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'message': message.toJson(), 'reaction': {...reaction.toJson(), 'message_id': messageId}, - })); + }, + ), + ); final res = await messageApi.sendReaction(messageId, reaction); @@ -293,17 +326,24 @@ void main() { final message = Message(id: messageId); final reaction = Reaction(type: reactionType, messageId: messageId); - when(() => client.post( - path, - data: jsonEncode({ - 'reaction': reaction.toJson(), - 'skip_push': false, - 'enforce_unique': true, - }), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: jsonEncode({ + 'reaction': reaction.toJson(), + 'skip_push': false, + 'enforce_unique': true, + }), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'message': message.toJson(), 'reaction': {...reaction.toJson(), 'message_id': messageId}, - })); + }, + ), + ); final res = await messageApi.sendReaction( messageId, @@ -326,8 +366,7 @@ void main() { const path = '/messages/$messageId/reaction/$reactionType'; - when(() => client.delete(path)).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.delete(path)).thenAnswer((_) async => successResponse(path, data: {})); final res = await messageApi.deleteReaction(messageId, reactionType); @@ -351,18 +390,25 @@ void main() { ), ); - when(() => client.get( - path, - queryParameters: { - ...const PaginationParams().toJson(), - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.get( + path, + queryParameters: { + ...const PaginationParams().toJson(), + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'reactions': [ ...reactions.map( (it) => {...it.toJson(), 'message_id': messageId}, ), - ] - })); + ], + }, + ), + ); final res = await messageApi.getReactions(messageId, pagination: options); @@ -391,17 +437,24 @@ void main() { }, ); - when(() => client.post( - path, - data: {'language': language}, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: {'language': language}, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'message': { ...translatedMessage.toJson(), 'i18n': { language: translatedMessageText, }, }, - })); + }, + ), + ); final res = await messageApi.translateMessage(messageId, language); @@ -427,14 +480,21 @@ void main() { ), ); - when(() => client.get( - path, - queryParameters: { - ...options.toJson(), - }, - )).thenAnswer((_) async => successResponse(path, data: { - 'messages': [...messages.map((it) => it.toJson())] - })); + when( + () => client.get( + path, + queryParameters: { + ...options.toJson(), + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'messages': [...messages.map((it) => it.toJson())], + }, + ), + ); final res = await messageApi.getReplies(parentId, options: options); @@ -464,13 +524,17 @@ void main() { message: draftMessage, ); - when(() => client.post( - path, - data: jsonEncode({'message': draftMessage}), - )).thenAnswer((_) async => successResponse( - path, - data: {'draft': draft.toJson()}, - )); + when( + () => client.post( + path, + data: jsonEncode({'message': draftMessage}), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: {'draft': draft.toJson()}, + ), + ); final res = await messageApi.createDraft( channelId, @@ -493,11 +557,12 @@ void main() { const path = '/channels/$channelType/$channelId/draft'; - when(() => client.delete(path, queryParameters: {})) - .thenAnswer((_) async => successResponse( - path, - data: {}, - )); + when(() => client.delete(path, queryParameters: {})).thenAnswer( + (_) async => successResponse( + path, + data: {}, + ), + ); final res = await messageApi.deleteDraft( channelId, @@ -517,11 +582,12 @@ void main() { const path = '/channels/$channelType/$channelId/draft'; - when(() => client.delete(path, queryParameters: {'parent_id': parentId})) - .thenAnswer((_) async => successResponse( - path, - data: {}, - )); + when(() => client.delete(path, queryParameters: {'parent_id': parentId})).thenAnswer( + (_) async => successResponse( + path, + data: {}, + ), + ); final res = await messageApi.deleteDraft( channelId, @@ -551,10 +617,14 @@ void main() { message: draftMessage, ); - when(() => client.get(path, queryParameters: {})) - .thenAnswer((_) async => successResponse(path, data: { - 'draft': draft.toJson(), - })); + when(() => client.get(path, queryParameters: {})).thenAnswer( + (_) async => successResponse( + path, + data: { + 'draft': draft.toJson(), + }, + ), + ); final res = await messageApi.getDraft( channelId, @@ -589,12 +659,19 @@ void main() { parentId: parentId, ); - when(() => client.get( - path, - queryParameters: {'parent_id': parentId}, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.get( + path, + queryParameters: {'parent_id': parentId}, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'draft': draft.toJson(), - })); + }, + ), + ); final res = await messageApi.getDraft( channelId, @@ -624,22 +701,31 @@ void main() { ), ); - when(() => client.post( - path, - data: any(named: 'data'), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: any(named: 'data'), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'drafts': [...draftMessages.map((it) => it.toJson())], - })); + }, + ), + ); final res = await messageApi.queryDrafts(); expect(res, isNotNull); expect(res.drafts.length, draftMessages.length); - verify(() => client.post( - path, - data: any(named: 'data'), - )).called(1); + verify( + () => client.post( + path, + data: any(named: 'data'), + ), + ).called(1); verifyNoMoreInteractions(client); }); diff --git a/packages/stream_chat/test/src/core/api/moderation_api_test.dart b/packages/stream_chat/test/src/core/api/moderation_api_test.dart index 69d85fd91d..c5c9cfccfc 100644 --- a/packages/stream_chat/test/src/core/api/moderation_api_test.dart +++ b/packages/stream_chat/test/src/core/api/moderation_api_test.dart @@ -7,10 +7,10 @@ import '../../mocks.dart'; void main() { Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); late final client = MockHttpClient(); late ModerationApi moderationApi; @@ -65,15 +65,15 @@ void main() { const path = '/moderation/mute/channel'; - when(() => client.post( - path, - data: { - 'channel_cid': channelCid, - 'expiration': expiration.inMilliseconds, - }, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'channel_cid': channelCid, + 'expiration': expiration.inMilliseconds, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await moderationApi.muteChannel( channelCid, @@ -91,12 +91,12 @@ void main() { const path = '/moderation/unmute/channel'; - when(() => client.post( - path, - data: {'channel_cid': channelCid}, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: {'channel_cid': channelCid}, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await moderationApi.unmuteChannel(channelCid); @@ -111,14 +111,14 @@ void main() { const path = '/moderation/flag'; - when(() => client.post( - path, - data: { - 'target_message_id': messageId, - }, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'target_message_id': messageId, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await moderationApi.flagMessage(messageId); @@ -133,14 +133,14 @@ void main() { const path = '/moderation/unflag'; - when(() => client.post( - path, - data: { - 'target_message_id': messageId, - }, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'target_message_id': messageId, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await moderationApi.unflagMessage(messageId); @@ -155,11 +155,14 @@ void main() { const path = '/moderation/flag'; - when(() => client.post(path, data: { - 'target_user_id': userId, - })) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'target_user_id': userId, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await moderationApi.flagUser(userId); @@ -174,14 +177,14 @@ void main() { const path = '/moderation/unflag'; - when(() => client.post( - path, - data: { - 'target_user_id': userId, - }, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'target_user_id': userId, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await moderationApi.unflagUser(userId); @@ -197,12 +200,15 @@ void main() { const path = '/moderation/ban'; - when(() => client.post(path, data: { - 'target_user_id': targetUserId, - ...options, - })) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'target_user_id': targetUserId, + ...options, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await moderationApi.banUser(targetUserId, options: options); diff --git a/packages/stream_chat/test/src/core/api/polls_api_test.dart b/packages/stream_chat/test/src/core/api/polls_api_test.dart index 20a3de8d85..2c303218f2 100644 --- a/packages/stream_chat/test/src/core/api/polls_api_test.dart +++ b/packages/stream_chat/test/src/core/api/polls_api_test.dart @@ -15,10 +15,10 @@ import '../../mocks.dart'; void main() { Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); late final client = MockHttpClient(); late PollsApi pollsApi; @@ -41,12 +41,19 @@ void main() { const path = '/polls'; - when(() => client.post( - path, - data: jsonEncode(poll), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: jsonEncode(poll), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'poll': poll.toJson(), - })); + }, + ), + ); final res = await pollsApi.createPoll(poll); @@ -73,10 +80,14 @@ void main() { ], ); - when(() => client.get(path)) - .thenAnswer((_) async => successResponse(path, data: { - 'poll': poll.toJson(), - })); + when(() => client.get(path)).thenAnswer( + (_) async => successResponse( + path, + data: { + 'poll': poll.toJson(), + }, + ), + ); final res = await pollsApi.getPoll(pollId); @@ -101,12 +112,19 @@ void main() { const path = '/polls'; - when(() => client.put( - path, - data: jsonEncode(poll), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.put( + path, + data: jsonEncode(poll), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'poll': poll.toJson(), - })); + }, + ), + ); final res = await pollsApi.updatePoll(poll); @@ -134,12 +152,19 @@ void main() { ], ); - when(() => client.patch( - path, - data: jsonEncode({'set': set, 'unset': unset}), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.patch( + path, + data: jsonEncode({'set': set, 'unset': unset}), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'poll': poll.toJson(), - })); + }, + ), + ); final res = await pollsApi.partialUpdatePoll( pollId, @@ -151,11 +176,15 @@ void main() { expect(res.poll.id, pollId); expect(res.poll.name, set['name']); - verify(() => client.patch(path, + verify( + () => client.patch( + path, data: jsonEncode({ 'set': set, 'unset': unset, - }))).called(1); + }), + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -164,8 +193,7 @@ void main() { const path = '/polls/$pollId'; - when(() => client.delete(path)).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.delete(path)).thenAnswer((_) async => successResponse(path, data: {})); final res = await pollsApi.deletePoll(pollId); @@ -184,15 +212,22 @@ void main() { const path = '/polls/$pollId/options'; - when(() => client.post( - path, - data: jsonEncode(option), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: jsonEncode(option), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'poll_option': option.toJson() ..addAll({ 'id': option.id, }), - })); + }, + ), + ); final res = await pollsApi.createPollOption(pollId, option); @@ -214,13 +249,17 @@ void main() { text: 'test-option-value', ); - when(() => client.get(path)) - .thenAnswer((_) async => successResponse(path, data: { - 'poll_option': option.toJson() - ..addAll({ - 'id': option.id, - }), - })); + when(() => client.get(path)).thenAnswer( + (_) async => successResponse( + path, + data: { + 'poll_option': option.toJson() + ..addAll({ + 'id': option.id, + }), + }, + ), + ); final res = await pollsApi.getPollOption(pollId, optionId); @@ -240,15 +279,22 @@ void main() { const path = '/polls/$pollId/options'; - when(() => client.put( - path, - data: jsonEncode(option), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.put( + path, + data: jsonEncode(option), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'poll_option': option.toJson() ..addAll({ 'id': option.id, }), - })); + }, + ), + ); final res = await pollsApi.updatePollOption(pollId, option); @@ -265,8 +311,7 @@ void main() { const path = '/polls/$pollId/options/$optionId'; - when(() => client.delete(path)).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.delete(path)).thenAnswer((_) async => successResponse(path, data: {})); final res = await pollsApi.deletePollOption(pollId, optionId); @@ -286,14 +331,21 @@ void main() { const path = '/messages/$messageId/polls/$pollId/vote'; - when(() => client.post( - path, - data: jsonEncode({ - 'vote': vote, - }), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: jsonEncode({ + 'vote': vote, + }), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'vote': vote.toJson(), - })); + }, + ), + ); final res = await pollsApi.castPollVote(messageId, pollId, vote); @@ -315,10 +367,14 @@ void main() { optionId: 'test-option-id', ); - when(() => client.delete(path)) - .thenAnswer((_) async => successResponse(path, data: { - 'vote': vote.toJson(), - })); + when(() => client.delete(path)).thenAnswer( + (_) async => successResponse( + path, + data: { + 'vote': vote.toJson(), + }, + ), + ); final res = await pollsApi.removePollVote(messageId, pollId, voteId); @@ -359,9 +415,14 @@ void main() { path, data: payload, ), - ).thenAnswer((_) async => successResponse(path, data: { + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'polls': [...polls.map((it) => it.toJson())], - })); + }, + ), + ); final res = await pollsApi.queryPolls( filter: filter, @@ -405,9 +466,14 @@ void main() { path, data: payload, ), - ).thenAnswer((_) async => successResponse(path, data: { + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'votes': [...votes.map((it) => it.toJson())], - })); + }, + ), + ); final res = await pollsApi.queryPollVotes( pollId, diff --git a/packages/stream_chat/test/src/core/api/reminders_api_test.dart b/packages/stream_chat/test/src/core/api/reminders_api_test.dart index e0c3be7061..919bb47661 100644 --- a/packages/stream_chat/test/src/core/api/reminders_api_test.dart +++ b/packages/stream_chat/test/src/core/api/reminders_api_test.dart @@ -15,10 +15,10 @@ import '../../mocks.dart'; void main() { Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); late final client = MockHttpClient(); late RemindersApi remindersApi; @@ -50,10 +50,14 @@ void main() { ), ]; - when(() => client.post(path, data: jsonEncode({}))) - .thenAnswer((_) async => successResponse(path, data: { - 'reminders': reminders.map((r) => r.toJson()).toList(), - })); + when(() => client.post(path, data: jsonEncode({}))).thenAnswer( + (_) async => successResponse( + path, + data: { + 'reminders': reminders.map((r) => r.toJson()).toList(), + }, + ), + ); final res = await remindersApi.queryReminders(); @@ -89,10 +93,14 @@ void main() { ), ); - when(() => client.post(path, data: expectedPayload)) - .thenAnswer((_) async => successResponse(path, data: { - 'reminders': reminders.map((r) => r.toJson()).toList(), - })); + when(() => client.post(path, data: expectedPayload)).thenAnswer( + (_) async => successResponse( + path, + data: { + 'reminders': reminders.map((r) => r.toJson()).toList(), + }, + ), + ); final res = await remindersApi.queryReminders( filter: filter, @@ -122,10 +130,14 @@ void main() { updatedAt: DateTime(2024, 1, 1), ); - when(() => client.post(path, data: jsonEncode({}))) - .thenAnswer((_) async => successResponse(path, data: { - 'reminder': reminder.toJson(), - })); + when(() => client.post(path, data: jsonEncode({}))).thenAnswer( + (_) async => successResponse( + path, + data: { + 'reminder': reminder.toJson(), + }, + ), + ); final res = await remindersApi.createReminder(messageId); @@ -154,13 +166,16 @@ void main() { 'remind_at': remindAt.toUtc().toIso8601String(), }); - when(() => client.post(path, data: expectedPayload)) - .thenAnswer((_) async => successResponse(path, data: { - 'reminder': reminder.toJson(), - })); + when(() => client.post(path, data: expectedPayload)).thenAnswer( + (_) async => successResponse( + path, + data: { + 'reminder': reminder.toJson(), + }, + ), + ); - final res = - await remindersApi.createReminder(messageId, remindAt: remindAt); + final res = await remindersApi.createReminder(messageId, remindAt: remindAt); expect(res, isNotNull); expect(res.reminder.messageId, messageId); @@ -185,10 +200,14 @@ void main() { updatedAt: DateTime(2024, 1, 2), ); - when(() => client.patch(path, data: jsonEncode({}))) - .thenAnswer((_) async => successResponse(path, data: { - 'reminder': reminder.toJson(), - })); + when(() => client.patch(path, data: jsonEncode({}))).thenAnswer( + (_) async => successResponse( + path, + data: { + 'reminder': reminder.toJson(), + }, + ), + ); final res = await remindersApi.updateReminder(messageId); @@ -217,13 +236,16 @@ void main() { 'remind_at': remindAt.toUtc().toIso8601String(), }); - when(() => client.patch(path, data: expectedPayload)) - .thenAnswer((_) async => successResponse(path, data: { - 'reminder': reminder.toJson(), - })); + when(() => client.patch(path, data: expectedPayload)).thenAnswer( + (_) async => successResponse( + path, + data: { + 'reminder': reminder.toJson(), + }, + ), + ); - final res = - await remindersApi.updateReminder(messageId, remindAt: remindAt); + final res = await remindersApi.updateReminder(messageId, remindAt: remindAt); expect(res, isNotNull); expect(res.reminder.messageId, messageId); @@ -239,8 +261,7 @@ void main() { const messageId = 'test-message-id'; const path = '/messages/$messageId/reminders'; - when(() => client.delete(path)).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.delete(path)).thenAnswer((_) async => successResponse(path, data: {})); final res = await remindersApi.deleteReminder(messageId); diff --git a/packages/stream_chat/test/src/core/api/responses_test.dart b/packages/stream_chat/test/src/core/api/responses_test.dart index 5e3430f38c..644e75b847 100644 --- a/packages/stream_chat/test/src/core/api/responses_test.dart +++ b/packages/stream_chat/test/src/core/api/responses_test.dart @@ -3304,8 +3304,7 @@ void main() { const jsonExample = ''' {"reactions": [{"message_id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f","user_id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680","user": {"id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680","role": "user","created_at": "2020-01-28T22:17:30.83015Z","updated_at": "2020-01-28T22:17:31.19435Z","banned": false,"online": false,"image": "https://randomuser.me/api/portraits/women/2.jpg","name": "Mia Denys"},"type": "love","score": 1,"created_at": "2020-01-28T22:17:31.128376Z","updated_at": "2020-01-28T22:17:31.128376Z"}]} '''; - final response = - QueryReactionsResponse.fromJson(json.decode(jsonExample)); + final response = QueryReactionsResponse.fromJson(json.decode(jsonExample)); expect(response.reactions, isA>()); }); @@ -3412,14 +3411,12 @@ void main() { }] } '''; - final response = - SearchMessagesResponse.fromJson(json.decode(jsonExample)); + final response = SearchMessagesResponse.fromJson(json.decode(jsonExample)); expect(response.results, isA>()); }); test('ListDevicesResponse', () { - const jsonExample = - '''{"devices":[{"push_provider":"firebase","id":"test"}],"duration":"0.35ms"}'''; + const jsonExample = '''{"devices":[{"push_provider":"firebase","id":"test"}],"duration":"0.35ms"}'''; final response = ListDevicesResponse.fromJson(json.decode(jsonExample)); expect(response.devices, isA>()); }); @@ -3517,8 +3514,7 @@ void main() { test('ConnectGuestUserResponse', () { const jsonExample = '''{"user":{"id":"guest-ac612aee-25fe-49fb-b1af-969e41f452a0-wild-breeze-7","role":"guest","created_at":"2020-02-03T10:19:01.538434Z","updated_at":"2020-02-03T10:19:01.539543Z","banned":false,"online":false},"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiZ3Vlc3QtYWM2MTJhZWUtMjVmZS00OWZiLWIxYWYtOTY5ZTQxZjQ1MmEwLXdpbGQtYnJlZXplLTcifQ.mmoFGu7oJjpFsp7nFN78UbIpO7gowbuIbyoppsuvbXA","duration":"4.66ms"}'''; - final response = - ConnectGuestUserResponse.fromJson(json.decode(jsonExample)); + final response = ConnectGuestUserResponse.fromJson(json.decode(jsonExample)); expect(response.user, isA()); expect(response.accessToken, isA()); }); @@ -3550,8 +3546,7 @@ void main() { "updated_at": "2020-01-28T22:17:31.092262Z", "mentioned_users": [] }],"duration":"4.66ms"}'''; - final response = - GetMessagesByIdResponse.fromJson(json.decode(jsonExample)); + final response = GetMessagesByIdResponse.fromJson(json.decode(jsonExample)); expect(response.messages, isA>()); }); @@ -4474,8 +4469,7 @@ void main() { final channel1Prefs = user1ChannelPrefs['channel1']!; expect(channel1Prefs.chatLevel, ChatLevel.all); - expect( - channel1Prefs.disabledUntil, DateTime.parse('2024-12-31T23:59:59Z')); + expect(channel1Prefs.disabledUntil, DateTime.parse('2024-12-31T23:59:59Z')); final channel2Prefs = user1ChannelPrefs['channel2']!; expect(channel2Prefs.chatLevel, ChatLevel.none); diff --git a/packages/stream_chat/test/src/core/api/user_api_test.dart b/packages/stream_chat/test/src/core/api/user_api_test.dart index 9a77907e64..ad61fbc94d 100644 --- a/packages/stream_chat/test/src/core/api/user_api_test.dart +++ b/packages/stream_chat/test/src/core/api/user_api_test.dart @@ -10,10 +10,10 @@ import '../../mocks.dart'; void main() { Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); late final client = MockHttpClient(); late UserApi userApi; @@ -32,16 +32,26 @@ void main() { final users = List.generate(3, (index) => User(id: 'test-user-id-$index')); - when(() => client.get(path, queryParameters: { + when( + () => client.get( + path, + queryParameters: { 'payload': jsonEncode({ 'presence': presence, 'sort': sort, 'filter_conditions': filter, ...pagination.toJson(), }), - })).thenAnswer((_) async => successResponse(path, data: { - 'users': [...users.map((it) => it.toJson())] - })); + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'users': [...users.map((it) => it.toJson())], + }, + ), + ); final res = await userApi.queryUsers( presence: presence, @@ -66,13 +76,17 @@ void main() { final updatedUsers = {for (final user in users) user.id: user}; - when(() => client.post(path, data: { + when( + () => client.post( + path, + data: { 'users': updatedUsers, - })).thenAnswer((_) async => successResponse(path, - data: { - 'users': updatedUsers - .map((key, value) => MapEntry(key, value.toJson())) - })); + }, + ), + ).thenAnswer( + (_) async => + successResponse(path, data: {'users': updatedUsers.map((key, value) => MapEntry(key, value.toJson()))}), + ); final res = await userApi.updateUsers(users); @@ -93,16 +107,18 @@ void main() { final updatedUser = {user.id: User(id: user.id, extraData: user.set!)}; - when(() => client.patch(path, data: { - 'users': [user], - })).thenAnswer( - (_) async => successResponse( + when( + () => client.patch( path, data: { - 'users': - updatedUser.map((key, value) => MapEntry(key, value.toJson())) + 'users': [user], }, ), + ).thenAnswer( + (_) async => successResponse( + path, + data: {'users': updatedUser.map((key, value) => MapEntry(key, value.toJson()))}, + ), ); final res = await userApi.partialUpdateUsers([user]); @@ -110,9 +126,14 @@ void main() { expect(res, isNotNull); expect(res.users.length, updatedUser.length); - verify(() => client.patch(path, data: { - 'users': [user] - })).called(1); + verify( + () => client.patch( + path, + data: { + 'users': [user], + }, + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -121,9 +142,14 @@ void main() { const path = '/users/block'; - when(() => client.post(path, data: { + when( + () => client.post( + path, + data: { 'blocked_user_id': targetUserId, - })).thenAnswer( + }, + ), + ).thenAnswer( (_) async => successResponse( path, data: { @@ -138,9 +164,14 @@ void main() { expect(res, isNotNull); - verify(() => client.post(path, data: { + verify( + () => client.post( + path, + data: { 'blocked_user_id': targetUserId, - })).called(1); + }, + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -149,9 +180,14 @@ void main() { const path = '/users/unblock'; - when(() => client.post(path, data: { + when( + () => client.post( + path, + data: { 'blocked_user_id': targetUserId, - })).thenAnswer( + }, + ), + ).thenAnswer( (_) async => successResponse( path, data: {}, @@ -162,9 +198,14 @@ void main() { expect(res, isNotNull); - verify(() => client.post(path, data: { + verify( + () => client.post( + path, + data: { 'blocked_user_id': targetUserId, - })).called(1); + }, + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -187,50 +228,53 @@ void main() { const path = '/unread'; when(() => client.get(path)).thenAnswer( - (_) async => successResponse(path, data: { - 'duration': '5.23ms', - 'total_unread_count': 42, - 'total_unread_threads_count': 8, - 'total_unread_count_by_team': {'team-1': 15, 'team-2': 27}, - 'channels': [ - { - 'channel_id': 'messaging:test-channel-1', - 'unread_count': 5, - 'last_read': '2024-01-15T10:30:00.000Z', - }, - { - 'channel_id': 'messaging:test-channel-2', - 'unread_count': 10, - 'last_read': '2024-01-15T09:15:00.000Z', - }, - ], - 'channel_type': [ - { - 'channel_type': 'messaging', - 'channel_count': 3, - 'unread_count': 25, - }, - { - 'channel_type': 'livestream', - 'channel_count': 1, - 'unread_count': 17, - }, - ], - 'threads': [ - { - 'unread_count': 3, - 'last_read': '2024-01-15T10:30:00.000Z', - 'last_read_message_id': 'message-1', - 'parent_message_id': 'parent-message-1', - }, - { - 'unread_count': 5, - 'last_read': '2024-01-15T09:45:00.000Z', - 'last_read_message_id': 'message-2', - 'parent_message_id': 'parent-message-2', - }, - ], - }), + (_) async => successResponse( + path, + data: { + 'duration': '5.23ms', + 'total_unread_count': 42, + 'total_unread_threads_count': 8, + 'total_unread_count_by_team': {'team-1': 15, 'team-2': 27}, + 'channels': [ + { + 'channel_id': 'messaging:test-channel-1', + 'unread_count': 5, + 'last_read': '2024-01-15T10:30:00.000Z', + }, + { + 'channel_id': 'messaging:test-channel-2', + 'unread_count': 10, + 'last_read': '2024-01-15T09:15:00.000Z', + }, + ], + 'channel_type': [ + { + 'channel_type': 'messaging', + 'channel_count': 3, + 'unread_count': 25, + }, + { + 'channel_type': 'livestream', + 'channel_count': 1, + 'unread_count': 17, + }, + ], + 'threads': [ + { + 'unread_count': 3, + 'last_read': '2024-01-15T10:30:00.000Z', + 'last_read_message_id': 'message-1', + 'parent_message_id': 'parent-message-1', + }, + { + 'unread_count': 5, + 'last_read': '2024-01-15T09:45:00.000Z', + 'last_read_message_id': 'message-2', + 'parent_message_id': 'parent-message-2', + }, + ], + }, + ), ); final res = await userApi.getUnreadCount(); diff --git a/packages/stream_chat/test/src/core/error/stream_chat_error_test.dart b/packages/stream_chat/test/src/core/error/stream_chat_error_test.dart index c95afd3cb4..4bba3b898d 100644 --- a/packages/stream_chat/test/src/core/error/stream_chat_error_test.dart +++ b/packages/stream_chat/test/src/core/error/stream_chat_error_test.dart @@ -92,7 +92,8 @@ void main() { const statusCode = 666; const message = 'test-error-message'; final options = RequestOptions(path: 'test-path'); - const data = ''' + const data = + ''' { "code": $code, "StatusCode": $statusCode, diff --git a/packages/stream_chat/test/src/core/http/adapter/custom_adapter_test.dart b/packages/stream_chat/test/src/core/http/adapter/custom_adapter_test.dart index 75c6bb76e5..ce334d3e92 100644 --- a/packages/stream_chat/test/src/core/http/adapter/custom_adapter_test.dart +++ b/packages/stream_chat/test/src/core/http/adapter/custom_adapter_test.dart @@ -15,11 +15,12 @@ void main() { final mockAdapter = MockHttpClientAdapter(); final httpClient = StreamHttpClient(apiKey, httpClientAdapter: mockAdapter); - when(() => mockAdapter.fetch(any(), any(), any())) - .thenAnswer((_) async => ResponseBody( - Stream.value(Uint8List(0)), - 200, - )); + when(() => mockAdapter.fetch(any(), any(), any())).thenAnswer( + (_) async => ResponseBody( + Stream.value(Uint8List(0)), + 200, + ), + ); await httpClient.get('/'); diff --git a/packages/stream_chat/test/src/core/http/interceptor/auth_interceptor_test.dart b/packages/stream_chat/test/src/core/http/interceptor/auth_interceptor_test.dart index 269e7c884d..cc92b01a8f 100644 --- a/packages/stream_chat/test/src/core/http/interceptor/auth_interceptor_test.dart +++ b/packages/stream_chat/test/src/core/http/interceptor/auth_interceptor_test.dart @@ -35,8 +35,7 @@ void main() { expect(queryParams.containsKey('user_id'), isFalse); final token = Token.development('test-user-id'); - when(() => tokenManager.loadToken(refresh: any(named: 'refresh'))) - .thenAnswer((_) async => token); + when(() => tokenManager.loadToken(refresh: any(named: 'refresh'))).thenAnswer((_) async => token); authInterceptor.onRequest(options, handler); @@ -51,8 +50,7 @@ void main() { expect(updatedQueryParams.containsKey('user_id'), isTrue); expect(updatedQueryParams['user_id'], token.userId); - verify(() => tokenManager.loadToken(refresh: any(named: 'refresh'))) - .called(1); + verify(() => tokenManager.loadToken(refresh: any(named: 'refresh'))).called(1); verifyNoMoreInteractions(tokenManager); }, ); @@ -95,13 +93,14 @@ void main() { when(() => tokenManager.isStatic).thenReturn(false); final token = Token.development('test-user-id'); - when(() => tokenManager.loadToken(refresh: true)) - .thenAnswer((_) async => token); + when(() => tokenManager.loadToken(refresh: true)).thenAnswer((_) async => token); - when(() => client.fetch(options)).thenAnswer((_) async => Response( - requestOptions: options, - statusCode: 200, - )); + when(() => client.fetch(options)).thenAnswer( + (_) async => Response( + requestOptions: options, + statusCode: 200, + ), + ); authInterceptor.onError(err, handler); @@ -142,8 +141,7 @@ void main() { when(() => tokenManager.isStatic).thenReturn(false); final token = Token.development('test-user-id'); - when(() => tokenManager.loadToken(refresh: true)) - .thenAnswer((_) async => token); + when(() => tokenManager.loadToken(refresh: true)).thenAnswer((_) async => token); when(() => client.fetch(options)).thenThrow(err); diff --git a/packages/stream_chat/test/src/core/http/stream_http_client_test.dart b/packages/stream_chat/test/src/core/http/stream_http_client_test.dart index 2ddc84e3b4..936af1670b 100644 --- a/packages/stream_chat/test/src/core/http/stream_http_client_test.dart +++ b/packages/stream_chat/test/src/core/http/stream_http_client_test.dart @@ -17,9 +17,9 @@ import '../../mocks.dart'; void main() { Response successResponse(String path) => Response( - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); DioException throwableError( String path, { @@ -53,19 +53,14 @@ void main() { const apiKey = 'api-key'; final client = StreamHttpClient(apiKey); - expect( - client.httpClient.interceptors - .whereType() - .length, - 1); + expect(client.httpClient.interceptors.whereType().length, 1); }); test('AuthInterceptor should be added if tokenManager is provided', () { const apiKey = 'api-key'; final client = StreamHttpClient(apiKey, tokenManager: TokenManager()); - expect( - client.httpClient.interceptors.whereType().length, 1); + expect(client.httpClient.interceptors.whereType().length, 1); }); test( @@ -78,9 +73,7 @@ void main() { ); expect( - client.httpClient.interceptors - .whereType() - .length, + client.httpClient.interceptors.whereType().length, 1, ); }, @@ -175,10 +168,12 @@ void main() { final client = StreamHttpClient('api-key', dio: dio); const path = 'test-get-api-path'; - when(() => dio.get( - path, - options: any(named: 'options'), - )).thenAnswer((_) async => successResponse(path)); + when( + () => dio.get( + path, + options: any(named: 'options'), + ), + ).thenAnswer((_) async => successResponse(path)); final res = await client.get(path); @@ -186,10 +181,12 @@ void main() { expect(res.statusCode, 200); expect(res.requestOptions.path, path); - verify(() => dio.get( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.get( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }); @@ -202,10 +199,12 @@ void main() { path, error: StreamChatNetworkError(ChatErrorCode.internalSystemError), ); - when(() => dio.get( - path, - options: any(named: 'options'), - )).thenThrow(error); + when( + () => dio.get( + path, + options: any(named: 'options'), + ), + ).thenThrow(error); try { await client.get(path); @@ -214,10 +213,12 @@ void main() { expect(e, StreamChatNetworkError.fromDioException(error)); } - verify(() => dio.get( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.get( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }); @@ -226,10 +227,12 @@ void main() { final client = StreamHttpClient('api-key', dio: dio); const path = 'test-post-api-path'; - when(() => dio.post( - path, - options: any(named: 'options'), - )).thenAnswer((_) async => successResponse(path)); + when( + () => dio.post( + path, + options: any(named: 'options'), + ), + ).thenAnswer((_) async => successResponse(path)); final res = await client.post(path); @@ -237,10 +240,12 @@ void main() { expect(res.statusCode, 200); expect(res.requestOptions.path, path); - verify(() => dio.post( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.post( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }); @@ -255,10 +260,12 @@ void main() { path, error: StreamChatNetworkError(ChatErrorCode.internalSystemError), ); - when(() => dio.post( - path, - options: any(named: 'options'), - )).thenThrow(error); + when( + () => dio.post( + path, + options: any(named: 'options'), + ), + ).thenThrow(error); try { await client.post(path); @@ -267,10 +274,12 @@ void main() { expect(e, StreamChatNetworkError.fromDioException(error)); } - verify(() => dio.post( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.post( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }, ); @@ -280,10 +289,12 @@ void main() { final client = StreamHttpClient('api-key', dio: dio); const path = 'test-delete-api-path'; - when(() => dio.delete( - path, - options: any(named: 'options'), - )).thenAnswer((_) async => successResponse(path)); + when( + () => dio.delete( + path, + options: any(named: 'options'), + ), + ).thenAnswer((_) async => successResponse(path)); final res = await client.delete(path); @@ -291,10 +302,12 @@ void main() { expect(res.statusCode, 200); expect(res.requestOptions.path, path); - verify(() => dio.delete( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.delete( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }); @@ -309,10 +322,12 @@ void main() { path, error: StreamChatNetworkError(ChatErrorCode.internalSystemError), ); - when(() => dio.delete( - path, - options: any(named: 'options'), - )).thenThrow(error); + when( + () => dio.delete( + path, + options: any(named: 'options'), + ), + ).thenThrow(error); try { await client.delete(path); @@ -321,10 +336,12 @@ void main() { expect(e, StreamChatNetworkError.fromDioException(error)); } - verify(() => dio.delete( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.delete( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }, ); @@ -334,10 +351,12 @@ void main() { final client = StreamHttpClient('api-key', dio: dio); const path = 'test-patch-api-path'; - when(() => dio.patch( - path, - options: any(named: 'options'), - )).thenAnswer((_) async => successResponse(path)); + when( + () => dio.patch( + path, + options: any(named: 'options'), + ), + ).thenAnswer((_) async => successResponse(path)); final res = await client.patch(path); @@ -345,10 +364,12 @@ void main() { expect(res.statusCode, 200); expect(res.requestOptions.path, path); - verify(() => dio.patch( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.patch( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }); @@ -363,10 +384,12 @@ void main() { path, error: StreamChatNetworkError(ChatErrorCode.internalSystemError), ); - when(() => dio.patch( - path, - options: any(named: 'options'), - )).thenThrow(error); + when( + () => dio.patch( + path, + options: any(named: 'options'), + ), + ).thenThrow(error); try { await client.patch(path); @@ -375,10 +398,12 @@ void main() { expect(e, StreamChatNetworkError.fromDioException(error)); } - verify(() => dio.patch( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.patch( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }, ); @@ -388,10 +413,12 @@ void main() { final client = StreamHttpClient('api-key', dio: dio); const path = 'test-put-api-path'; - when(() => dio.put( - path, - options: any(named: 'options'), - )).thenAnswer((_) async => successResponse(path)); + when( + () => dio.put( + path, + options: any(named: 'options'), + ), + ).thenAnswer((_) async => successResponse(path)); final res = await client.put(path); @@ -399,10 +426,12 @@ void main() { expect(res.statusCode, 200); expect(res.requestOptions.path, path); - verify(() => dio.put( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.put( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }); @@ -417,10 +446,12 @@ void main() { path, error: StreamChatNetworkError(ChatErrorCode.internalSystemError), ); - when(() => dio.put( - path, - options: any(named: 'options'), - )).thenThrow(error); + when( + () => dio.put( + path, + options: any(named: 'options'), + ), + ).thenThrow(error); try { await client.put(path); @@ -429,10 +460,12 @@ void main() { expect(e, StreamChatNetworkError.fromDioException(error)); } - verify(() => dio.put( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.put( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }, ); @@ -444,11 +477,13 @@ void main() { const path = 'test-delete-api-path'; final file = MultipartFile.fromBytes([]); - when(() => dio.post( - path, - data: any(named: 'data'), - options: any(named: 'options'), - )).thenAnswer((_) async => successResponse(path)); + when( + () => dio.post( + path, + data: any(named: 'data'), + options: any(named: 'options'), + ), + ).thenAnswer((_) async => successResponse(path)); final res = await client.postFile(path, file); @@ -456,11 +491,13 @@ void main() { expect(res.statusCode, 200); expect(res.requestOptions.path, path); - verify(() => dio.post( - path, - data: any(named: 'data'), - options: any(named: 'options'), - )).called(1); + verify( + () => dio.post( + path, + data: any(named: 'data'), + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }); @@ -477,11 +514,13 @@ void main() { path, error: StreamChatNetworkError(ChatErrorCode.internalSystemError), ); - when(() => dio.post( - path, - data: any(named: 'data'), - options: any(named: 'options'), - )).thenThrow(error); + when( + () => dio.post( + path, + data: any(named: 'data'), + options: any(named: 'options'), + ), + ).thenThrow(error); try { await client.postFile(path, file); @@ -490,11 +529,13 @@ void main() { expect(e, StreamChatNetworkError.fromDioException(error)); } - verify(() => dio.post( - path, - data: any(named: 'data'), - options: any(named: 'options'), - )).called(1); + verify( + () => dio.post( + path, + data: any(named: 'data'), + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }, ); @@ -504,10 +545,12 @@ void main() { final client = StreamHttpClient('api-key', dio: dio); const path = 'test-request-api-path'; - when(() => dio.request( - path, - options: any(named: 'options'), - )).thenAnswer((_) async => successResponse(path)); + when( + () => dio.request( + path, + options: any(named: 'options'), + ), + ).thenAnswer((_) async => successResponse(path)); final res = await client.request(path); @@ -515,10 +558,12 @@ void main() { expect(res.statusCode, 200); expect(res.requestOptions.path, path); - verify(() => dio.request( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.request( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }); @@ -534,10 +579,12 @@ void main() { streamChatDioError: true, error: StreamChatNetworkError(ChatErrorCode.internalSystemError), ); - when(() => dio.request( - path, - options: any(named: 'options'), - )).thenThrow(error); + when( + () => dio.request( + path, + options: any(named: 'options'), + ), + ).thenThrow(error); try { await client.request(path); @@ -546,10 +593,12 @@ void main() { expect(e, error.error); } - verify(() => dio.request( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.request( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }, ); diff --git a/packages/stream_chat/test/src/core/http/token_manager_test.dart b/packages/stream_chat/test/src/core/http/token_manager_test.dart index 163bafffe2..626fd676b4 100644 --- a/packages/stream_chat/test/src/core/http/token_manager_test.dart +++ b/packages/stream_chat/test/src/core/http/token_manager_test.dart @@ -32,8 +32,7 @@ void main() { expect(tokenManager.userId, isNull); const userId = 'test-user-id'; - Future tokenProvider(String userId) async => - Token.development(userId).rawValue; + Future tokenProvider(String userId) async => Token.development(userId).rawValue; final returnedToken = await tokenManager.setTokenOrProvider( userId, provider: tokenProvider, @@ -65,8 +64,7 @@ void main() { const userId = 'test-user-id'; final token = Token.development(userId); - Future tokenProvider(String userId) async => - Token.development(userId).rawValue; + Future tokenProvider(String userId) async => Token.development(userId).rawValue; try { await tokenManager.setTokenOrProvider( userId, diff --git a/packages/stream_chat/test/src/core/http/token_test.dart b/packages/stream_chat/test/src/core/http/token_test.dart index 0f0e04253e..543ffda6ba 100644 --- a/packages/stream_chat/test/src/core/http/token_test.dart +++ b/packages/stream_chat/test/src/core/http/token_test.dart @@ -43,8 +43,7 @@ void main() { '`.guest` should create a guest-token with provided user and provider', () async { final user = User(id: 'test-user-id'); - Future provider(User user) async => - Token.development(user.id).rawValue; + Future provider(User user) async => Token.development(user.id).rawValue; final token = await Token.guest(user, provider); expect(token, isNotNull); diff --git a/packages/stream_chat/test/src/core/models/attachment_file_test.dart b/packages/stream_chat/test/src/core/models/attachment_file_test.dart index 6dce83f90e..440f1588ab 100644 --- a/packages/stream_chat/test/src/core/models/attachment_file_test.dart +++ b/packages/stream_chat/test/src/core/models/attachment_file_test.dart @@ -8,8 +8,7 @@ import '../../utils.dart'; void main() { group('src/models/attachment_file', () { test('should parse json correctly', () { - final attachment = - AttachmentFile.fromJson(jsonFixture('attachment_file.json')); + final attachment = AttachmentFile.fromJson(jsonFixture('attachment_file.json')); expect(attachment.name, 'test.jpg'); expect(attachment.size, 12); expect( diff --git a/packages/stream_chat/test/src/core/models/attachment_giphy_info_test.dart b/packages/stream_chat/test/src/core/models/attachment_giphy_info_test.dart index d135c217d7..7d044ea81e 100644 --- a/packages/stream_chat/test/src/core/models/attachment_giphy_info_test.dart +++ b/packages/stream_chat/test/src/core/models/attachment_giphy_info_test.dart @@ -17,15 +17,17 @@ void main() { group('GiphyInfoX', () { test('giphyInfo returns valid GiphyInfo object when data is valid', () { - final attachment = Attachment(extraData: const { - 'giphy': { - 'original': { - 'url': 'https://example.com/original.gif', - 'width': '200', - 'height': '150', - } - } - }); + final attachment = Attachment( + extraData: const { + 'giphy': { + 'original': { + 'url': 'https://example.com/original.gif', + 'width': '200', + 'height': '150', + }, + }, + }, + ); final giphyInfo = attachment.giphyInfo(GiphyInfoType.original); @@ -43,17 +45,18 @@ void main() { expect(giphyInfo, isNull); }); - test('giphyInfo returns null when the specific GiphyInfoType is missing', - () { - final attachment = Attachment(extraData: const { - 'giphy': { - 'fixed_height': { - 'url': 'https://example.com/fixed_height.gif', - 'width': '100', - 'height': '100', - } - } - }); + test('giphyInfo returns null when the specific GiphyInfoType is missing', () { + final attachment = Attachment( + extraData: const { + 'giphy': { + 'fixed_height': { + 'url': 'https://example.com/fixed_height.gif', + 'width': '100', + 'height': '100', + }, + }, + }, + ); final giphyInfo = attachment.giphyInfo(GiphyInfoType.original); diff --git a/packages/stream_chat/test/src/core/models/attachment_test.dart b/packages/stream_chat/test/src/core/models/attachment_test.dart index f702956760..4417ef1173 100644 --- a/packages/stream_chat/test/src/core/models/attachment_test.dart +++ b/packages/stream_chat/test/src/core/models/attachment_test.dart @@ -29,8 +29,7 @@ void main() { final channel = Attachment( type: 'giphy', title: 'soo', - titleLink: - 'https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti', + titleLink: 'https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti', ); expect( @@ -38,8 +37,7 @@ void main() { { 'type': 'giphy', 'title': 'soo', - 'title_link': - 'https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti', + 'title_link': 'https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti', 'actions': [], }, ); @@ -51,17 +49,12 @@ void main() { expect(attachment.fileSize, 3); expect(attachment.mimeType, 'text/plain'); - expect(attachment.toJson(), { - 'title': 'myfile.txt', - 'actions': [], - 'file_size': 3, - 'mime_type': 'text/plain' - }); + expect(attachment.toJson(), {'title': 'myfile.txt', 'actions': [], 'file_size': 3, 'mime_type': 'text/plain'}); expect(Attachment.fromJson(attachment.toJson()).toJson(), { 'title': 'myfile.txt', 'actions': [], 'file_size': 3, - 'mime_type': 'text/plain' + 'mime_type': 'text/plain', }); // Setting the size and mimeType using extraData should work fine @@ -88,10 +81,13 @@ void main() { // if file is available, should override size and mimeType. final fileThree = AttachmentFile(size: 9, path: 'myfolder/fileThree.png'); - newAttachment = attachment.copyWith(file: fileThree, extraData: { - 'file_size': 88, - 'mime_type': 'application/pdf', - }); + newAttachment = attachment.copyWith( + file: fileThree, + extraData: { + 'file_size': 88, + 'mime_type': 'application/pdf', + }, + ); expect(newAttachment.extraData['file_size'], 9); expect(newAttachment.extraData['mime_type'], 'image/png'); diff --git a/packages/stream_chat/test/src/core/models/channel_state_test.dart b/packages/stream_chat/test/src/core/models/channel_state_test.dart index 8d7410fcbd..70870104ed 100644 --- a/packages/stream_chat/test/src/core/models/channel_state_test.dart +++ b/packages/stream_chat/test/src/core/models/channel_state_test.dart @@ -6,8 +6,7 @@ import '../../utils.dart'; void main() { group('src/models/channel_state', () { test('should parse json correctly', () { - final channelState = - ChannelState.fromJson(jsonFixture('channel_state.json')); + final channelState = ChannelState.fromJson(jsonFixture('channel_state.json')); expect(channelState.channel?.cid, 'team:dev'); expect(channelState.channel?.id, 'dev'); expect(channelState.channel?.team, 'test'); @@ -16,12 +15,9 @@ void main() { expect(channelState.channel?.config, isNotNull); expect(channelState.channel?.config.commands, hasLength(1)); expect(channelState.channel?.config.commands[0], isA()); - expect(channelState.channel?.lastMessageAt, - DateTime.parse('2020-01-30T13:43:41.062362Z')); - expect(channelState.channel?.createdAt, - DateTime.parse('2019-04-03T18:43:33.213373Z')); - expect(channelState.channel?.updatedAt, - DateTime.parse('2019-04-03T18:43:33.213374Z')); + expect(channelState.channel?.lastMessageAt, DateTime.parse('2020-01-30T13:43:41.062362Z')); + expect(channelState.channel?.createdAt, DateTime.parse('2019-04-03T18:43:33.213373Z')); + expect(channelState.channel?.updatedAt, DateTime.parse('2019-04-03T18:43:33.213374Z')); expect(channelState.channel?.createdBy, isA()); expect(channelState.channel?.frozen, true); expect(channelState.channel?.extraData['example'], 1); @@ -147,8 +143,7 @@ void main() { memberCount: 42, ); - final field = - channelState.getComparableField(ChannelSortKey.memberCount); + final field = channelState.getComparableField(ChannelSortKey.memberCount); expect(field, isNotNull); expect(field!.value, equals(42)); }); diff --git a/packages/stream_chat/test/src/core/models/draft_message_test.dart b/packages/stream_chat/test/src/core/models/draft_message_test.dart index 6542e8b4df..0557c58759 100644 --- a/packages/stream_chat/test/src/core/models/draft_message_test.dart +++ b/packages/stream_chat/test/src/core/models/draft_message_test.dart @@ -35,8 +35,7 @@ void main() { expect(draftMessage.extraData, isEmpty); }); - test('should create a valid instance with UUID when id is not provided', - () { + test('should create a valid instance with UUID when id is not provided', () { final messageWithoutId = DraftMessage(text: text); expect(messageWithoutId.id, isNotNull); expect(messageWithoutId.id, isNotEmpty); @@ -156,8 +155,7 @@ void main() { expect(json['poll_id'], equals(pollId)); }); - test('should append command to text field in toJson when command exists', - () { + test('should append command to text field in toJson when command exists', () { final draftWithCommand = DraftMessage( id: id, text: 'Hello world', @@ -244,8 +242,7 @@ void main() { expect(deserializedMessage.id, equals(id)); expect(deserializedMessage.text, equals(text)); - expect(deserializedMessage.extraData['custom_field'], - equals('custom_value')); + expect(deserializedMessage.extraData['custom_field'], equals('custom_value')); expect(deserializedMessage.extraData['priority'], equals(5)); }); diff --git a/packages/stream_chat/test/src/core/models/event_test.dart b/packages/stream_chat/test/src/core/models/event_test.dart index f7847c4c60..82598566e3 100644 --- a/packages/stream_chat/test/src/core/models/event_test.dart +++ b/packages/stream_chat/test/src/core/models/event_test.dart @@ -94,7 +94,7 @@ void main() { 'total_unread_count': 0, 'unread_channels': 0, 'unread_threads': 0, - 'blocked_user_ids': [] + 'blocked_user_ids': [], }, 'user': {'id': 'id', 'teams': [], 'online': false, 'banned': false}, 'total_unread_count': 1, @@ -121,7 +121,7 @@ void main() { 'mentioned_users': [], 'silent': false, }, - } + }, }, ); diff --git a/packages/stream_chat/test/src/core/models/member_test.dart b/packages/stream_chat/test/src/core/models/member_test.dart index 86693a6504..b00884d450 100644 --- a/packages/stream_chat/test/src/core/models/member_test.dart +++ b/packages/stream_chat/test/src/core/models/member_test.dart @@ -105,10 +105,8 @@ void main() { final field1 = recentMember.getComparableField(MemberSortKey.createdAt); final field2 = olderMember.getComparableField(MemberSortKey.createdAt); - expect(field1!.compareTo(field2!), - greaterThan(0)); // More recent > Less recent - expect( - field2.compareTo(field1), lessThan(0)); // Less recent < More recent + expect(field1!.compareTo(field2!), greaterThan(0)); // More recent > Less recent + expect(field2.compareTo(field1), lessThan(0)); // Less recent < More recent }); test('should compare two members correctly using userId', () { @@ -159,10 +157,8 @@ void main() { final field1 = owner.getComparableField(MemberSortKey.channelRole); final field2 = moderator.getComparableField(MemberSortKey.channelRole); - expect(field1!.compareTo(field2!), - greaterThan(0)); // 'owner' > 'moderator' alphabetically - expect(field2.compareTo(field1), - lessThan(0)); // 'moderator' < 'owner' alphabetically + expect(field1!.compareTo(field2!), greaterThan(0)); // 'owner' > 'moderator' alphabetically + expect(field2.compareTo(field1), lessThan(0)); // 'moderator' < 'owner' alphabetically }); test('should compare two members correctly using extraData', () { diff --git a/packages/stream_chat/test/src/core/models/message_reaction_helper_test.dart b/packages/stream_chat/test/src/core/models/message_reaction_helper_test.dart index f24748ea73..0aa5ea5561 100644 --- a/packages/stream_chat/test/src/core/models/message_reaction_helper_test.dart +++ b/packages/stream_chat/test/src/core/models/message_reaction_helper_test.dart @@ -45,10 +45,8 @@ void main() { expect(updatedMessage.reactionGroups!.containsKey('like'), isTrue); expect(updatedMessage.reactionGroups!['like']!.count, 1); expect(updatedMessage.reactionGroups!['like']!.sumScores, 1); - expect(updatedMessage.reactionGroups!['like']!.firstReactionAt, - testReaction.createdAt); - expect(updatedMessage.reactionGroups!['like']!.lastReactionAt, - testReaction.createdAt); + expect(updatedMessage.reactionGroups!['like']!.firstReactionAt, testReaction.createdAt); + expect(updatedMessage.reactionGroups!['like']!.lastReactionAt, testReaction.createdAt); }); test('should add reaction to a message with existing reactions', () { @@ -151,8 +149,7 @@ void main() { expect(updatedMessage.ownReactions, isNotNull); expect(updatedMessage.ownReactions!.length, 2); - final reactionTypes = - updatedMessage.ownReactions!.map((r) => r.type).toList(); + final reactionTypes = updatedMessage.ownReactions!.map((r) => r.type).toList(); expect(reactionTypes, contains('like')); expect(reactionTypes, contains('love')); diff --git a/packages/stream_chat/test/src/core/models/message_state_test.dart b/packages/stream_chat/test/src/core/models/message_state_test.dart index c0351ebd77..513f2b6f13 100644 --- a/packages/stream_chat/test/src/core/models/message_state_test.dart +++ b/packages/stream_chat/test/src/core/models/message_state_test.dart @@ -113,8 +113,7 @@ void main() { test( 'isSent should return true if the message state is MessageCompleted with Sent state', () { - const messageState = - MessageState.completed(state: CompletedState.sent()); + const messageState = MessageState.completed(state: CompletedState.sent()); expect(messageState.isSent, true); }, ); diff --git a/packages/stream_chat/test/src/core/models/message_test.dart b/packages/stream_chat/test/src/core/models/message_test.dart index 2f363f45d7..325b098208 100644 --- a/packages/stream_chat/test/src/core/models/message_test.dart +++ b/packages/stream_chat/test/src/core/models/message_test.dart @@ -15,8 +15,7 @@ void main() { test('should parse json correctly', () { final message = Message.fromJson(jsonFixture('message.json')); expect(message.id, '4637f7e4-a06b-42db-ba5a-8d8270dd926f'); - expect(message.text, - 'https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA'); + expect(message.text, 'https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA'); expect(message.type, 'regular'); expect(message.user, isA()); expect(message.silent, isA()); @@ -41,24 +40,19 @@ void main() { test('should serialize to json correctly', () { final message = Message( id: '4637f7e4-a06b-42db-ba5a-8d8270dd926f', - text: - 'https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA', + text: 'https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA', attachments: [ Attachment.fromJson(const { 'type': 'giphy', 'author_name': 'GIPHY', 'title': 'The Lion King Disney GIF - Find \u0026 Share on GIPHY', - 'title_link': - 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif', + 'title_link': 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif', 'text': '''Discover \u0026 share this Lion King Live Action GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.''', - 'image_url': - 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif', - 'thumb_url': - 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif', - 'asset_url': - 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.mp4', - }) + 'image_url': 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif', + 'thumb_url': 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif', + 'asset_url': 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.mp4', + }), ], showInChannel: true, parentId: 'parentId', @@ -348,22 +342,18 @@ void main() { }); test('should return true when user is in restrictedVisibility list', () { - final message = - Message(restrictedVisibility: const ['user1', 'user2', 'user3']); + final message = Message(restrictedVisibility: const ['user1', 'user2', 'user3']); expect(message.isVisibleTo('user2'), true); }); - test('should return false when user is not in restrictedVisibility list', - () { - final message = - Message(restrictedVisibility: const ['user1', 'user2', 'user3']); + test('should return false when user is not in restrictedVisibility list', () { + final message = Message(restrictedVisibility: const ['user1', 'user2', 'user3']); expect(message.isVisibleTo('user4'), false); }); test('should handle case sensitivity correctly', () { final message = Message(restrictedVisibility: const ['User1', 'USER2']); - expect(message.isVisibleTo('user1'), false, - reason: 'Should be case sensitive'); + expect(message.isVisibleTo('user1'), false, reason: 'Should be case sensitive'); expect(message.isVisibleTo('User1'), true); }); }); @@ -380,15 +370,12 @@ void main() { }); test('should return false when user is in restrictedVisibility list', () { - final message = - Message(restrictedVisibility: const ['user1', 'user2', 'user3']); + final message = Message(restrictedVisibility: const ['user1', 'user2', 'user3']); expect(message.isNotVisibleTo('user2'), false); }); - test('should return true when user is not in restrictedVisibility list', - () { - final message = - Message(restrictedVisibility: const ['user1', 'user2', 'user3']); + test('should return true when user is not in restrictedVisibility list', () { + final message = Message(restrictedVisibility: const ['user1', 'user2', 'user3']); expect(message.isNotVisibleTo('user4'), true); }); diff --git a/packages/stream_chat/test/src/core/models/moderation_test.dart b/packages/stream_chat/test/src/core/models/moderation_test.dart index 4a099d5fc4..2405c44a90 100644 --- a/packages/stream_chat/test/src/core/models/moderation_test.dart +++ b/packages/stream_chat/test/src/core/models/moderation_test.dart @@ -153,8 +153,7 @@ void main() { }); test('should create from custom string correctly', () { - expect(ModerationAction.fromJson('custom'), - const ModerationAction('custom')); + expect(ModerationAction.fromJson('custom'), const ModerationAction('custom')); }); test('should handle legacy v1 moderation actions correctly', () { @@ -179,8 +178,7 @@ void main() { expect(ModerationAction.toJson(ModerationAction.flag), 'flag'); expect(ModerationAction.toJson(ModerationAction.remove), 'remove'); expect(ModerationAction.toJson(ModerationAction.shadow), 'shadow'); - expect(ModerationAction.toJson(const ModerationAction('custom')), - 'custom'); + expect(ModerationAction.toJson(const ModerationAction('custom')), 'custom'); }); test('should serialize legacy v1 action strings correctly', () { diff --git a/packages/stream_chat/test/src/core/models/own_user_test.dart b/packages/stream_chat/test/src/core/models/own_user_test.dart index 10f864ebd8..905a824600 100644 --- a/packages/stream_chat/test/src/core/models/own_user_test.dart +++ b/packages/stream_chat/test/src/core/models/own_user_test.dart @@ -28,8 +28,7 @@ void main() { expect(ownUser.createdAt, DateTime.parse('2020-03-03T16:48:28.853674Z')); expect(ownUser.updatedAt, DateTime.parse('2021-05-26T03:22:20.296181Z')); - expect( - ownUser.lastActive, DateTime.parse('2021-06-16T11:59:59.003453014Z')); + expect(ownUser.lastActive, DateTime.parse('2021-06-16T11:59:59.003453014Z')); expect(ownUser.banned, false); expect(ownUser.online, true); expect(ownUser.devices.length, 1); diff --git a/packages/stream_chat/test/src/core/models/poll_test.dart b/packages/stream_chat/test/src/core/models/poll_test.dart index bb3ba1c0a1..ae44676408 100644 --- a/packages/stream_chat/test/src/core/models/poll_test.dart +++ b/packages/stream_chat/test/src/core/models/poll_test.dart @@ -63,7 +63,7 @@ void main() { expect(json['name'], 'test'); expect(json['description'], isNull); expect(json['options'], [ - {'text': 'option1 text'} + {'text': 'option1 text'}, ]); expect(json['voting_visibility'], 'public'); expect(json['enforce_unique_vote'], true); diff --git a/packages/stream_chat/test/src/core/models/reaction_test.dart b/packages/stream_chat/test/src/core/models/reaction_test.dart index a60ad6e31f..1b5bdb4de7 100644 --- a/packages/stream_chat/test/src/core/models/reaction_test.dart +++ b/packages/stream_chat/test/src/core/models/reaction_test.dart @@ -23,7 +23,7 @@ void main() { 'online': false, 'banned': false, 'image': 'https://randomuser.me/api/portraits/women/45.jpg', - 'name': 'Daisy Morgan' + 'name': 'Daisy Morgan', }, ); expect(reaction.score, 1); @@ -62,10 +62,8 @@ void main() { final reaction = Reaction.fromJson(jsonFixture('reaction.json')); var newReaction = reaction.copyWith(); expect(newReaction.messageId, '76cd8c82-b557-4e48-9d12-87995d3a0e04'); - expect( - newReaction.createdAt, DateTime.parse('2020-01-28T22:17:31.108742Z')); - expect( - newReaction.updatedAt, DateTime.parse('2020-01-28T22:17:31.108742Z')); + expect(newReaction.createdAt, DateTime.parse('2020-01-28T22:17:31.108742Z')); + expect(newReaction.updatedAt, DateTime.parse('2020-01-28T22:17:31.108742Z')); expect(newReaction.type, 'wow'); expect( newReaction.user?.toJson(), @@ -78,7 +76,7 @@ void main() { 'online': false, 'banned': false, 'image': 'https://randomuser.me/api/portraits/women/45.jpg', - 'name': 'Daisy Morgan' + 'name': 'Daisy Morgan', }, ); expect(newReaction.score, 1); diff --git a/packages/stream_chat/test/src/core/models/read_test.dart b/packages/stream_chat/test/src/core/models/read_test.dart index 197fa3f161..4a538b51ab 100644 --- a/packages/stream_chat/test/src/core/models/read_test.dart +++ b/packages/stream_chat/test/src/core/models/read_test.dart @@ -33,12 +33,7 @@ void main() { ); expect(read.toJson(), { - 'user': { - 'id': 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e', - 'teams': [], - 'online': false, - 'banned': false - }, + 'user': {'id': 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e', 'teams': [], 'online': false, 'banned': false}, 'last_read': '2020-01-28T22:17:30.966485Z', 'unread_messages': 10, 'last_read_message_id': '8cc1301d-2d47-4305-945a-cd8e19b736d6', diff --git a/packages/stream_chat/test/src/core/models/serialization_test.dart b/packages/stream_chat/test/src/core/models/serialization_test.dart index 63b1428f78..594a3427b1 100644 --- a/packages/stream_chat/test/src/core/models/serialization_test.dart +++ b/packages/stream_chat/test/src/core/models/serialization_test.dart @@ -30,15 +30,14 @@ void main() { }); test('should have empty extraData', () { - final result = Serializer.moveToExtraDataFromRoot({ - 'prop1': 'test', - 'prop2': 123, - 'prop3': true, - }, [ - 'prop1', - 'prop2', - 'prop3' - ]); + final result = Serializer.moveToExtraDataFromRoot( + { + 'prop1': 'test', + 'prop2': 123, + 'prop3': true, + }, + ['prop1', 'prop2', 'prop3'], + ); expect(result, { 'prop1': 'test', diff --git a/packages/stream_chat/test/src/core/models/thread_test.dart b/packages/stream_chat/test/src/core/models/thread_test.dart index 21ab698a59..9e0a04bbee 100644 --- a/packages/stream_chat/test/src/core/models/thread_test.dart +++ b/packages/stream_chat/test/src/core/models/thread_test.dart @@ -91,8 +91,7 @@ void main() { final updatedThread = thread.copyWith(draft: updatedDraft); expect(updatedThread.draft?.message.text, equals('Updated draft')); - expect( - updatedThread.draft?.message.text, isNot(equals(draft.message.text))); + expect(updatedThread.draft?.message.text, isNot(equals(draft.message.text))); // Test copyWith with null draft (removing draft) final removedDraftThread = thread.copyWith(draft: null); @@ -209,8 +208,7 @@ void main() { // Test text equality instead of object identity expect(thread1.draft?.message.text, equals(thread2.draft?.message.text)); - expect(thread1.draft?.message.text, - isNot(equals(thread3.draft?.message.text))); + expect(thread1.draft?.message.text, isNot(equals(thread3.draft?.message.text))); }); }); } diff --git a/packages/stream_chat/test/src/core/models/user_block_test.dart b/packages/stream_chat/test/src/core/models/user_block_test.dart index 3297e0016b..18162de5d7 100644 --- a/packages/stream_chat/test/src/core/models/user_block_test.dart +++ b/packages/stream_chat/test/src/core/models/user_block_test.dart @@ -30,21 +30,11 @@ void main() { expect( userBlock.toJson(), { - 'user': { - 'id': 'user-1', - 'teams': [], - 'online': false, - 'banned': false - }, - 'blocked_user': { - 'id': 'user-2', - 'teams': [], - 'online': false, - 'banned': false - }, + 'user': {'id': 'user-1', 'teams': [], 'online': false, 'banned': false}, + 'blocked_user': {'id': 'user-2', 'teams': [], 'online': false, 'banned': false}, 'user_id': 'user-1', 'blocked_user_id': 'user-2', - 'created_at': '2020-01-28T22:17:30.830150Z' + 'created_at': '2020-01-28T22:17:30.830150Z', }, ); }); diff --git a/packages/stream_chat/test/src/core/models/user_test.dart b/packages/stream_chat/test/src/core/models/user_test.dart index 5e16ea861a..5915e442aa 100644 --- a/packages/stream_chat/test/src/core/models/user_test.dart +++ b/packages/stream_chat/test/src/core/models/user_test.dart @@ -9,8 +9,7 @@ void main() { const id = 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e'; const role = 'test-role'; const name = 'John'; - const image = - 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow'; + const image = 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow'; const extraDataStringTest = 'Extra data test'; const extraDataIntTest = 1; const extraDataDoubleTest = 1.1; @@ -352,15 +351,11 @@ void main() { lastActive: DateTime(2023, 6, 10), ); - final field1 = - recentlyActive.getComparableField(UserSortKey.lastActive); - final field2 = - lessRecentlyActive.getComparableField(UserSortKey.lastActive); + final field1 = recentlyActive.getComparableField(UserSortKey.lastActive); + final field2 = lessRecentlyActive.getComparableField(UserSortKey.lastActive); - expect(field1!.compareTo(field2!), - greaterThan(0)); // More recent > Less recent - expect( - field2.compareTo(field1), lessThan(0)); // Less recent < More recent + expect(field1!.compareTo(field2!), greaterThan(0)); // More recent > Less recent + expect(field2.compareTo(field1), lessThan(0)); // Less recent < More recent }); test('should compare two users correctly using banned status', () { diff --git a/packages/stream_chat/test/src/core/util/event_controller_test.dart b/packages/stream_chat/test/src/core/util/event_controller_test.dart index a789d2826b..fdf330a4d0 100644 --- a/packages/stream_chat/test/src/core/util/event_controller_test.dart +++ b/packages/stream_chat/test/src/core/util/event_controller_test.dart @@ -229,9 +229,7 @@ void main() { final event2 = Event(type: EventType.messageUpdated); final streamEvents = []; - controller - .where((event) => event.type == EventType.messageNew) - .listen(streamEvents.add); + controller.where((event) => event.type == EventType.messageNew).listen(streamEvents.add); controller.add(event1); controller.add(event2); diff --git a/packages/stream_chat/test/src/core/util/serializer_test.dart b/packages/stream_chat/test/src/core/util/serializer_test.dart index fbbb96a20c..9ae3fa536b 100644 --- a/packages/stream_chat/test/src/core/util/serializer_test.dart +++ b/packages/stream_chat/test/src/core/util/serializer_test.dart @@ -19,7 +19,7 @@ void main() { 'name': 'Sahil', 'age': 22, 'country': 'India', - } + }, }); }); @@ -30,7 +30,7 @@ void main() { 'name': 'Sahil', 'age': 22, 'country': 'India', - } + }, }); expect(serializer, { 'test': 'test', diff --git a/packages/stream_chat/test/src/db/chat_persistence_client_test.dart b/packages/stream_chat/test/src/db/chat_persistence_client_test.dart index 7676a7963a..555d13ecd0 100644 --- a/packages/stream_chat/test/src/db/chat_persistence_client_test.dart +++ b/packages/stream_chat/test/src/db/chat_persistence_client_test.dart @@ -46,32 +46,27 @@ class TestPersistenceClient extends ChatPersistenceClient { Future deletePinnedMessageByCids(List cids) => Future.value(); @override - Future deletePinnedMessageByIds(List messageIds) => - Future.value(); + Future deletePinnedMessageByIds(List messageIds) => Future.value(); @override - Future deleteMessagesFromUser( - {String? cid, - required String userId, - bool hardDelete = false, - DateTime? deletedAt}) => - throw UnimplementedError(); + Future deleteMessagesFromUser({ + String? cid, + required String userId, + bool hardDelete = false, + DateTime? deletedAt, + }) => throw UnimplementedError(); @override - Future deleteReactionsByMessageId(List messageIds) => - Future.value(); + Future deleteReactionsByMessageId(List messageIds) => Future.value(); @override - Future deletePinnedMessageReactionsByMessageId( - List messageIds) => - Future.value(); + Future deletePinnedMessageReactionsByMessageId(List messageIds) => Future.value(); @override Future deletePollVotesByPollIds(List pollIds) => Future.value(); @override - Future deleteDraftMessageByCid(String cid, {String? parentId}) => - Future.value(); + Future deleteDraftMessageByCid(String cid, {String? parentId}) => Future.value(); @override Future disconnect({bool flush = false}) => throw UnimplementedError(); @@ -80,23 +75,21 @@ class TestPersistenceClient extends ChatPersistenceClient { Future flush() => throw UnimplementedError(); @override - Future getChannelByCid(String cid) async => - ChannelModel(cid: cid); + Future getChannelByCid(String cid) async => ChannelModel(cid: cid); @override Future> getChannelCids() => throw UnimplementedError(); @override - Future> getChannelStates( - {Filter? filter, - SortOrder? channelStateSort, - int? messageLimit, - PaginationParams? paginationParams}) => - throw UnimplementedError(); + Future> getChannelStates({ + Filter? filter, + SortOrder? channelStateSort, + int? messageLimit, + PaginationParams? paginationParams, + }) => throw UnimplementedError(); @override - Future>> getChannelThreads(String cid) => - throw UnimplementedError(); + Future>> getChannelThreads(String cid) => throw UnimplementedError(); @override Future getConnectionInfo() => throw UnimplementedError(); @@ -108,14 +101,10 @@ class TestPersistenceClient extends ChatPersistenceClient { Future> getMembersByCid(String cid) async => []; @override - Future> getMessagesByCid(String cid, - {PaginationParams? messagePagination}) async => - []; + Future> getMessagesByCid(String cid, {PaginationParams? messagePagination}) async => []; @override - Future> getPinnedMessagesByCid(String cid, - {PaginationParams? messagePagination}) async => - []; + Future> getPinnedMessagesByCid(String cid, {PaginationParams? messagePagination}) async => []; @override Future> getReadsByCid(String cid) async => []; @@ -124,22 +113,18 @@ class TestPersistenceClient extends ChatPersistenceClient { Future getDraftMessageByCid( String cid, { String? parentId, - }) async => - Draft( - channelCid: cid, - parentId: parentId, - createdAt: DateTime.now(), - message: DraftMessage(id: 'message-id', text: 'message-text'), - ); + }) async => Draft( + channelCid: cid, + parentId: parentId, + createdAt: DateTime.now(), + message: DraftMessage(id: 'message-id', text: 'message-text'), + ); @override - Future> getReplies(String parentId, - {PaginationParams? options}) => - throw UnimplementedError(); + Future> getReplies(String parentId, {PaginationParams? options}) => throw UnimplementedError(); @override - Future updateChannelQueries(Filter? filter, List cids, - {bool clearQueryCache = false}) => + Future updateChannelQueries(Filter? filter, List cids, {bool clearQueryCache = false}) => throw UnimplementedError(); @override @@ -149,15 +134,13 @@ class TestPersistenceClient extends ChatPersistenceClient { Future updateConnectionInfo(Event event) => throw UnimplementedError(); @override - Future updateLastSyncAt(DateTime lastSyncAt) => - throw UnimplementedError(); + Future updateLastSyncAt(DateTime lastSyncAt) => throw UnimplementedError(); @override Future updateReactions(List reactions) => Future.value(); @override - Future updatePinnedMessageReactions(List reactions) => - Future.value(); + Future updatePinnedMessageReactions(List reactions) => Future.value(); @override Future updatePollVotes(List pollVotes) => Future.value(); @@ -166,20 +149,16 @@ class TestPersistenceClient extends ChatPersistenceClient { Future updateUsers(List users) => Future.value(); @override - Future bulkUpdateMembers(Map?> members) => - Future.value(); + Future bulkUpdateMembers(Map?> members) => Future.value(); @override - Future bulkUpdateMessages(Map?> messages) => - Future.value(); + Future bulkUpdateMessages(Map?> messages) => Future.value(); @override - Future bulkUpdatePinnedMessages(Map?> messages) => - Future.value(); + Future bulkUpdatePinnedMessages(Map?> messages) => Future.value(); @override - Future bulkUpdateReads(Map?> reads) => - Future.value(); + Future bulkUpdateReads(Map?> reads) => Future.value(); @override Future deletePollsByIds(List pollIds) => Future.value(); @@ -203,8 +182,7 @@ class TestPersistenceClient extends ChatPersistenceClient { Future deleteLocationsByCid(String cid) => Future.value(); @override - Future deleteLocationsByMessageIds(List messageIds) => - Future.value(); + Future deleteLocationsByMessageIds(List messageIds) => Future.value(); } void main() { @@ -273,8 +251,8 @@ void main() { user: user, ownReactions: [Reaction(type: 'test', user: user)], latestReactions: [Reaction(type: 'test', user: user)], - ) - ] + ), + ], }; persistenceClient.updateChannelThreads(cid, threads); }); @@ -296,7 +274,7 @@ void main() { user: user, ownReactions: [Reaction(type: 'test', user: user)], latestReactions: [Reaction(type: 'test', user: user)], - ) + ), ], pinnedMessages: [ Message( @@ -305,13 +283,10 @@ void main() { user: user, ownReactions: [Reaction(type: 'test', user: user)], latestReactions: [Reaction(type: 'test', user: user)], - ) + ), ], read: [ - Read( - lastRead: DateTime.now(), - user: user, - lastReadMessageId: 'last-test-message'), + Read(lastRead: DateTime.now(), user: user, lastReadMessageId: 'last-test-message'), ], members: [Member(user: user)], ); diff --git a/packages/stream_chat/test/src/fakes.dart b/packages/stream_chat/test/src/fakes.dart index ff2c538e74..4e561b0884 100644 --- a/packages/stream_chat/test/src/fakes.dart +++ b/packages/stream_chat/test/src/fakes.dart @@ -34,8 +34,7 @@ class FakeTokenManager extends Fake implements TokenManager { String userId, { Token? token, TokenProvider? provider, - }) async => - this.token; + }) async => this.token; @override void reset() {} @@ -48,8 +47,8 @@ class FakePersistenceClient extends Fake implements ChatPersistenceClient { FakePersistenceClient({ DateTime? lastSyncAt, List? channelCids, - }) : _lastSyncAt = lastSyncAt, - _channelCids = channelCids ?? []; + }) : _lastSyncAt = lastSyncAt, + _channelCids = channelCids ?? []; String? _userId; bool _isConnected = false; @@ -144,8 +143,7 @@ class FakeChatApi extends Fake implements StreamChatApi { AttachmentFileUploader? _fileUploader; @override - AttachmentFileUploader get fileUploader => - _fileUploader ??= MockAttachmentFileUploader(); + AttachmentFileUploader get fileUploader => _fileUploader ??= MockAttachmentFileUploader(); } class FakeClientState extends Fake implements ClientState { @@ -218,8 +216,7 @@ class FakeWebSocket extends Fake implements WebSocket { ConnectionStatus get connectionStatus => _connectionStatusController.value; @override - Stream get connectionStatusStream => - _connectionStatusController.stream; + Stream get connectionStatusStream => _connectionStatusController.stream; @override Completer? connectionCompleter; @@ -265,8 +262,7 @@ class FakeWebSocketWithConnectionError extends Fake implements WebSocket { ConnectionStatus get connectionStatus => _connectionStatusController.value; @override - Stream get connectionStatusStream => - _connectionStatusController.stream; + Stream get connectionStatusStream => _connectionStatusController.stream; @override Completer? connectionCompleter; @@ -296,8 +292,7 @@ class FakeWebSocketWithConnectionError extends Fake implements WebSocket { class FakeChannelState extends Fake implements ChannelState {} -class FakePartialUpdateMemberResponse extends Fake - implements PartialUpdateMemberResponse { +class FakePartialUpdateMemberResponse extends Fake implements PartialUpdateMemberResponse { FakePartialUpdateMemberResponse({ Member? channelMember, }) : _channelMember = channelMember ?? Member(); diff --git a/packages/stream_chat/test/src/matchers.dart b/packages/stream_chat/test/src/matchers.dart index 752f980373..27a3b823ac 100644 --- a/packages/stream_chat/test/src/matchers.dart +++ b/packages/stream_chat/test/src/matchers.dart @@ -9,8 +9,7 @@ import 'package:stream_chat/src/core/models/message.dart'; import 'package:stream_chat/src/core/models/user.dart'; import 'package:test/test.dart'; -Matcher isSameMultipartFileAs(MultipartFile targetFile) => - _IsSameMultipartFileAs(targetFile: targetFile); +Matcher isSameMultipartFileAs(MultipartFile targetFile) => _IsSameMultipartFileAs(targetFile: targetFile); class _IsSameMultipartFileAs extends Matcher { const _IsSameMultipartFileAs({required this.targetFile}); @@ -18,16 +17,13 @@ class _IsSameMultipartFileAs extends Matcher { final MultipartFile targetFile; @override - Description describe(Description description) => - description.add('is same multipartFile as $targetFile'); + Description describe(Description description) => description.add('is same multipartFile as $targetFile'); @override - bool matches(covariant MultipartFile file, Map matchState) => - file.length == targetFile.length; + bool matches(covariant MultipartFile file, Map matchState) => file.length == targetFile.length; } -Matcher isSameEventAs(Event targetEvent) => - _IsSameEventAs(targetEvent: targetEvent); +Matcher isSameEventAs(Event targetEvent) => _IsSameEventAs(targetEvent: targetEvent); class _IsSameEventAs extends Matcher { const _IsSameEventAs({required this.targetEvent}); @@ -35,12 +31,10 @@ class _IsSameEventAs extends Matcher { final Event targetEvent; @override - Description describe(Description description) => - description.add('is same event as $targetEvent'); + Description describe(Description description) => description.add('is same event as $targetEvent'); @override - bool matches(covariant Event event, Map matchState) => - event.type == targetEvent.type; + bool matches(covariant Event event, Map matchState) => event.type == targetEvent.type; } Matcher isSameMessageAs( @@ -51,16 +45,15 @@ Matcher isSameMessageAs( bool matchAttachments = false, bool matchAttachmentsUploadState = false, bool matchParentId = false, -}) => - _IsSameMessageAs( - targetMessage: targetMessage, - matchText: matchText, - matchReactions: matchReactions, - matchMessageState: matchMessageState, - matchAttachments: matchAttachments, - matchAttachmentsUploadState: matchAttachmentsUploadState, - matchParentId: matchParentId, - ); +}) => _IsSameMessageAs( + targetMessage: targetMessage, + matchText: matchText, + matchReactions: matchReactions, + matchMessageState: matchMessageState, + matchAttachments: matchAttachments, + matchAttachmentsUploadState: matchAttachmentsUploadState, + matchParentId: matchParentId, +); class _IsSameMessageAs extends Matcher { const _IsSameMessageAs({ @@ -82,8 +75,7 @@ class _IsSameMessageAs extends Matcher { final bool matchParentId; @override - Description describe(Description description) => - description.add('is same message as $targetMessage'); + Description describe(Description description) => description.add('is same message as $targetMessage'); @override bool matches(covariant Message message, Map matchState) { @@ -96,19 +88,13 @@ class _IsSameMessageAs extends Matcher { } if (matchReactions) { matches &= const ListEquality().equals( - message.ownReactions - ?.map((it) => '${it.type}-${it.messageId}') - .toList(), - targetMessage.ownReactions - ?.map((it) => '${it.type}-${it.messageId}') - .toList()); + message.ownReactions?.map((it) => '${it.type}-${it.messageId}').toList(), + targetMessage.ownReactions?.map((it) => '${it.type}-${it.messageId}').toList(), + ); matches &= const ListEquality().equals( - message.latestReactions - ?.map((it) => '${it.type}-${it.messageId}') - .toList(), - targetMessage.latestReactions - ?.map((it) => '${it.type}-${it.messageId}') - .toList()); + message.latestReactions?.map((it) => '${it.type}-${it.messageId}').toList(), + targetMessage.latestReactions?.map((it) => '${it.type}-${it.messageId}').toList(), + ); } if (matchAttachments) { bool matchAttachments() { @@ -142,13 +128,12 @@ Matcher isSameDraftMessageAs( bool matchText = false, bool matchAttachments = false, bool matchParentId = false, -}) => - _IsSameDraftMessageAs( - targetMessage: targetMessage, - matchText: matchText, - matchAttachments: matchAttachments, - matchParentId: matchParentId, - ); +}) => _IsSameDraftMessageAs( + targetMessage: targetMessage, + matchText: matchText, + matchAttachments: matchAttachments, + matchParentId: matchParentId, +); class _IsSameDraftMessageAs extends Matcher { const _IsSameDraftMessageAs({ @@ -164,8 +149,7 @@ class _IsSameDraftMessageAs extends Matcher { final bool matchParentId; @override - Description describe(Description description) => - description.add('is same draft message as $targetMessage'); + Description describe(Description description) => description.add('is same draft message as $targetMessage'); @override bool matches(covariant DraftMessage message, Map matchState) { @@ -199,11 +183,10 @@ class _IsSameDraftMessageAs extends Matcher { Matcher isSameAttachmentAs( Attachment targetAttachment, { bool matchUploadState = false, -}) => - _IsSameAttachmentAs( - targetAttachment: targetAttachment, - matchUploadState: matchUploadState, - ); +}) => _IsSameAttachmentAs( + targetAttachment: targetAttachment, + matchUploadState: matchUploadState, +); class _IsSameAttachmentAs extends Matcher { const _IsSameAttachmentAs({ @@ -215,8 +198,7 @@ class _IsSameAttachmentAs extends Matcher { final bool matchUploadState; @override - Description describe(Description description) => - description.add('is same attachment as $targetAttachment'); + Description describe(Description description) => description.add('is same attachment as $targetAttachment'); @override bool matches(covariant Attachment attachment, Map matchState) { @@ -236,15 +218,13 @@ class _IsSameUserAs extends Matcher { final User targetUser; @override - Description describe(Description description) => - description.add('is same user as $targetUser'); + Description describe(Description description) => description.add('is same user as $targetUser'); @override bool matches(covariant User user, Map matchState) => user.id == targetUser.id; } -Matcher isCorrectChannelFor(ChannelState channelState) => - _IsCorrectChannelFor(channelState: channelState); +Matcher isCorrectChannelFor(ChannelState channelState) => _IsCorrectChannelFor(channelState: channelState); class _IsCorrectChannelFor extends Matcher { const _IsCorrectChannelFor({required this.channelState}); @@ -252,10 +232,8 @@ class _IsCorrectChannelFor extends Matcher { final ChannelState channelState; @override - Description describe(Description description) => - description.add('is correct channel for $channelState'); + Description describe(Description description) => description.add('is correct channel for $channelState'); @override - bool matches(covariant Channel channel, Map matchState) => - channel.cid == channelState.channel?.cid; + bool matches(covariant Channel channel, Map matchState) => channel.cid == channelState.channel?.cid; } diff --git a/packages/stream_chat/test/src/mocks.dart b/packages/stream_chat/test/src/mocks.dart index 9539ee1843..a845274bc4 100644 --- a/packages/stream_chat/test/src/mocks.dart +++ b/packages/stream_chat/test/src/mocks.dart @@ -67,8 +67,7 @@ class MockModerationApi extends Mock implements ModerationApi {} class MockGeneralApi extends Mock implements GeneralApi {} -class MockAttachmentFileUploader extends Mock - implements AttachmentFileUploader {} +class MockAttachmentFileUploader extends Mock implements AttachmentFileUploader {} class MockPersistenceClient extends Mock implements ChatPersistenceClient { String? _userId; @@ -117,11 +116,10 @@ class MockStreamChatClient extends Mock implements StreamChatClient { String? eventType4, ]) { if (eventType == null || eventType == EventType.any) return eventStream; - return eventStream.where((event) => - event.type == eventType || - event.type == eventType2 || - event.type == eventType3 || - event.type == eventType4); + return eventStream.where( + (event) => + event.type == eventType || event.type == eventType2 || event.type == eventType3 || event.type == eventType4, + ); } @override @@ -132,8 +130,7 @@ class MockStreamChatClientWithPersistence extends MockStreamChatClient { ChatPersistenceClient? _persistenceClient; @override - ChatPersistenceClient get chatPersistenceClient => - _persistenceClient ??= MockPersistenceClient(); + ChatPersistenceClient get chatPersistenceClient => _persistenceClient ??= MockPersistenceClient(); @override bool get persistenceEnabled => true; @@ -166,13 +163,10 @@ class MockRetryQueueChannel extends Mock implements Channel { String? eventType3, String? eventType4, ]) { - return client - .on(eventType, eventType2, eventType3, eventType4) - .where((e) => e.cid == cid); + return client.on(eventType, eventType2, eventType3, eventType4).where((e) => e.cid == cid); } } class MockWebSocket extends Mock implements WebSocket {} -class MockChannelDeliveryReporter extends Mock - implements ChannelDeliveryReporter {} +class MockChannelDeliveryReporter extends Mock implements ChannelDeliveryReporter {} diff --git a/packages/stream_chat/test/src/utils.dart b/packages/stream_chat/test/src/utils.dart index 768d2fe296..dd3030d94c 100644 --- a/packages/stream_chat/test/src/utils.dart +++ b/packages/stream_chat/test/src/utils.dart @@ -28,5 +28,4 @@ extension IntX on num { } // Top level util function to delay the code execution -Future delay(num milliseconds) => - Future.delayed(Duration(milliseconds: milliseconds.toInt())); +Future delay(num milliseconds) => Future.delayed(Duration(milliseconds: milliseconds.toInt())); diff --git a/packages/stream_chat/test/src/ws/websocket_test.dart b/packages/stream_chat/test/src/ws/websocket_test.dart index 053a8d78ff..5509310083 100644 --- a/packages/stream_chat/test/src/ws/websocket_test.dart +++ b/packages/stream_chat/test/src/ws/websocket_test.dart @@ -24,8 +24,7 @@ void main() { WebSocketChannel channelProvider( Uri uri, { Iterable? protocols, - }) => - webSocketChannel; + }) => webSocketChannel; webSocket = WebSocket( apiKey: 'api-key', diff --git a/packages/stream_chat_flutter/example/lib/debug/channel_page.dart b/packages/stream_chat_flutter/example/lib/debug/channel_page.dart index a1d4b6a419..a5719b1a16 100644 --- a/packages/stream_chat_flutter/example/lib/debug/channel_page.dart +++ b/packages/stream_chat_flutter/example/lib/debug/channel_page.dart @@ -39,8 +39,7 @@ class _DebugChannelPageState extends State { _channelSubscription = _channel.state!.channelStateStream.listen((state) { setState(() => _channelState = state); }); - _ownUserSubscription = - _channel.client.state.currentUserStream.listen((ownUser) { + _ownUserSubscription = _channel.client.state.currentUserStream.listen((ownUser) { setState(() => _ownUser = ownUser); }); } @@ -54,10 +53,8 @@ class _DebugChannelPageState extends State { @override Widget build(BuildContext context) { - final members = - _channelState?.members ?? _channel.state?.members ?? const []; - final mutes = - _ownUser?.mutes ?? _channel.client.state.currentUser?.mutes ?? const []; + final members = _channelState?.members ?? _channel.state?.members ?? const []; + final mutes = _ownUser?.mutes ?? _channel.client.state.currentUser?.mutes ?? const []; //SingleChildScrollView return Scaffold( appBar: AppBar( diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index 3518d0056f..9f21d9c77a 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -195,16 +195,16 @@ class _ChannelListPageState extends State { @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, - ); - }, - ), - ); + body: StreamChannelListView( + onChannelTap: widget.onTap, + controller: _listController, + itemBuilder: (context, channels, index, defaultWidget) { + return defaultWidget.copyWith( + selected: channels[index] == widget.selectedChannel, + ); + }, + ), + ); } class ChannelPage extends StatefulWidget { @@ -254,81 +254,83 @@ class _ChannelPageState extends State { Expanded( child: StreamMessageListView( threadBuilder: (_, parent) => ThreadPage(parent: parent!), - messageBuilder: ( - context, - messageDetails, - messages, - defaultWidget, - ) { - // The threshold after which the message is considered - // swiped. - const threshold = 0.2; - - final isMyMessage = messageDetails.isMyMessage; - - // The direction in which the message can be swiped. - final swipeDirection = isMyMessage - ? SwipeDirection.endToStart // - : SwipeDirection.startToEnd; - - return Swipeable( - key: ValueKey(messageDetails.message.id), - direction: swipeDirection, - swipeThreshold: threshold, - onSwiped: (details) => reply(messageDetails.message), - backgroundBuilder: (context, details) { - // The alignment of the swipe action. - final alignment = isMyMessage - ? Alignment.centerRight // - : Alignment.centerLeft; - - // The progress of the swipe action. - final progress = - math.min(details.progress, threshold) / threshold; - - // The offset for the reply icon. - var offset = Offset.lerp( - const Offset(-24, 0), - const Offset(12, 0), - progress, - )!; - - // If the message is mine, we need to flip the offset. - if (isMyMessage) { - offset = Offset(-offset.dx, -offset.dy); - } - - final _streamTheme = StreamChatTheme.of(context); - - return Align( - alignment: alignment, - child: Transform.translate( - offset: offset, - child: Opacity( - opacity: progress, - child: SizedBox.square( - dimension: 30, - child: CustomPaint( - painter: AnimatedCircleBorderPainter( - progress: progress, - color: _streamTheme.colorTheme.borders, - ), - child: Center( - child: StreamSvgIcon( - icon: StreamSvgIcons.reply, - size: lerpDouble(0, 18, progress), - color: _streamTheme.colorTheme.accentPrimary, + messageBuilder: + ( + context, + messageDetails, + messages, + defaultWidget, + ) { + // The threshold after which the message is considered + // swiped. + const threshold = 0.2; + + final isMyMessage = messageDetails.isMyMessage; + + // The direction in which the message can be swiped. + final swipeDirection = isMyMessage + ? SwipeDirection + .endToStart // + : SwipeDirection.startToEnd; + + return Swipeable( + key: ValueKey(messageDetails.message.id), + direction: swipeDirection, + swipeThreshold: threshold, + onSwiped: (details) => reply(messageDetails.message), + backgroundBuilder: (context, details) { + // The alignment of the swipe action. + final alignment = isMyMessage + ? Alignment + .centerRight // + : Alignment.centerLeft; + + // The progress of the swipe action. + final progress = math.min(details.progress, threshold) / threshold; + + // The offset for the reply icon. + var offset = Offset.lerp( + const Offset(-24, 0), + const Offset(12, 0), + progress, + )!; + + // If the message is mine, we need to flip the offset. + if (isMyMessage) { + offset = Offset(-offset.dx, -offset.dy); + } + + final _streamTheme = StreamChatTheme.of(context); + + return Align( + alignment: alignment, + child: Transform.translate( + offset: offset, + child: Opacity( + opacity: progress, + child: SizedBox.square( + dimension: 30, + child: CustomPaint( + painter: AnimatedCircleBorderPainter( + progress: progress, + color: _streamTheme.colorTheme.borders, + ), + child: Center( + child: StreamSvgIcon( + icon: StreamSvgIcons.reply, + size: lerpDouble(0, 18, progress), + color: _streamTheme.colorTheme.accentPrimary, + ), + ), ), ), ), ), - ), - ), + ); + }, + child: defaultWidget.copyWith(onReplyTap: reply), ); }, - child: defaultWidget.copyWith(onReplyTap: reply), - ); - }, ), ), StreamMessageInput( diff --git a/packages/stream_chat_flutter/example/lib/split_view.dart b/packages/stream_chat_flutter/example/lib/split_view.dart index 6f75b71a69..a873f92265 100644 --- a/packages/stream_chat_flutter/example/lib/split_view.dart +++ b/packages/stream_chat_flutter/example/lib/split_view.dart @@ -113,11 +113,11 @@ class _ChannelListPageState extends State { @override Widget build(BuildContext context) => Scaffold( - body: StreamChannelListView( - onChannelTap: widget.onTap, - controller: _listController, - ), - ); + body: StreamChannelListView( + onChannelTap: widget.onTap, + controller: _listController, + ), + ); } class ChannelPage extends StatelessWidget { @@ -127,20 +127,20 @@ class ChannelPage extends StatelessWidget { @override Widget build(BuildContext context) => Navigator( - onGenerateRoute: (settings) => MaterialPageRoute( - builder: (context) => const Scaffold( - appBar: StreamChannelHeader( - showBackButton: false, - ), - body: Column( - children: [ - Expanded( - child: StreamMessageListView(), - ), - StreamMessageInput(), - ], + onGenerateRoute: (settings) => MaterialPageRoute( + builder: (context) => const Scaffold( + appBar: StreamChannelHeader( + showBackButton: false, + ), + body: Column( + children: [ + Expanded( + child: StreamMessageListView(), ), - ), + StreamMessageInput(), + ], ), - ); + ), + ), + ); } diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart index 8e5c52142f..bc57390a29 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart @@ -96,21 +96,21 @@ class _ChannelListPageState extends State { @override Widget build(BuildContext context) => Scaffold( - body: StreamChannelListView( - controller: _listController, - itemBuilder: _channelPreviewBuilder, - onChannelTap: (channel) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: channel, - child: const ChannelPage(), - ), - ), - ); - }, - ), - ); + body: StreamChannelListView( + controller: _listController, + itemBuilder: _channelPreviewBuilder, + onChannelTap: (channel) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: const ChannelPage(), + ), + ), + ); + }, + ), + ); Widget _channelPreviewBuilder( BuildContext context, @@ -143,12 +143,10 @@ class _ChannelListPageState extends State { ), title: StreamChannelName( textStyle: StreamChannelPreviewTheme.of(context).titleStyle!.copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - // ignore: deprecated_member_use - .withOpacity(opacity), - ), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis + // ignore: deprecated_member_use + .withOpacity(opacity), + ), channel: channel, ), subtitle: Text(subtitle), diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart index 905753f7e1..f271dae858 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart @@ -82,20 +82,20 @@ class _ChannelListPageState extends State { @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(), - ), - ), - ); - }, - ), - ); + body: StreamChannelListView( + controller: _listController, + onChannelTap: (channel) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: const ChannelPage(), + ), + ), + ); + }, + ), + ); } class ChannelPage extends StatelessWidget { diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart index ef205abfe5..029dfdac61 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart @@ -133,8 +133,7 @@ class ChannelPage extends StatelessWidget { StreamMessageWidget _, ) { final message = details.message; - final isCurrentUser = - StreamChat.of(context).currentUser!.id == message.user!.id; + final isCurrentUser = StreamChat.of(context).currentUser!.id == message.user!.id; final textAlign = isCurrentUser ? TextAlign.right : TextAlign.left; final color = isCurrentUser ? Colors.blueGrey : Colors.blue; diff --git a/packages/stream_chat_flutter/example/pubspec.yaml b/packages/stream_chat_flutter/example/pubspec.yaml index 515df5f429..ed569474dc 100644 --- a/packages/stream_chat_flutter/example/pubspec.yaml +++ b/packages/stream_chat_flutter/example/pubspec.yaml @@ -16,8 +16,8 @@ version: 1.0.0+1 # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: collection: ^1.17.2 diff --git a/packages/stream_chat_flutter/lib/conditional_parent_builder/conditional_parent_builder.dart b/packages/stream_chat_flutter/lib/conditional_parent_builder/conditional_parent_builder.dart index 8314757de3..757be609e2 100644 --- a/packages/stream_chat_flutter/lib/conditional_parent_builder/conditional_parent_builder.dart +++ b/packages/stream_chat_flutter/lib/conditional_parent_builder/conditional_parent_builder.dart @@ -5,10 +5,11 @@ 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, -); +typedef ParentBuilder = + Widget Function( + BuildContext context, + Widget child, + ); /// {@template conditionalParentBuilder} /// A widget that allows developers to conditionally wrap the [child] widget diff --git a/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget.dart b/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget.dart index f820eb2c13..5bc8129188 100644 --- a/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget.dart +++ b/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget.dart @@ -26,14 +26,11 @@ class DesktopWidget extends DesktopWidgetBase { final PlatformBuilder? linux; @override - Widget createMacosWidget(BuildContext context) => - macOS?.call(context) ?? const Empty(); + Widget createMacosWidget(BuildContext context) => macOS?.call(context) ?? const Empty(); @override - Widget createWindowsWidget(BuildContext context) => - windows?.call(context) ?? const Empty(); + Widget createWindowsWidget(BuildContext context) => windows?.call(context) ?? const Empty(); @override - Widget createLinuxWidget(BuildContext context) => - linux?.call(context) ?? const Empty(); + Widget createLinuxWidget(BuildContext context) => linux?.call(context) ?? const Empty(); } diff --git a/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget_base.dart b/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget_base.dart index f5a42a7bd7..9a1a822dfc 100644 --- a/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget_base.dart +++ b/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget_base.dart @@ -3,9 +3,10 @@ import 'package:flutter/material.dart' show Theme; import 'package:flutter/widgets.dart'; /// A generic widget builder function. -typedef PlatformBuilder = T Function( - BuildContext context, -); +typedef PlatformBuilder = + T Function( + BuildContext context, + ); /// An abstract class used as a building block for creating /// [DesktopPlatformWidget]s. @@ -21,8 +22,7 @@ typedef PlatformBuilder = T Function( /// * M = macOS /// * W = Windows /// * L = Linux -abstract class DesktopWidgetBase extends StatelessWidget { +abstract class DesktopWidgetBase extends StatelessWidget { /// Builds a [DesktopWidgetBase]. const DesktopWidgetBase({super.key}); diff --git a/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget_builder.dart b/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget_builder.dart index 923678617d..5aafeee047 100644 --- a/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget_builder.dart +++ b/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget_builder.dart @@ -2,10 +2,11 @@ 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, -)?; +typedef DesktopTargetBuilder = + Widget? Function( + BuildContext context, + Widget? child, + )?; /// A widget that utilizes [DesktopWidgetBuilder]s to build different widgets /// for each specified desktop platform. diff --git a/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget.dart b/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget.dart index ff558ef1e7..426556b08c 100644 --- a/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget.dart +++ b/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget.dart @@ -26,14 +26,11 @@ class PlatformWidget extends PlatformWidgetBase { final PlatformBuilder? web; @override - Widget createDesktopWidget(BuildContext context) => - desktop?.call(context) ?? const Empty(); + Widget createDesktopWidget(BuildContext context) => desktop?.call(context) ?? const Empty(); @override - Widget createMobileWidget(BuildContext context) => - mobile?.call(context) ?? const Empty(); + Widget createMobileWidget(BuildContext context) => mobile?.call(context) ?? const Empty(); @override - Widget createWebWidget(BuildContext context) => - web?.call(context) ?? const Empty(); + Widget createWebWidget(BuildContext context) => web?.call(context) ?? const Empty(); } diff --git a/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_base.dart b/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_base.dart index 0487bd4396..8bf30cc361 100644 --- a/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_base.dart +++ b/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_base.dart @@ -2,9 +2,10 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; /// A generic widget builder function. -typedef PlatformBuilder = T Function( - BuildContext context, -); +typedef PlatformBuilder = + T Function( + BuildContext context, + ); /// An abstract class used as a building block for creating [PlatformWidget]s. /// @@ -21,8 +22,7 @@ typedef PlatformBuilder = T Function( /// * M = Mobile /// * D = Desktop /// * W = Web -abstract class PlatformWidgetBase extends StatelessWidget { +abstract class PlatformWidgetBase extends StatelessWidget { /// Builds a [PlatformWidgetBase]. const PlatformWidgetBase({ super.key, diff --git a/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_builder.dart b/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_builder.dart index b3950d2f0c..ef5e4db01c 100644 --- a/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_builder.dart +++ b/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_builder.dart @@ -2,10 +2,11 @@ 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, -)?; +typedef PlatformTargetBuilder = + Widget? Function( + BuildContext context, + Widget? child, + )?; /// A widget that utilizes [PlatformTargetBuilder]s to build different widgets /// for each specified platform. diff --git a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/element_registry.dart b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/element_registry.dart index 03f274f387..21d3b59446 100644 --- a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/element_registry.dart +++ b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/element_registry.dart @@ -38,9 +38,9 @@ class _RegistryWidgetState extends State { @override Widget build(BuildContext context) => _InheritedRegistryWidget( - state: this, - child: widget.child, - ); + state: this, + child: widget.child, + ); } class _InheritedRegistryWidget extends InheritedWidget { @@ -66,30 +66,25 @@ class _RegisteredElement extends ProxyElement { @override void mount(Element? parent, dynamic newSlot) { super.mount(parent, newSlot); - final _inheritedRegistryWidget = - dependOnInheritedWidgetOfExactType<_InheritedRegistryWidget>()!; + final _inheritedRegistryWidget = dependOnInheritedWidgetOfExactType<_InheritedRegistryWidget>()!; _registryWidgetState = _inheritedRegistryWidget.state; _registryWidgetState.registeredElements.add(this); - _registryWidgetState.widget.elementNotifier?.value = - _registryWidgetState.registeredElements; + _registryWidgetState.widget.elementNotifier?.value = _registryWidgetState.registeredElements; } @override void didChangeDependencies() { super.didChangeDependencies(); - final _inheritedRegistryWidget = - dependOnInheritedWidgetOfExactType<_InheritedRegistryWidget>()!; + final _inheritedRegistryWidget = dependOnInheritedWidgetOfExactType<_InheritedRegistryWidget>()!; _registryWidgetState = _inheritedRegistryWidget.state; _registryWidgetState.registeredElements.add(this); - _registryWidgetState.widget.elementNotifier?.value = - _registryWidgetState.registeredElements; + _registryWidgetState.widget.elementNotifier?.value = _registryWidgetState.registeredElements; } @override void unmount() { _registryWidgetState.registeredElements.remove(this); - _registryWidgetState.widget.elementNotifier?.value = - _registryWidgetState.registeredElements; + _registryWidgetState.widget.elementNotifier?.value = _registryWidgetState.registeredElements; super.unmount(); } } diff --git a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/item_positions_listener.dart b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/item_positions_listener.dart index d2752a00b2..808a2906fd 100644 --- a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/item_positions_listener.dart +++ b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/item_positions_listener.dart @@ -51,9 +51,7 @@ class ItemPosition { itemTrailingEdge == other.itemTrailingEdge; @override - int get hashCode => - 31 * (31 * (index.hashCode + 7) + itemLeadingEdge.hashCode) + - itemTrailingEdge.hashCode; + int get hashCode => 31 * (31 * (index.hashCode + 7) + itemLeadingEdge.hashCode) + itemTrailingEdge.hashCode; @override String toString() => diff --git a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/positioned_list.dart b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/positioned_list.dart index df08329cf8..a04f7c3397 100644 --- a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/positioned_list.dart +++ b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/positioned_list.dart @@ -47,9 +47,9 @@ class PositionedList extends StatefulWidget { this.findChildIndexCallback, this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, }) : assert( - (positionedIndex == 0) || (positionedIndex < itemCount), - 'positionedIndex must be 0 or a value less than itemCount', - ); + (positionedIndex == 0) || (positionedIndex < itemCount), + 'positionedIndex must be 0 or a value less than itemCount', + ); /// Number of items the [itemBuilder] can produce. final int itemCount; @@ -186,81 +186,78 @@ class _PositionedListState extends State { @override Widget build(BuildContext context) => RegistryWidget( - elementNotifier: registeredElements, - child: UnboundedCustomScrollView( - anchor: widget.alignment, - center: _centerKey, - controller: scrollController, - scrollDirection: widget.scrollDirection, - reverse: widget.reverse, - cacheExtent: widget.cacheExtent, - physics: widget.physics, - shrinkWrap: widget.shrinkWrap, - semanticChildCount: widget.semanticChildCount ?? widget.itemCount, - keyboardDismissBehavior: widget.keyboardDismissBehavior, - slivers: [ - if (widget.positionedIndex > 0) - SliverPadding( - padding: _leadingSliverPadding, - sliver: SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) => widget.separatorBuilder == null - ? _buildItem(widget.positionedIndex - (index + 1)) - : _buildSeparatedListElement( - widget.positionedIndex * 2 - (index + 1), - ), - childCount: widget.separatorBuilder == null - ? widget.positionedIndex - : widget.positionedIndex * 2, - addSemanticIndexes: false, - addRepaintBoundaries: widget.addRepaintBoundaries, - addAutomaticKeepAlives: widget.addAutomaticKeepAlives, - findChildIndexCallback: widget.findChildIndexCallback, - ), - ), - ), - SliverPadding( - key: _centerKey, - padding: _centerSliverPadding, - sliver: SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) => widget.separatorBuilder == null - ? _buildItem(index + widget.positionedIndex) - : _buildSeparatedListElement( - index + widget.positionedIndex * 2, - ), - childCount: widget.itemCount != 0 ? 1 : 0, - addSemanticIndexes: false, - addRepaintBoundaries: widget.addRepaintBoundaries, - addAutomaticKeepAlives: widget.addAutomaticKeepAlives, - findChildIndexCallback: widget.findChildIndexCallback, - ), + elementNotifier: registeredElements, + child: UnboundedCustomScrollView( + anchor: widget.alignment, + center: _centerKey, + controller: scrollController, + scrollDirection: widget.scrollDirection, + reverse: widget.reverse, + cacheExtent: widget.cacheExtent, + physics: widget.physics, + shrinkWrap: widget.shrinkWrap, + semanticChildCount: widget.semanticChildCount ?? widget.itemCount, + keyboardDismissBehavior: widget.keyboardDismissBehavior, + slivers: [ + if (widget.positionedIndex > 0) + SliverPadding( + padding: _leadingSliverPadding, + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => widget.separatorBuilder == null + ? _buildItem(widget.positionedIndex - (index + 1)) + : _buildSeparatedListElement( + widget.positionedIndex * 2 - (index + 1), + ), + childCount: widget.separatorBuilder == null ? widget.positionedIndex : widget.positionedIndex * 2, + addSemanticIndexes: false, + addRepaintBoundaries: widget.addRepaintBoundaries, + addAutomaticKeepAlives: widget.addAutomaticKeepAlives, + findChildIndexCallback: widget.findChildIndexCallback, ), ), - if (widget.positionedIndex >= 0 && - widget.positionedIndex < widget.itemCount - 1) - SliverPadding( - padding: _trailingSliverPadding, - sliver: SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) => widget.separatorBuilder == null - ? _buildItem(index + widget.positionedIndex + 1) - : _buildSeparatedListElement( - index + widget.positionedIndex * 2 + 1, - ), - childCount: widget.separatorBuilder == null - ? widget.itemCount - widget.positionedIndex - 1 - : 2 * (widget.itemCount - widget.positionedIndex - 1), - addSemanticIndexes: false, - addRepaintBoundaries: widget.addRepaintBoundaries, - addAutomaticKeepAlives: widget.addAutomaticKeepAlives, - findChildIndexCallback: widget.findChildIndexCallback, - ), - ), - ), - ], + ), + SliverPadding( + key: _centerKey, + padding: _centerSliverPadding, + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => widget.separatorBuilder == null + ? _buildItem(index + widget.positionedIndex) + : _buildSeparatedListElement( + index + widget.positionedIndex * 2, + ), + childCount: widget.itemCount != 0 ? 1 : 0, + addSemanticIndexes: false, + addRepaintBoundaries: widget.addRepaintBoundaries, + addAutomaticKeepAlives: widget.addAutomaticKeepAlives, + findChildIndexCallback: widget.findChildIndexCallback, + ), + ), ), - ); + if (widget.positionedIndex >= 0 && widget.positionedIndex < widget.itemCount - 1) + SliverPadding( + padding: _trailingSliverPadding, + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => widget.separatorBuilder == null + ? _buildItem(index + widget.positionedIndex + 1) + : _buildSeparatedListElement( + index + widget.positionedIndex * 2 + 1, + ), + childCount: widget.separatorBuilder == null + ? widget.itemCount - widget.positionedIndex - 1 + : 2 * (widget.itemCount - widget.positionedIndex - 1), + addSemanticIndexes: false, + addRepaintBoundaries: widget.addRepaintBoundaries, + addAutomaticKeepAlives: widget.addAutomaticKeepAlives, + findChildIndexCallback: widget.findChildIndexCallback, + ), + ), + ), + ], + ), + ); Widget _buildSeparatedListElement(int index) { if (index.isEven) { @@ -274,63 +271,51 @@ class _PositionedListState extends State { final child = widget.itemBuilder(context, index); return RegisteredElementWidget( key: IndexedKey(child.key, index), - child: widget.addSemanticIndexes - ? IndexedSemantics(index: index, child: child) - : child, + child: widget.addSemanticIndexes ? IndexedSemantics(index: index, child: child) : child, ); } EdgeInsets get _leadingSliverPadding => (widget.scrollDirection == Axis.vertical ? widget.reverse - ? widget.padding?.copyWith(top: 0) - : widget.padding?.copyWith(bottom: 0) + ? widget.padding?.copyWith(top: 0) + : widget.padding?.copyWith(bottom: 0) : widget.reverse - ? widget.padding?.copyWith(left: 0) - : widget.padding?.copyWith(right: 0)) ?? + ? widget.padding?.copyWith(left: 0) + : widget.padding?.copyWith(right: 0)) ?? EdgeInsets.zero; EdgeInsets get _centerSliverPadding => widget.scrollDirection == Axis.vertical ? widget.reverse - ? widget.padding?.copyWith( - top: widget.positionedIndex == widget.itemCount - 1 - ? widget.padding!.top - : 0, - bottom: - widget.positionedIndex == 0 ? widget.padding!.bottom : 0, - ) ?? - EdgeInsets.zero - : widget.padding?.copyWith( - top: widget.positionedIndex == 0 ? widget.padding!.top : 0, - bottom: widget.positionedIndex == widget.itemCount - 1 - ? widget.padding!.bottom - : 0, - ) ?? - EdgeInsets.zero + ? widget.padding?.copyWith( + top: widget.positionedIndex == widget.itemCount - 1 ? widget.padding!.top : 0, + bottom: widget.positionedIndex == 0 ? widget.padding!.bottom : 0, + ) ?? + EdgeInsets.zero + : widget.padding?.copyWith( + top: widget.positionedIndex == 0 ? widget.padding!.top : 0, + bottom: widget.positionedIndex == widget.itemCount - 1 ? widget.padding!.bottom : 0, + ) ?? + EdgeInsets.zero : widget.reverse - ? widget.padding?.copyWith( - left: widget.positionedIndex == widget.itemCount - 1 - ? widget.padding!.left - : 0, - right: widget.positionedIndex == 0 ? widget.padding!.right : 0, - ) ?? - EdgeInsets.zero - : widget.padding?.copyWith( - left: widget.positionedIndex == 0 ? widget.padding!.left : 0, - right: widget.positionedIndex == widget.itemCount - 1 - ? widget.padding!.right - : 0, - ) ?? - EdgeInsets.zero; - - EdgeInsets get _trailingSliverPadding => - widget.scrollDirection == Axis.vertical - ? widget.reverse - ? widget.padding?.copyWith(bottom: 0) ?? EdgeInsets.zero - : widget.padding?.copyWith(top: 0) ?? EdgeInsets.zero - : widget.reverse - ? widget.padding?.copyWith(right: 0) ?? EdgeInsets.zero - : widget.padding?.copyWith(left: 0) ?? EdgeInsets.zero; + ? widget.padding?.copyWith( + left: widget.positionedIndex == widget.itemCount - 1 ? widget.padding!.left : 0, + right: widget.positionedIndex == 0 ? widget.padding!.right : 0, + ) ?? + EdgeInsets.zero + : widget.padding?.copyWith( + left: widget.positionedIndex == 0 ? widget.padding!.left : 0, + right: widget.positionedIndex == widget.itemCount - 1 ? widget.padding!.right : 0, + ) ?? + EdgeInsets.zero; + + EdgeInsets get _trailingSliverPadding => widget.scrollDirection == Axis.vertical + ? widget.reverse + ? widget.padding?.copyWith(bottom: 0) ?? EdgeInsets.zero + : widget.padding?.copyWith(top: 0) ?? EdgeInsets.zero + : widget.reverse + ? widget.padding?.copyWith(right: 0) ?? EdgeInsets.zero + : widget.padding?.copyWith(left: 0) ?? EdgeInsets.zero; void _schedulePositionNotificationUpdate() { if (!updateScheduled) { @@ -361,34 +346,34 @@ class _PositionedListState extends State { if (widget.scrollDirection == Axis.vertical) { final reveal = viewport!.getOffsetToReveal(box, 0).offset; if (!reveal.isFinite) continue; - final itemOffset = - reveal - viewport.offset.pixels + anchor * viewport.size.height; - positions.add(ItemPosition( - index: key.index, - itemLeadingEdge: itemOffset.round() / - scrollController.position.viewportDimension, - itemTrailingEdge: (itemOffset + box.size.height).round() / - scrollController.position.viewportDimension, - )); + final itemOffset = reveal - viewport.offset.pixels + anchor * viewport.size.height; + positions.add( + ItemPosition( + index: key.index, + itemLeadingEdge: itemOffset.round() / scrollController.position.viewportDimension, + itemTrailingEdge: (itemOffset + box.size.height).round() / scrollController.position.viewportDimension, + ), + ); } else { - final itemOffset = - box.localToGlobal(Offset.zero, ancestor: viewport).dx; + final itemOffset = box.localToGlobal(Offset.zero, ancestor: viewport).dx; if (!itemOffset.isFinite) continue; - positions.add(ItemPosition( - index: key.index, - itemLeadingEdge: (widget.reverse - ? scrollController.position.viewportDimension - - (itemOffset + box.size.width) - : itemOffset) - .round() / - scrollController.position.viewportDimension, - itemTrailingEdge: (widget.reverse - ? scrollController.position.viewportDimension - - itemOffset - : (itemOffset + box.size.width)) - .round() / - scrollController.position.viewportDimension, - )); + positions.add( + ItemPosition( + index: key.index, + itemLeadingEdge: + (widget.reverse + ? scrollController.position.viewportDimension - (itemOffset + box.size.width) + : itemOffset) + .round() / + scrollController.position.viewportDimension, + itemTrailingEdge: + (widget.reverse + ? scrollController.position.viewportDimension - itemOffset + : (itemOffset + box.size.width)) + .round() / + scrollController.position.viewportDimension, + ), + ); } } widget.itemPositionsNotifier?.itemPositions.value = positions; diff --git a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/scroll_view.dart b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/scroll_view.dart index 911512495b..160e8aa240 100644 --- a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/scroll_view.dart +++ b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/scroll_view.dart @@ -28,9 +28,9 @@ class UnboundedCustomScrollView extends CustomScrollView { super.semanticChildCount, super.dragStartBehavior, super.keyboardDismissBehavior, - }) : _shrinkWrap = shrinkWrap, - _anchor = anchor, - super(shrinkWrap: false); + }) : _shrinkWrap = shrinkWrap, + _anchor = anchor, + super(shrinkWrap: false); final bool _shrinkWrap; diff --git a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/scrollable_positioned_list.dart b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/scrollable_positioned_list.dart index af44d52562..e160dc6148 100644 --- a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/scrollable_positioned_list.dart +++ b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/scrollable_positioned_list.dart @@ -52,8 +52,8 @@ class ScrollablePositionedList extends StatefulWidget { this.minCacheExtent, this.findChildIndexCallback, this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, - }) : itemPositionsNotifier = itemPositionsListener as ItemPositionsNotifier?, - separatorBuilder = null; + }) : itemPositionsNotifier = itemPositionsListener as ItemPositionsNotifier?, + separatorBuilder = null; /// Create a [ScrollablePositionedList] whose items are provided by /// [itemBuilder] and separators provided by [separatorBuilder]. @@ -278,8 +278,7 @@ class ItemScrollController { } } -class _ScrollablePositionedListState extends State - with TickerProviderStateMixin { +class _ScrollablePositionedListState extends State with TickerProviderStateMixin { /// Details for the primary (active) [ListView]. _ListDisplayDetails primary = _ListDisplayDetails(const ValueKey('Ping')); @@ -318,10 +317,8 @@ class _ScrollablePositionedListState extends State @override void dispose() { - primary.itemPositionsNotifier.itemPositions - .removeListener(_updatePositions); - secondary.itemPositionsNotifier.itemPositions - .removeListener(_updatePositions); + primary.itemPositionsNotifier.itemPositions.removeListener(_updatePositions); + secondary.itemPositionsNotifier.itemPositions.removeListener(_updatePositions); _animationController?.dispose(); super.dispose(); } @@ -431,12 +428,9 @@ class _ScrollablePositionedListState extends State } double _cacheExtent(BoxConstraints constraints) => max( - (widget.scrollDirection == Axis.vertical - ? constraints.maxHeight - : constraints.maxWidth) * - _screenScrollCount, - widget.minCacheExtent ?? 0, - ); + (widget.scrollDirection == Axis.vertical ? constraints.maxHeight : constraints.maxWidth) * _screenScrollCount, + widget.minCacheExtent ?? 0, + ); void _jumpTo({required int index, required double alignment}) { _stopScroll(canceled: true); @@ -494,14 +488,12 @@ class _ScrollablePositionedListState extends State required List opacityAnimationWeights, }) async { final direction = index > primary.target ? 1 : -1; - final itemPosition = - primary.itemPositionsNotifier.itemPositions.value.firstWhereOrNull( + final itemPosition = primary.itemPositionsNotifier.itemPositions.value.firstWhereOrNull( (ItemPosition itemPosition) => itemPosition.index == index, ); if (itemPosition != null) { // Scroll directly. - final localScrollAmount = itemPosition.itemLeadingEdge * - primary.scrollController.position.viewportDimension; + final localScrollAmount = itemPosition.itemLeadingEdge * primary.scrollController.position.viewportDimension; await primary.scrollController.animateTo( primary.scrollController.offset + localScrollAmount - @@ -510,31 +502,29 @@ class _ScrollablePositionedListState extends State curve: curve, ); } else { - final scrollAmount = _screenScrollCount * - primary.scrollController.position.viewportDimension; + final scrollAmount = _screenScrollCount * primary.scrollController.position.viewportDimension; final startCompleter = Completer(); final endCompleter = Completer(); startAnimationCallback = () { SchedulerBinding.instance.addPostFrameCallback((_) { startAnimationCallback = () {}; _animationController?.dispose(); - _animationController = - AnimationController(vsync: this, duration: duration)..forward(); - opacity.parent = _opacityAnimation(opacityAnimationWeights) - .animate(_animationController!); - secondary.scrollController.jumpTo(-direction * - (_screenScrollCount * - primary.scrollController.position.viewportDimension - - alignment * - secondary.scrollController.position.viewportDimension)); - - startCompleter.complete(primary.scrollController.animateTo( - primary.scrollController.offset + direction * scrollAmount, - duration: duration, - curve: curve, - )); - endCompleter.complete(secondary.scrollController - .animateTo(0, duration: duration, curve: curve)); + _animationController = AnimationController(vsync: this, duration: duration)..forward(); + opacity.parent = _opacityAnimation(opacityAnimationWeights).animate(_animationController!); + secondary.scrollController.jumpTo( + -direction * + (_screenScrollCount * primary.scrollController.position.viewportDimension - + alignment * secondary.scrollController.position.viewportDimension), + ); + + startCompleter.complete( + primary.scrollController.animateTo( + primary.scrollController.offset + direction * scrollAmount, + duration: duration, + curve: curve, + ), + ); + endCompleter.complete(secondary.scrollController.animateTo(0, duration: duration, curve: curve)); }); }; setState(() { @@ -599,14 +589,13 @@ class _ScrollablePositionedListState extends State } void _updatePositions() { - final itemPositions = primary.itemPositionsNotifier.itemPositions.value - .where((ItemPosition position) => - position.itemLeadingEdge < 1 && position.itemTrailingEdge > 0); + final itemPositions = primary.itemPositionsNotifier.itemPositions.value.where( + (ItemPosition position) => position.itemLeadingEdge < 1 && position.itemTrailingEdge > 0, + ); if (itemPositions.isNotEmpty) { PageStorage.of(context).writeState( context, - itemPositions.reduce((value, element) => - value.itemLeadingEdge < element.itemLeadingEdge ? value : element), + itemPositions.reduce((value, element) => value.itemLeadingEdge < element.itemLeadingEdge ? value : element), ); } widget.itemPositionsNotifier?.itemPositions.value = itemPositions; diff --git a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/viewport.dart b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/viewport.dart index aac9acc9e0..dac5e20f19 100644 --- a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/viewport.dart +++ b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/viewport.dart @@ -38,8 +38,7 @@ class UnboundedViewport extends Viewport { RenderViewport createRenderObject(BuildContext context) { return UnboundedRenderViewport( axisDirection: axisDirection, - crossAxisDirection: crossAxisDirection ?? - Viewport.getDefaultCrossAxisDirection(context, axisDirection), + crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection), anchor: anchor, offset: offset, cacheExtent: cacheExtent, @@ -233,10 +232,8 @@ class UnboundedRenderViewport extends RenderViewport { // to the zero scroll offset (the line between the forward slivers and the // reverse slivers). final centerOffset = mainAxisExtent * anchor - correctedOffset; - final reverseDirectionRemainingPaintExtent = - centerOffset.clamp(0.0, mainAxisExtent); - final forwardDirectionRemainingPaintExtent = - (mainAxisExtent - centerOffset).clamp(0.0, mainAxisExtent); + final reverseDirectionRemainingPaintExtent = centerOffset.clamp(0.0, mainAxisExtent); + final forwardDirectionRemainingPaintExtent = (mainAxisExtent - centerOffset).clamp(0.0, mainAxisExtent); switch (cacheExtentStyle) { case CacheExtentStyle.pixel: @@ -249,10 +246,8 @@ class UnboundedRenderViewport extends RenderViewport { final fullCacheExtent = mainAxisExtent + 2 * _calculatedCacheExtent!; final centerCacheOffset = centerOffset + _calculatedCacheExtent!; - final reverseDirectionRemainingCacheExtent = - centerCacheOffset.clamp(0.0, fullCacheExtent); - final forwardDirectionRemainingCacheExtent = - (fullCacheExtent - centerCacheOffset).clamp(0.0, fullCacheExtent); + final reverseDirectionRemainingCacheExtent = centerCacheOffset.clamp(0.0, fullCacheExtent); + final forwardDirectionRemainingCacheExtent = (fullCacheExtent - centerCacheOffset).clamp(0.0, fullCacheExtent); final leadingNegativeChild = childBefore(center!); @@ -269,8 +264,7 @@ class UnboundedRenderViewport extends RenderViewport { growthDirection: GrowthDirection.reverse, advance: childBefore, remainingCacheExtent: reverseDirectionRemainingCacheExtent, - cacheOrigin: (mainAxisExtent - centerOffset) - .clamp(-_calculatedCacheExtent!, 0.0), + cacheOrigin: (mainAxisExtent - centerOffset).clamp(-_calculatedCacheExtent!, 0.0), ); if (result != 0.0) return -result; } @@ -280,9 +274,7 @@ class UnboundedRenderViewport extends RenderViewport { child: center, scrollOffset: math.max(0, -centerOffset), overlap: leadingNegativeChild == null ? math.min(0, -centerOffset) : 0.0, - layoutOffset: centerOffset >= mainAxisExtent - ? centerOffset - : reverseDirectionRemainingPaintExtent, + layoutOffset: centerOffset >= mainAxisExtent ? centerOffset : reverseDirectionRemainingPaintExtent, remainingPaintExtent: forwardDirectionRemainingPaintExtent, mainAxisExtent: mainAxisExtent, crossAxisExtent: crossAxisExtent, diff --git a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/wrapping.dart b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/wrapping.dart index c3a8129ce7..e746813cae 100644 --- a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/wrapping.dart +++ b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/wrapping.dart @@ -59,8 +59,7 @@ class CustomShrinkWrappingViewport extends CustomViewport { CustomRenderShrinkWrappingViewport createRenderObject(BuildContext context) { return CustomRenderShrinkWrappingViewport( axisDirection: axisDirection, - crossAxisDirection: crossAxisDirection ?? - Viewport.getDefaultCrossAxisDirection(context, axisDirection), + crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection), offset: offset, anchor: anchor, cacheExtent: cacheExtent, @@ -74,8 +73,7 @@ class CustomShrinkWrappingViewport extends CustomViewport { ) { renderObject ..axisDirection = axisDirection - ..crossAxisDirection = crossAxisDirection ?? - Viewport.getDefaultCrossAxisDirection(context, axisDirection) + ..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection) ..anchor = anchor ..offset = offset ..cacheExtent = cacheExtent @@ -267,10 +265,8 @@ class CustomRenderShrinkWrappingViewport extends CustomRenderViewport { final maxScrollOffset = math.max(math.min(0, top), bottom); final minScrollOffset = math.min(top, maxScrollOffset); - final didAcceptViewportDimension = - offset.applyViewportDimension(effectiveExtent); - final didAcceptContentDimension = - offset.applyContentDimensions(minScrollOffset, maxScrollOffset); + final didAcceptViewportDimension = offset.applyViewportDimension(effectiveExtent); + final didAcceptContentDimension = offset.applyContentDimensions(minScrollOffset, maxScrollOffset); if (didAcceptViewportDimension && didAcceptContentDimension) { break; } @@ -278,12 +274,10 @@ class CustomRenderShrinkWrappingViewport extends CustomRenderViewport { } while (true); switch (axis) { case Axis.vertical: - size = - constraints.constrainDimensions(crossAxisExtent, effectiveExtent); + size = constraints.constrainDimensions(crossAxisExtent, effectiveExtent); break; case Axis.horizontal: - size = - constraints.constrainDimensions(effectiveExtent, crossAxisExtent); + size = constraints.constrainDimensions(effectiveExtent, crossAxisExtent); break; } } @@ -313,10 +307,8 @@ class CustomRenderShrinkWrappingViewport extends CustomRenderViewport { // to the zero scroll offset (the line between the forward slivers and the // reverse slivers). final centerOffset = mainAxisExtent * anchor - correctedOffset; - final reverseDirectionRemainingPaintExtent = - centerOffset.clamp(0.0, mainAxisExtent); - final forwardDirectionRemainingPaintExtent = - (mainAxisExtent - centerOffset).clamp(0.0, mainAxisExtent); + final reverseDirectionRemainingPaintExtent = centerOffset.clamp(0.0, mainAxisExtent); + final forwardDirectionRemainingPaintExtent = (mainAxisExtent - centerOffset).clamp(0.0, mainAxisExtent); switch (cacheExtentStyle) { case CacheExtentStyle.pixel: @@ -329,10 +321,8 @@ class CustomRenderShrinkWrappingViewport extends CustomRenderViewport { final fullCacheExtent = mainAxisExtent + 2 * _calculatedCacheExtent!; final centerCacheOffset = centerOffset + _calculatedCacheExtent!; - final reverseDirectionRemainingCacheExtent = - centerCacheOffset.clamp(0.0, fullCacheExtent); - final forwardDirectionRemainingCacheExtent = - (fullCacheExtent - centerCacheOffset).clamp(0.0, fullCacheExtent); + final reverseDirectionRemainingCacheExtent = centerCacheOffset.clamp(0.0, fullCacheExtent); + final forwardDirectionRemainingCacheExtent = (fullCacheExtent - centerCacheOffset).clamp(0.0, fullCacheExtent); final leadingNegativeChild = childBefore(center!); @@ -349,8 +339,7 @@ class CustomRenderShrinkWrappingViewport extends CustomRenderViewport { growthDirection: GrowthDirection.reverse, advance: childBefore, remainingCacheExtent: reverseDirectionRemainingCacheExtent, - cacheOrigin: (mainAxisExtent - centerOffset) - .clamp(-_calculatedCacheExtent!, 0.0), + cacheOrigin: (mainAxisExtent - centerOffset).clamp(-_calculatedCacheExtent!, 0.0), ); if (result != 0.0) return -result; } @@ -360,9 +349,7 @@ class CustomRenderShrinkWrappingViewport extends CustomRenderViewport { child: center, scrollOffset: math.max(0, -centerOffset), overlap: leadingNegativeChild == null ? math.min(0, -centerOffset) : 0.0, - layoutOffset: centerOffset >= mainAxisExtent - ? centerOffset - : reverseDirectionRemainingPaintExtent, + layoutOffset: centerOffset >= mainAxisExtent ? centerOffset : reverseDirectionRemainingPaintExtent, remainingPaintExtent: forwardDirectionRemainingPaintExtent, mainAxisExtent: mainAxisExtent, crossAxisExtent: crossAxisExtent, @@ -450,16 +437,15 @@ abstract class CustomViewport extends MultiChildRenderObjectWidget { this.cacheExtentStyle = CacheExtentStyle.pixel, this.clipBehavior = Clip.hardEdge, List slivers = const [], - }) : assert( - center == null || - slivers.where((Widget child) => child.key == center).length == 1, - 'There should be at most one child with the same key as the center child: $center', - ), - assert( - cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null, - 'A cacheExtent is required when using cacheExtentStyle.viewport', - ), - super(children: slivers); + }) : assert( + center == null || slivers.where((Widget child) => child.key == center).length == 1, + 'There should be at most one child with the same key as the center child: $center', + ), + assert( + cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null, + 'A cacheExtent is required when using cacheExtentStyle.viewport', + ), + super(children: slivers); /// The direction in which the [offset]'s [ViewportOffset.pixels] increases. /// @@ -533,24 +519,24 @@ abstract class CustomViewport extends MultiChildRenderObjectWidget { ) { switch (axisDirection) { case AxisDirection.up: - assert(debugCheckHasDirectionality( - context, - why: - "to determine the cross-axis direction when the viewport has an 'up' axisDirection", - alternative: - "Alternatively, consider specifying the 'crossAxisDirection' argument on the Viewport.", - )); + assert( + debugCheckHasDirectionality( + context, + why: "to determine the cross-axis direction when the viewport has an 'up' axisDirection", + alternative: "Alternatively, consider specifying the 'crossAxisDirection' argument on the Viewport.", + ), + ); return textDirectionToAxisDirection(Directionality.of(context)); case AxisDirection.right: return AxisDirection.down; case AxisDirection.down: - assert(debugCheckHasDirectionality( - context, - why: - "to determine the cross-axis direction when the viewport has a 'down' axisDirection", - alternative: - "Alternatively, consider specifying the 'crossAxisDirection' argument on the Viewport.", - )); + assert( + debugCheckHasDirectionality( + context, + why: "to determine the cross-axis direction when the viewport has a 'down' axisDirection", + alternative: "Alternatively, consider specifying the 'crossAxisDirection' argument on the Viewport.", + ), + ); return textDirectionToAxisDirection(Directionality.of(context)); case AxisDirection.left: return AxisDirection.down; @@ -568,28 +554,34 @@ abstract class CustomViewport extends MultiChildRenderObjectWidget { super.debugFillProperties(properties); properties ..add(EnumProperty('axisDirection', axisDirection)) - ..add(EnumProperty( - 'crossAxisDirection', - crossAxisDirection, - defaultValue: null, - )) + ..add( + EnumProperty( + 'crossAxisDirection', + crossAxisDirection, + defaultValue: null, + ), + ) ..add(DoubleProperty('anchor', anchor)) ..add(DiagnosticsProperty('offset', offset)); if (center != null) { properties.add(DiagnosticsProperty('center', center)); } else if (children.isNotEmpty && children.first.key != null) { - properties.add(DiagnosticsProperty( - 'center', - children.first.key, - tooltip: 'implicit', - )); + properties.add( + DiagnosticsProperty( + 'center', + children.first.key, + tooltip: 'implicit', + ), + ); } properties ..add(DiagnosticsProperty('cacheExtent', cacheExtent)) - ..add(DiagnosticsProperty( - 'cacheExtentStyle', - cacheExtentStyle, - )); + ..add( + DiagnosticsProperty( + 'cacheExtentStyle', + cacheExtentStyle, + ), + ); } } @@ -601,8 +593,7 @@ class _ViewportElement extends MultiChildRenderObjectElement { CustomViewport get widget => super.widget as CustomViewport; @override - CustomRenderViewport get renderObject => - super.renderObject as CustomRenderViewport; + CustomRenderViewport get renderObject => super.renderObject as CustomRenderViewport; @override void mount(Element? parent, dynamic newSlot) { @@ -618,9 +609,8 @@ class _ViewportElement extends MultiChildRenderObjectElement { void _updateCenter() { if (widget.center != null) { - renderObject.center = children - .singleWhere((Element element) => element.widget.key == widget.center) - .renderObject as RenderSliver?; + renderObject.center = + children.singleWhere((Element element) => element.widget.key == widget.center).renderObject as RenderSliver?; } else if (children.isNotEmpty) { renderObject.center = children.first.renderObject as RenderSliver?; } else { @@ -630,15 +620,16 @@ class _ViewportElement extends MultiChildRenderObjectElement { @override void debugVisitOnstageChildren(ElementVisitor visitor) { - children.where((Element e) { - final renderSliver = e.renderObject! as RenderSliver; - return renderSliver.geometry!.visible; - }).forEach(visitor); + children + .where((Element e) { + final renderSliver = e.renderObject! as RenderSliver; + return renderSliver.geometry!.visible; + }) + .forEach(visitor); } } -class CustomSliverPhysicalContainerParentData - extends SliverPhysicalContainerParentData { +class CustomSliverPhysicalContainerParentData extends SliverPhysicalContainerParentData { /// The position of the child relative to the zero scroll offset. /// /// The number of pixels from from the zero scroll offset of the parent sliver @@ -685,8 +676,7 @@ class CustomSliverPhysicalContainerParentData /// placed inside a [RenderSliver] (the opposite of this class). /// * [RenderShrinkWrappingViewport], a variant of [RenderViewport] that /// shrink-wraps its contents along the main axis. -abstract class CustomRenderViewport - extends RenderViewportBase { +abstract class CustomRenderViewport extends RenderViewportBase { /// Creates a viewport for [RenderSliver] objects. /// /// If the [center] is not specified, then the first child in the `children` @@ -704,15 +694,15 @@ abstract class CustomRenderViewport super.cacheExtent, super.cacheExtentStyle, super.clipBehavior, - }) : assert( - anchor >= 0.0 && anchor <= 1.0, - 'Anchor must be between 0.0 and 1.0.', - ), - assert( - cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null, - 'A cacheExtent is required when using CacheExtentStyle.viewport.', - ), - _center = center { + }) : assert( + anchor >= 0.0 && anchor <= 1.0, + 'Anchor must be between 0.0 and 1.0.', + ), + assert( + cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null, + 'A cacheExtent is required when using CacheExtentStyle.viewport.', + ), + _center = center { addAll(children); if (center == null && firstChild != null) _center = firstChild; } @@ -735,8 +725,7 @@ abstract class CustomRenderViewport /// /// * [RenderViewportBase.describeSemanticsConfiguration], which adds this /// tag to its [SemanticsConfiguration]. - static const SemanticsTag useTwoPaneSemantics = - SemanticsTag('RenderViewport.twoPane'); + static const SemanticsTag useTwoPaneSemantics = SemanticsTag('RenderViewport.twoPane'); /// When a top-level [SemanticsNode] below a [RenderAbstractViewport] is /// tagged with [excludeFromScrolling] it will not be part of the scrolling @@ -751,8 +740,7 @@ abstract class CustomRenderViewport /// bar) can tag its [SemanticsNode] with [excludeFromScrolling] to indicate /// that it should no longer be considered for semantic actions related to /// scrolling. - static const SemanticsTag excludeFromScrolling = - SemanticsTag('RenderViewport.excludeFromScrolling'); + static const SemanticsTag excludeFromScrolling = SemanticsTag('RenderViewport.excludeFromScrolling'); @override void setupParentData(RenderObject child) { @@ -900,8 +888,7 @@ abstract class CustomRenderViewport double layoutOffset, GrowthDirection growthDirection, ) { - final childParentData = - child.parentData! as CustomSliverPhysicalContainerParentData; + final childParentData = child.parentData! as CustomSliverPhysicalContainerParentData; childParentData ..layoutOffset = layoutOffset ..growthDirection = growthDirection; @@ -909,8 +896,7 @@ abstract class CustomRenderViewport @override Offset paintOffsetOf(RenderSliver child) { - final childParentData = - child.parentData! as CustomSliverPhysicalContainerParentData; + final childParentData = child.parentData! as CustomSliverPhysicalContainerParentData; return computeAbsolutePaintOffset( child, childParentData.layoutOffset!, @@ -983,8 +969,7 @@ abstract class CustomRenderViewport RenderSliver child, double parentMainAxisPosition, ) { - final childParentData = - child.parentData! as CustomSliverPhysicalContainerParentData; + final childParentData = child.parentData! as CustomSliverPhysicalContainerParentData; switch (applyGrowthDirectionToAxisDirection( child.constraints.axisDirection, child.constraints.growthDirection, @@ -993,11 +978,9 @@ abstract class CustomRenderViewport case AxisDirection.right: return parentMainAxisPosition - childParentData.layoutOffset!; case AxisDirection.up: - return (size.height - parentMainAxisPosition) - - childParentData.layoutOffset!; + return (size.height - parentMainAxisPosition) - childParentData.layoutOffset!; case AxisDirection.left: - return (size.width - parentMainAxisPosition) - - childParentData.layoutOffset!; + return (size.width - parentMainAxisPosition) - childParentData.layoutOffset!; } } diff --git a/packages/stream_chat_flutter/lib/src/ai_assistant/ai_typing_indicator_view.dart b/packages/stream_chat_flutter/lib/src/ai_assistant/ai_typing_indicator_view.dart index ebafb3ef2a..288cedf09b 100644 --- a/packages/stream_chat_flutter/lib/src/ai_assistant/ai_typing_indicator_view.dart +++ b/packages/stream_chat_flutter/lib/src/ai_assistant/ai_typing_indicator_view.dart @@ -145,25 +145,25 @@ class _AnimatedDot extends StatefulWidget { State<_AnimatedDot> createState() => _AnimatedDotState(); } -class _AnimatedDotState extends State<_AnimatedDot> - with SingleTickerProviderStateMixin<_AnimatedDot> { +class _AnimatedDotState extends State<_AnimatedDot> with SingleTickerProviderStateMixin<_AnimatedDot> { late final AnimationController _repeatingController; @override void initState() { super.initState(); - _repeatingController = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 800), - )..addStatusListener( - (status) { - if (status == AnimationStatus.completed) { - if (mounted) _repeatingController.reverse(); - } else if (status == AnimationStatus.dismissed) { - if (mounted) _repeatingController.forward(); - } - }, - ); + _repeatingController = + AnimationController( + vsync: this, + duration: const Duration(milliseconds: 800), + )..addStatusListener( + (status) { + if (status == AnimationStatus.completed) { + if (mounted) _repeatingController.reverse(); + } else if (status == AnimationStatus.dismissed) { + if (mounted) _repeatingController.forward(); + } + }, + ); Future.delayed( Duration(milliseconds: 200 * widget.index), diff --git a/packages/stream_chat_flutter/lib/src/ai_assistant/stream_typewriter_builder.dart b/packages/stream_chat_flutter/lib/src/ai_assistant/stream_typewriter_builder.dart index e004784bf6..f686766004 100644 --- a/packages/stream_chat_flutter/lib/src/ai_assistant/stream_typewriter_builder.dart +++ b/packages/stream_chat_flutter/lib/src/ai_assistant/stream_typewriter_builder.dart @@ -54,9 +54,7 @@ class TypewriterValue { @override bool operator ==(Object other) { if (identical(this, other)) return true; - return other is TypewriterValue && - other.text == text && - other.state == state; + return other is TypewriterValue && other.text == text && other.state == state; } @override @@ -205,11 +203,12 @@ class TypewriterController extends ValueNotifier { /// A widget builder for a [StreamTypewriterBuilder]. It allows you to build a /// widget depending on the [TypewriterValue]'s value. /// {@endtemplate} -typedef TypewriterWidgetBuilder = Widget Function( - BuildContext context, - TypewriterValue value, - Widget? child, -); +typedef TypewriterWidgetBuilder = + Widget Function( + BuildContext context, + TypewriterValue value, + Widget? child, + ); /// {@template streamTypewriterBuilder} /// A widget that listens to a [TypewriterController] and rebuilds whenever the diff --git a/packages/stream_chat_flutter/lib/src/ai_assistant/streaming_message_view.dart b/packages/stream_chat_flutter/lib/src/ai_assistant/streaming_message_view.dart index 4422b5d86a..0a81884920 100644 --- a/packages/stream_chat_flutter/lib/src/ai_assistant/streaming_message_view.dart +++ b/packages/stream_chat_flutter/lib/src/ai_assistant/streaming_message_view.dart @@ -82,8 +82,8 @@ class _StreamingMessageViewState extends State { onTapLink: switch (widget.onTapLink) { final onTapLink? => onTapLink, _ => (String link, String? href, String title) { - if (href != null) launchURL(context, href); - }, + if (href != null) launchURL(context, href); + }, }, ); } diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart index dcb6a47a08..adbcbe84d1 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart @@ -44,7 +44,8 @@ class StreamAttachmentUploadStateBuilder extends StatelessWidget { final messageId = message.id; final attachmentId = attachment.id; - final inProgress = inProgressBuilder ?? + final inProgress = + inProgressBuilder ?? (context, int sent, int total) { return _InProgressState( sent: sent, @@ -53,7 +54,8 @@ class StreamAttachmentUploadStateBuilder extends StatelessWidget { ); }; - final failed = failedBuilder ?? + final failed = + failedBuilder ?? (context, error) { return _FailedState( error: error, @@ -64,8 +66,7 @@ class StreamAttachmentUploadStateBuilder extends StatelessWidget { final success = successBuilder ?? (context) => _SuccessState(); - final preparing = preparingBuilder ?? - (context) => _PreparingState(attachmentId: attachmentId); + final preparing = preparingBuilder ?? (context) => _PreparingState(attachmentId: attachmentId); return attachment.uploadState.when( preparing: () => preparing(context), diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment_widget_catalog.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment_widget_catalog.dart index 78c0ba1f52..abd9f0e441 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_widget_catalog.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_widget_catalog.dart @@ -52,8 +52,11 @@ class AttachmentWidgetCatalog { extension on List { /// Groups the attachments by their type. Map> get grouped { - return groupBy(where((it) { - return it.type != null; - }), (attachment) => attachment.type!); + return groupBy( + where((it) { + return it.type != null; + }), + (attachment) => attachment.type!, + ); } } diff --git a/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart index 24c1df2a5e..3bada795e5 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart @@ -16,10 +16,11 @@ part 'poll_attachment_builder.dart'; /// {@template streamAttachmentWidgetTapCallback} /// Signature for a function that's called when the user taps on an attachment. /// {@endtemplate} -typedef StreamAttachmentWidgetTapCallback = void Function( - Message message, - Attachment attachment, -); +typedef StreamAttachmentWidgetTapCallback = + void Function( + Message message, + Attachment attachment, + ); /// {@template attachmentWidgetBuilder} /// A builder which is used to build a widget for a given [Message] and diff --git a/packages/stream_chat_flutter/lib/src/attachment/builder/mixed_attachment_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/builder/mixed_attachment_builder.dart index 121c78e707..5e272ad1eb 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/builder/mixed_attachment_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/builder/mixed_attachment_builder.dart @@ -14,35 +14,34 @@ class MixedAttachmentBuilder extends StreamAttachmentWidgetBuilder { MixedAttachmentBuilder({ this.padding = const EdgeInsets.all(4), StreamAttachmentWidgetTapCallback? onAttachmentTap, - }) : _imageAttachmentBuilder = ImageAttachmentBuilder( - padding: EdgeInsets.zero, - onAttachmentTap: onAttachmentTap, - ), - _videoAttachmentBuilder = VideoAttachmentBuilder( - padding: EdgeInsets.zero, - onAttachmentTap: onAttachmentTap, - ), - _giphyAttachmentBuilder = GiphyAttachmentBuilder( - padding: EdgeInsets.zero, - onAttachmentTap: onAttachmentTap, - ), - _galleryAttachmentBuilder = GalleryAttachmentBuilder( - padding: EdgeInsets.zero, - onAttachmentTap: onAttachmentTap, - ), - _fileAttachmentBuilder = FileAttachmentBuilder( - padding: EdgeInsets.zero, - onAttachmentTap: onAttachmentTap, - ), - _urlAttachmentBuilder = UrlAttachmentBuilder( - padding: EdgeInsets.zero, - onAttachmentTap: onAttachmentTap, - ), - _voiceRecordingAttachmentPlaylistBuilder = - VoiceRecordingAttachmentPlaylistBuilder( - padding: EdgeInsets.zero, - onAttachmentTap: onAttachmentTap, - ); + }) : _imageAttachmentBuilder = ImageAttachmentBuilder( + padding: EdgeInsets.zero, + onAttachmentTap: onAttachmentTap, + ), + _videoAttachmentBuilder = VideoAttachmentBuilder( + padding: EdgeInsets.zero, + onAttachmentTap: onAttachmentTap, + ), + _giphyAttachmentBuilder = GiphyAttachmentBuilder( + padding: EdgeInsets.zero, + onAttachmentTap: onAttachmentTap, + ), + _galleryAttachmentBuilder = GalleryAttachmentBuilder( + padding: EdgeInsets.zero, + onAttachmentTap: onAttachmentTap, + ), + _fileAttachmentBuilder = FileAttachmentBuilder( + padding: EdgeInsets.zero, + onAttachmentTap: onAttachmentTap, + ), + _urlAttachmentBuilder = UrlAttachmentBuilder( + padding: EdgeInsets.zero, + onAttachmentTap: onAttachmentTap, + ), + _voiceRecordingAttachmentPlaylistBuilder = VoiceRecordingAttachmentPlaylistBuilder( + padding: EdgeInsets.zero, + onAttachmentTap: onAttachmentTap, + ); /// The padding to apply to the mixed attachment widget. final EdgeInsetsGeometry padding; @@ -53,8 +52,7 @@ class MixedAttachmentBuilder extends StreamAttachmentWidgetBuilder { late final StreamAttachmentWidgetBuilder _galleryAttachmentBuilder; late final StreamAttachmentWidgetBuilder _fileAttachmentBuilder; late final StreamAttachmentWidgetBuilder _urlAttachmentBuilder; - late final StreamAttachmentWidgetBuilder - _voiceRecordingAttachmentPlaylistBuilder; + late final StreamAttachmentWidgetBuilder _voiceRecordingAttachmentPlaylistBuilder; @override bool canHandle( diff --git a/packages/stream_chat_flutter/lib/src/attachment/builder/url_attachment_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/builder/url_attachment_builder.dart index 58c85fe15b..b8f9c40f7a 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/builder/url_attachment_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/builder/url_attachment_builder.dart @@ -52,9 +52,7 @@ class UrlAttachmentBuilder extends StreamAttachmentWidgetBuilder { final isMyMessage = message.user?.id == client.state.currentUser?.id; final streamChatTheme = StreamChatTheme.of(context); - final messageTheme = isMyMessage - ? streamChatTheme.ownMessageTheme - : streamChatTheme.otherMessageTheme; + final messageTheme = isMyMessage ? streamChatTheme.ownMessageTheme : streamChatTheme.otherMessageTheme; Widget _buildUrlPreview(Attachment urlPreview) { VoidCallback? onTap; @@ -65,9 +63,8 @@ class UrlAttachmentBuilder extends StreamAttachmentWidgetBuilder { final host = Uri.parse(urlPreview.titleLink!).host; final splitList = host.split('.'); final hostName = splitList.length == 3 ? splitList[1] : splitList[0]; - final hostDisplayName = urlPreview.authorName?.sentenceCase ?? - getWebsiteName(hostName.toLowerCase()) ?? - hostName.sentenceCase; + final hostDisplayName = + urlPreview.authorName?.sentenceCase ?? getWebsiteName(hostName.toLowerCase()) ?? hostName.sentenceCase; return InkWell( onTap: onTap, diff --git a/packages/stream_chat_flutter/lib/src/attachment/builder/voice_recording_attachment_playlist_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/builder/voice_recording_attachment_playlist_builder.dart index f53fea8642..220b3d7ed6 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/builder/voice_recording_attachment_playlist_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/builder/voice_recording_attachment_playlist_builder.dart @@ -9,8 +9,7 @@ part of 'attachment_widget_builder.dart'; /// The widget is built when the message has at least one voice recording /// attachment. /// {@endtemplate} -class VoiceRecordingAttachmentPlaylistBuilder - extends StreamAttachmentWidgetBuilder { +class VoiceRecordingAttachmentPlaylistBuilder extends StreamAttachmentWidgetBuilder { /// {@macro voiceRecordingAttachmentPlaylistBuilder} const VoiceRecordingAttachmentPlaylistBuilder({ this.shape, diff --git a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart index a6cf3a04a0..9d107ff338 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart @@ -59,7 +59,8 @@ class StreamFileAttachment extends StatelessWidget { final colorTheme = chatTheme.colorTheme; final backgroundColor = this.backgroundColor ?? colorTheme.barsBg; - final shape = this.shape ?? + final shape = + this.shape ?? RoundedRectangleBorder( side: BorderSide( color: colorTheme.borders, @@ -103,7 +104,8 @@ class StreamFileAttachment extends StatelessWidget { const SizedBox(width: 8), Material( type: MaterialType.transparency, - child: trailing ?? + child: + trailing ?? _Trailing( attachment: file, message: message, diff --git a/packages/stream_chat_flutter/lib/src/attachment/gallery_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/gallery_attachment.dart index 22cbf84265..51d457acbd 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/gallery_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/gallery_attachment.dart @@ -72,7 +72,8 @@ class StreamGalleryAttachment extends StatelessWidget { final chatTheme = StreamChatTheme.of(context); final colorTheme = chatTheme.colorTheme; - final shape = this.shape ?? + final shape = + this.shape ?? RoundedRectangleBorder( side: BorderSide( color: colorTheme.borders, @@ -218,8 +219,7 @@ class StreamGalleryAttachment extends StatelessWidget { ); } - Widget _buildForFourOrMore( - BuildContext context, List attachments) { + Widget _buildForFourOrMore(BuildContext context, List attachments) { final pattern = >[]; final children = []; diff --git a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart index e370cd00bf..8c1690b812 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart @@ -58,7 +58,8 @@ class StreamGiphyAttachment extends StatelessWidget { final chatTheme = StreamChatTheme.of(context); final colorTheme = chatTheme.colorTheme; - final shape = this.shape ?? + final shape = + this.shape ?? RoundedRectangleBorder( side: BorderSide( color: colorTheme.borders, diff --git a/packages/stream_chat_flutter/lib/src/attachment/handler/common.dart b/packages/stream_chat_flutter/lib/src/attachment/handler/common.dart index 8c58fe2dc3..256c8ac6da 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/handler/common.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/handler/common.dart @@ -78,8 +78,7 @@ Future downloadAttachmentData( queryParameters: queryParameters, cancelToken: cancelToken, // set responseType to `bytes` - options: options?.copyWith(responseType: ResponseType.bytes) ?? - Options(responseType: ResponseType.bytes), + options: options?.copyWith(responseType: ResponseType.bytes) ?? Options(responseType: ResponseType.bytes), ); final bytes = Uint8List.fromList(response.data!); diff --git a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart index 3a9de9dde3..ef3568b165 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart @@ -11,8 +11,7 @@ class StreamAttachmentHandler extends StreamAttachmentHandlerBase { /// Returns the singleton instance of [StreamAttachmentHandler]. // ignore: prefer_constructors_over_static_methods - static StreamAttachmentHandler get instance => - _instance ??= StreamAttachmentHandler._(); + static StreamAttachmentHandler get instance => _instance ??= StreamAttachmentHandler._(); late final _filePicker = FilePicker.platform; diff --git a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart index 8e209ea8a2..8ad12ddd3e 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart @@ -64,8 +64,7 @@ class StreamAttachmentHandler extends StreamAttachmentHandlerBase { /// Returns the singleton instance of [StreamAttachmentHandler]. // ignore: prefer_constructors_over_static_methods - static StreamAttachmentHandler get instance => - _instance ??= StreamAttachmentHandler._(); + static StreamAttachmentHandler get instance => _instance ??= StreamAttachmentHandler._(); late final _imagePicker = ImagePicker(); late final _filePicker = FilePicker.platform; @@ -140,9 +139,7 @@ class StreamAttachmentHandler extends StreamAttachmentHandlerBase { final tempDir = await getTemporaryDirectory(); final tempPath = Uri.file(tempDir.path, windows: CurrentPlatform.isWindows); - final tempFilePath = tempPath - .resolve(fileName!) - .toFilePath(windows: CurrentPlatform.isWindows); + final tempFilePath = tempPath.resolve(fileName!).toFilePath(windows: CurrentPlatform.isWindows); final attachmentFileBytes = attachmentFile.bytes; if (attachmentFileBytes == null) { diff --git a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart index 5a3837b487..7b82779f5a 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart @@ -62,7 +62,8 @@ class StreamImageAttachment extends StatelessWidget { final chatTheme = StreamChatTheme.of(context); final colorTheme = chatTheme.colorTheme; - final shape = this.shape ?? + final shape = + this.shape ?? RoundedRectangleBorder( side: BorderSide( color: colorTheme.borders, diff --git a/packages/stream_chat_flutter/lib/src/attachment/poll_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/poll_attachment.dart index 231d4ad455..a176bcfa64 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/poll_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/poll_attachment.dart @@ -62,7 +62,8 @@ class _PollAttachmentState extends State { Widget build(BuildContext context) { final theme = StreamChatTheme.of(context); - final shape = widget.shape ?? + final shape = + widget.shape ?? RoundedRectangleBorder( side: BorderSide( color: theme.colorTheme.borders, diff --git a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/file_attachment_thumbnail.dart b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/file_attachment_thumbnail.dart index b6f14b291c..a3b8472cb8 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/file_attachment_thumbnail.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/file_attachment_thumbnail.dart @@ -54,17 +54,17 @@ class StreamFileAttachmentThumbnail extends StatelessWidget { return switch (mediaType?.type) { AttachmentType.image => StreamImageAttachmentThumbnail( - image: file, - width: width, - height: height, - fit: fit, - ), + image: file, + width: width, + height: height, + fit: fit, + ), AttachmentType.video => StreamVideoAttachmentThumbnail( - video: file, - width: width, - height: height, - fit: fit, - ), + video: file, + width: width, + height: height, + fit: fit, + ), // Return a generic file type icon. _ => getFileTypeImage(mediaType?.mimeType), }; diff --git a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/image_attachment_thumbnail.dart b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/image_attachment_thumbnail.dart index 55474beae4..cbc7d8f1c9 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/image_attachment_thumbnail.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/image_attachment_thumbnail.dart @@ -79,10 +79,10 @@ class StreamImageAttachmentThumbnail extends StatelessWidget { final effectiveThumbnailSize = switch (thumbnailSize) { final thumbnailSize? => thumbnailSize, _ => ThumbnailSizeCalculator.calculate( - targetSize: constraints.biggest, - originalSize: image.originalSize, - pixelRatio: MediaQuery.devicePixelRatioOf(context), - ), + targetSize: constraints.biggest, + originalSize: image.originalSize, + pixelRatio: MediaQuery.devicePixelRatioOf(context), + ), }; final cacheWidth = effectiveThumbnailSize?.width.round(); diff --git a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/thumbnail_error.dart b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/thumbnail_error.dart index d5f5a4ae56..4ed0b4ed39 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/thumbnail_error.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/thumbnail_error.dart @@ -6,11 +6,12 @@ import 'package:flutter/material.dart'; /// The parameters represent the [BuildContext], [error] and [stackTrace] of the /// error that triggered this callback. /// {@endtemplate} -typedef ThumbnailErrorBuilder = Widget Function( - BuildContext context, - Object error, - StackTrace? stackTrace, -); +typedef ThumbnailErrorBuilder = + Widget Function( + BuildContext context, + Object error, + StackTrace? stackTrace, + ); /// {@template thumbnailError} /// A widget that shows an error state when a thumbnail fails to load. diff --git a/packages/stream_chat_flutter/lib/src/attachment/url_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/url_attachment.dart index 6d8629ea4c..31a018a316 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/url_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/url_attachment.dart @@ -40,7 +40,8 @@ class StreamUrlAttachment extends StatelessWidget { Widget build(BuildContext context) { final chatTheme = StreamChatTheme.of(context); final colorTheme = chatTheme.colorTheme; - final shape = this.shape ?? + final shape = + this.shape ?? RoundedRectangleBorder( side: BorderSide( color: colorTheme.borders, @@ -105,37 +106,41 @@ class StreamUrlAttachment extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (urlAttachment.title != null) - Builder(builder: (context) { - final maxLines = messageTheme.urlAttachmentTitleMaxLine; + Builder( + builder: (context) { + final maxLines = messageTheme.urlAttachmentTitleMaxLine; - TextOverflow? overflow; - if (maxLines != null && maxLines > 0) { - overflow = TextOverflow.ellipsis; - } + TextOverflow? overflow; + if (maxLines != null && maxLines > 0) { + overflow = TextOverflow.ellipsis; + } - return Text( - urlAttachment.title!.trim(), - maxLines: maxLines, - overflow: overflow, - style: messageTheme.urlAttachmentTitleStyle, - ); - }), + return Text( + urlAttachment.title!.trim(), + maxLines: maxLines, + overflow: overflow, + style: messageTheme.urlAttachmentTitleStyle, + ); + }, + ), if (urlAttachment.text != null) - Builder(builder: (context) { - final maxLines = messageTheme.urlAttachmentTextMaxLine; + Builder( + builder: (context) { + final maxLines = messageTheme.urlAttachmentTextMaxLine; - TextOverflow? overflow; - if (maxLines != null && maxLines > 0) { - overflow = TextOverflow.ellipsis; - } + TextOverflow? overflow; + if (maxLines != null && maxLines > 0) { + overflow = TextOverflow.ellipsis; + } - return Text( - urlAttachment.text!, - maxLines: maxLines, - overflow: overflow, - style: messageTheme.urlAttachmentTextStyle, - ); - }), + return Text( + urlAttachment.text!, + maxLines: maxLines, + overflow: overflow, + style: messageTheme.urlAttachmentTextStyle, + ); + }, + ), ], ), ), diff --git a/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart index a79d44c77f..b36d49ef0e 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart @@ -32,7 +32,8 @@ class StreamVideoAttachment extends StatelessWidget { Widget build(BuildContext context) { final chatTheme = StreamChatTheme.of(context); final colorTheme = chatTheme.colorTheme; - final shape = this.shape ?? + final shape = + this.shape ?? RoundedRectangleBorder( side: BorderSide( color: colorTheme.borders, diff --git a/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment.dart index 3681d30939..2a1e6e6d68 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment.dart @@ -11,12 +11,13 @@ const _kDefaultWaveformHeight = 28.0; /// /// Provides a flexible way to customize the trailing section of the /// voice recording player based on the current track and playback state. -typedef StreamVoiceRecordingAttachmentTrailingWidgetBuilder = Widget Function( - BuildContext context, - PlaylistTrack track, - PlaybackSpeed speed, - ValueChanged? onChangeSpeed, -); +typedef StreamVoiceRecordingAttachmentTrailingWidgetBuilder = + Widget Function( + BuildContext context, + PlaylistTrack track, + PlaybackSpeed speed, + ValueChanged? onChangeSpeed, + ); /// {@template streamVoiceRecordingAttachment} /// An embedded audio player for voice recordings with comprehensive playback @@ -102,9 +103,9 @@ class StreamVoiceRecordingAttachment extends StatelessWidget { duration: const Duration(milliseconds: 200), child: switch (track.state.isPlaying) { true => SpeedControlButton( - speed: speed, - onChangeSpeed: onChangeSpeed, - ), + speed: speed, + onChangeSpeed: onChangeSpeed, + ), false => getFileTypeImage(track.title?.mediaType?.mimeType), }, ); @@ -116,7 +117,8 @@ class StreamVoiceRecordingAttachment extends StatelessWidget { final waveformSliderTheme = theme.audioWaveformSliderTheme; final waveformTheme = waveformSliderTheme?.audioWaveformTheme; - final shape = this.shape ?? + final shape = + this.shape ?? RoundedRectangleBorder( side: BorderSide( color: StreamChatTheme.of(context).colorTheme.borders, @@ -181,8 +183,7 @@ class StreamVoiceRecordingAttachment extends StatelessWidget { spacingRatio: waveformTheme?.spacingRatio, heightScale: waveformTheme?.heightScale, thumbColor: waveformSliderTheme?.thumbColor, - thumbBorderColor: - waveformSliderTheme?.thumbBorderColor, + thumbBorderColor: waveformSliderTheme?.thumbBorderColor, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment_playlist.dart b/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment_playlist.dart index 2bd55a8023..53f77a07f6 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment_playlist.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment_playlist.dart @@ -56,12 +56,10 @@ class StreamVoiceRecordingAttachmentPlaylist extends StatefulWidget { } @override - State createState() => - _StreamVoiceRecordingAttachmentPlaylistState(); + State createState() => _StreamVoiceRecordingAttachmentPlaylistState(); } -class _StreamVoiceRecordingAttachmentPlaylistState - extends State { +class _StreamVoiceRecordingAttachmentPlaylistState extends State { late final _controller = StreamAudioPlaylistController( widget.voiceRecordings.toPlaylist(), ); diff --git a/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart b/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart index c36963a7aa..c87daabbdd 100644 --- a/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart @@ -105,148 +105,149 @@ class AttachmentActionsModal extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.end, mainAxisSize: MainAxisSize.min, - children: [ - if (showReply) - _buildButton( - context, - context.translations.replyLabel, - StreamSvgIcon( - size: 24, - icon: StreamSvgIcons.reply, - color: theme.colorTheme.textLowEmphasis, - ), - onReply, - ), - if (showShowInChat) - _buildButton( - context, - context.translations.showInChatLabel, - StreamSvgIcon( - size: 24, - icon: StreamSvgIcons.eye, - color: theme.colorTheme.textHighEmphasis, - ), - onShowMessage, - ), - if (showSave) - _buildButton( - context, - attachment.type == AttachmentType.video - ? context.translations.saveVideoLabel - : context.translations.saveImageLabel, - StreamSvgIcon( - size: 24, - icon: StreamSvgIcons.save, - color: theme.colorTheme.textLowEmphasis, - ), - () { - // Closing attachment actions modal before opening - // attachment download dialog - Navigator.of(context).pop(); - - final downloader = attachmentDownloader ?? - StreamAttachmentHandler.instance.downloadAttachment; - - // No need to show progress dialog in case of - // web or desktop. - if (isDesktopDeviceOrWeb) { - downloader(attachment); - return; - } - - final progressNotifier = - ValueNotifier<_DownloadProgress?>( - _DownloadProgress.initial(), - ); - - final downloadedPathNotifier = - ValueNotifier(null); - - downloader( - attachment, - onReceiveProgress: (received, total) { - progressNotifier.value = _DownloadProgress( - total, - received, - ); - }, - ).then((path) { - downloadedPathNotifier.value = path; - }).catchError((e, stk) { - print(e); - print(stk); - progressNotifier.value = null; - }); - - showDialog( - barrierDismissible: false, - context: context, - barrierColor: theme.colorTheme.overlay, - builder: (context) => _buildDownloadProgressDialog( - context, - progressNotifier, - downloadedPathNotifier, + children: + [ + if (showReply) + _buildButton( + context, + context.translations.replyLabel, + StreamSvgIcon( + size: 24, + icon: StreamSvgIcons.reply, + color: theme.colorTheme.textLowEmphasis, + ), + onReply, + ), + if (showShowInChat) + _buildButton( + context, + context.translations.showInChatLabel, + StreamSvgIcon( + size: 24, + icon: StreamSvgIcons.eye, + color: theme.colorTheme.textHighEmphasis, + ), + onShowMessage, + ), + if (showSave) + _buildButton( + context, + attachment.type == AttachmentType.video + ? context.translations.saveVideoLabel + : context.translations.saveImageLabel, + StreamSvgIcon( + size: 24, + icon: StreamSvgIcons.save, + color: theme.colorTheme.textLowEmphasis, + ), + () { + // Closing attachment actions modal before opening + // attachment download dialog + Navigator.of(context).pop(); + + final downloader = + attachmentDownloader ?? StreamAttachmentHandler.instance.downloadAttachment; + + // No need to show progress dialog in case of + // web or desktop. + if (isDesktopDeviceOrWeb) { + downloader(attachment); + return; + } + + final progressNotifier = ValueNotifier<_DownloadProgress?>( + _DownloadProgress.initial(), + ); + + final downloadedPathNotifier = ValueNotifier(null); + + downloader( + attachment, + onReceiveProgress: (received, total) { + progressNotifier.value = _DownloadProgress( + total, + received, + ); + }, + ) + .then((path) { + downloadedPathNotifier.value = path; + }) + .catchError((e, stk) { + print(e); + print(stk); + progressNotifier.value = null; + }); + + showDialog( + barrierDismissible: false, + context: context, + barrierColor: theme.colorTheme.overlay, + builder: (context) => _buildDownloadProgressDialog( + context, + progressNotifier, + downloadedPathNotifier, + ), + ); + }, + ), + if (StreamChat.of(context).currentUser?.id == message.user?.id && showDelete) + _buildButton( + context, + context.translations.deleteLabel.sentenceCase, + StreamSvgIcon( + size: 24, + icon: StreamSvgIcons.delete, + color: theme.colorTheme.accentError, + ), + () { + final channel = StreamChannel.of(context).channel; + if (message.attachments.length > 1 || message.text?.isNotEmpty == true) { + final currentAttachmentIndex = message.attachments.indexWhere( + (element) => element.id == attachment.id, + ); + final remainingAttachments = [...message.attachments] + ..removeAt(currentAttachmentIndex); + channel.updateMessage( + message.copyWith( + attachments: remainingAttachments, + ), + ); + Navigator.of(context) + ..pop() + ..maybePop(); + } else { + channel.deleteMessage(message); + Navigator.of(context) + ..pop() + ..maybePop(); + } + }, + color: theme.colorTheme.accentError, + ), + ...customActions + .map( + (e) => _buildButton( + context, + e.actionTitle, + e.icon, + e.onTap, + ), + ) + .toList(), + ] + .map( + (e) => Align( + alignment: Alignment.centerRight, + child: e, + ), + ) + .insertBetween( + Container( + height: 1, + color: theme.colorTheme.borders, ), - ); - }, - ), - if (StreamChat.of(context).currentUser?.id == - message.user?.id && - showDelete) - _buildButton( - context, - context.translations.deleteLabel.sentenceCase, - StreamSvgIcon( - size: 24, - icon: StreamSvgIcons.delete, - color: theme.colorTheme.accentError, - ), - () { - final channel = StreamChannel.of(context).channel; - if (message.attachments.length > 1 || - message.text?.isNotEmpty == true) { - final currentAttachmentIndex = - message.attachments.indexWhere( - (element) => element.id == attachment.id, - ); - final remainingAttachments = [...message.attachments] - ..removeAt(currentAttachmentIndex); - channel.updateMessage(message.copyWith( - attachments: remainingAttachments, - )); - Navigator.of(context) - ..pop() - ..maybePop(); - } else { - channel.deleteMessage(message); - Navigator.of(context) - ..pop() - ..maybePop(); - } - }, - color: theme.colorTheme.accentError, - ), - ...customActions - .map( - (e) => _buildButton( - context, - e.actionTitle, - e.icon, - e.onTap, ), - ) - .toList(), - ] - .map((e) => Align( - alignment: Alignment.centerRight, - child: e, - )) - .insertBetween( - Container( - height: 1, - color: theme.colorTheme.borders, - ), - ), ), ), ), @@ -276,10 +277,7 @@ class AttachmentActionsModal extends StatelessWidget { const SizedBox(width: 16), Text( title, - style: StreamChatTheme.of(context) - .textTheme - .body - .copyWith(color: color), + style: StreamChatTheme.of(context).textTheme.body.copyWith(color: color), ), ], ), @@ -336,40 +334,38 @@ class AttachmentActionsModal extends StatelessWidget { ), ) : _downloadComplete - ? SizedBox( - key: const Key('completedIcon'), - height: 160, - width: 160, - child: StreamSvgIcon( - icon: StreamSvgIcons.check, - color: theme.colorTheme.disabled, + ? SizedBox( + key: const Key('completedIcon'), + height: 160, + width: 160, + child: StreamSvgIcon( + icon: StreamSvgIcons.check, + color: theme.colorTheme.disabled, + ), + ) + : SizedBox( + height: 100, + width: 100, + child: Stack( + fit: StackFit.expand, + children: [ + CircularProgressIndicator.adaptive( + strokeWidth: 8, + valueColor: AlwaysStoppedAnimation( + theme.colorTheme.accentPrimary, + ), ), - ) - : SizedBox( - height: 100, - width: 100, - child: Stack( - fit: StackFit.expand, - children: [ - CircularProgressIndicator.adaptive( - strokeWidth: 8, - valueColor: AlwaysStoppedAnimation( - theme.colorTheme.accentPrimary, - ), - ), - Center( - child: Text( - '${progress.receivedValueInMB} MB', - style: - theme.textTheme.headline.copyWith( - color: - theme.colorTheme.textLowEmphasis, - ), - ), + Center( + child: Text( + '${progress.receivedValueInMB} MB', + style: theme.textTheme.headline.copyWith( + color: theme.colorTheme.textLowEmphasis, ), - ], + ), ), - ), + ], + ), + ), ), ), ), @@ -384,8 +380,7 @@ class AttachmentActionsModal extends StatelessWidget { class _DownloadProgress { const _DownloadProgress(this.total, this.received); - factory _DownloadProgress.initial() => - _DownloadProgress(double.maxFinite.toInt(), 0); + factory _DownloadProgress.initial() => _DownloadProgress(double.maxFinite.toInt(), 0); final int total; final int received; diff --git a/packages/stream_chat_flutter/lib/src/audio/audio_playlist_controller.dart b/packages/stream_chat_flutter/lib/src/audio/audio_playlist_controller.dart index 013aadde1e..1f226972cb 100644 --- a/packages/stream_chat_flutter/lib/src/audio/audio_playlist_controller.dart +++ b/packages/stream_chat_flutter/lib/src/audio/audio_playlist_controller.dart @@ -21,8 +21,8 @@ class StreamAudioPlaylistController extends ValueNotifier { StreamAudioPlaylistController.raw({ AudioPlayer? player, AudioPlaylistState state = const AudioPlaylistState(tracks: []), - }) : _player = player ?? AudioPlayer(), - super(state); + }) : _player = player ?? AudioPlayer(), + super(state); final AudioPlayer _player; @@ -45,21 +45,22 @@ class StreamAudioPlaylistController extends ValueNotifier { final tracks = [ ...value.tracks.mapIndexed((index, track) { final trackState = switch (index == currentIndex) { - true => state.playing - ? TrackState.playing - : switch (state.processingState) { - ProcessingState.idle => TrackState.idle, - ProcessingState.loading => TrackState.loading, - _ => TrackState.paused, - }, + true => + state.playing + ? TrackState.playing + : switch (state.processingState) { + ProcessingState.idle => TrackState.idle, + ProcessingState.loading => TrackState.loading, + _ => TrackState.paused, + }, false => switch (track.state) { - TrackState.idle => TrackState.idle, - _ => TrackState.paused, - }, + TrackState.idle => TrackState.idle, + _ => TrackState.paused, + }, }; return track.copyWith(state: trackState); - }) + }), ]; value = value.copyWith(tracks: tracks); @@ -74,7 +75,7 @@ class StreamAudioPlaylistController extends ValueNotifier { ...value.tracks.mapIndexed((index, track) { if (index != currentIndex) return track; return track.copyWith(position: position); - }) + }), ]; value = value.copyWith(tracks: tracks); diff --git a/packages/stream_chat_flutter/lib/src/audio/audio_playlist_state.dart b/packages/stream_chat_flutter/lib/src/audio/audio_playlist_state.dart index 36a3078037..204dddd0c3 100644 --- a/packages/stream_chat_flutter/lib/src/audio/audio_playlist_state.dart +++ b/packages/stream_chat_flutter/lib/src/audio/audio_playlist_state.dart @@ -86,7 +86,8 @@ enum TrackState { playing, /// The track is currently paused. - paused; + paused + ; /// Returns `true` if the track is currently idle. bool get isIdle => this == TrackState.idle; @@ -112,7 +113,8 @@ enum PlaybackSpeed { faster._(1.5), /// The fastest speed of the playback (2x). - fastest._(2); + fastest._(2) + ; const PlaybackSpeed._(this.speed); diff --git a/packages/stream_chat_flutter/lib/src/audio/audio_sampling.dart b/packages/stream_chat_flutter/lib/src/audio/audio_sampling.dart index 0381d273eb..943032510d 100644 --- a/packages/stream_chat_flutter/lib/src/audio/audio_sampling.dart +++ b/packages/stream_chat_flutter/lib/src/audio/audio_sampling.dart @@ -36,23 +36,21 @@ List downSample(List data, int targetOutputSize) { final previousBucketRefPoint = data[lastSelectedPointIndex]; final nextBucketMean = _getNextBucketMean(data, bucketIndex, bucketSize); - final currentBucketStartIndex = - ((bucketIndex - 1) * bucketSize).floor() + 1; + final currentBucketStartIndex = ((bucketIndex - 1) * bucketSize).floor() + 1; final nextBucketStartIndex = (bucketIndex * bucketSize).floor() + 1; - final countUnitsBetweenAtoC = - 1 + nextBucketStartIndex - currentBucketStartIndex; + final countUnitsBetweenAtoC = 1 + nextBucketStartIndex - currentBucketStartIndex; var maxArea = -1.0; var triangleArea = -1.0; double? maxAreaPoint; - for (var currentPointIndex = currentBucketStartIndex; - currentPointIndex < nextBucketStartIndex; - currentPointIndex++) { - final countUnitsBetweenAtoB = - (currentPointIndex - currentBucketStartIndex).abs() + 1; - final countUnitsBetweenBtoC = - countUnitsBetweenAtoC - countUnitsBetweenAtoB; + for ( + var currentPointIndex = currentBucketStartIndex; + currentPointIndex < nextBucketStartIndex; + currentPointIndex++ + ) { + final countUnitsBetweenAtoB = (currentPointIndex - currentBucketStartIndex).abs() + 1; + final countUnitsBetweenBtoC = countUnitsBetweenAtoC - countUnitsBetweenAtoB; final currentPointValue = data[currentPointIndex]; triangleArea = _triangleAreaHeron( @@ -107,11 +105,8 @@ double _getNextBucketMean( double bucketSize, ) { final nextBucketStartIndex = (currentBucketIndex * bucketSize).floor() + 1; - var nextNextBucketStartIndex = - ((currentBucketIndex + 1) * bucketSize).floor() + 1; - nextNextBucketStartIndex = nextNextBucketStartIndex < data.length - ? nextNextBucketStartIndex - : data.length; + var nextNextBucketStartIndex = ((currentBucketIndex + 1) * bucketSize).floor() + 1; + nextNextBucketStartIndex = nextNextBucketStartIndex < data.length ? nextNextBucketStartIndex : data.length; return _mean(data.sublist(nextBucketStartIndex, nextNextBucketStartIndex)); } diff --git a/packages/stream_chat_flutter/lib/src/autocomplete/stream_autocomplete.dart b/packages/stream_chat_flutter/lib/src/autocomplete/stream_autocomplete.dart index 98487690c2..9cbcc3d662 100644 --- a/packages/stream_chat_flutter/lib/src/autocomplete/stream_autocomplete.dart +++ b/packages/stream_chat_flutter/lib/src/autocomplete/stream_autocomplete.dart @@ -19,7 +19,8 @@ enum OptionsAlignment { /// The options are displayed above the field. /// /// This is the default. - above; + above + ; Anchor _toAnchor() { switch (this) { @@ -45,11 +46,12 @@ enum OptionsAlignment { /// See also: /// /// * [StreamAutocomplete.fieldViewBuilder], which is of this type. -typedef StreamAutocompleteFieldViewBuilder = Widget Function( - BuildContext context, - StreamMessageEditingController messageEditingController, - FocusNode focusNode, -); +typedef StreamAutocompleteFieldViewBuilder = + Widget Function( + BuildContext context, + StreamMessageEditingController messageEditingController, + FocusNode focusNode, + ); /// The type of the [StreamAutocompleteTrigger] callback which returns a /// [Widget] that displays the specified [options]. @@ -57,11 +59,12 @@ typedef StreamAutocompleteFieldViewBuilder = Widget Function( /// See also: /// /// * [StreamAutocompleteTrigger.optionsViewBuilder], which is of this type. -typedef StreamAutocompleteOptionsViewBuilder = Widget Function( - BuildContext context, - StreamAutocompleteQuery autocompleteQuery, - StreamMessageEditingController messageEditingController, -); +typedef StreamAutocompleteOptionsViewBuilder = + Widget Function( + BuildContext context, + StreamAutocompleteQuery autocompleteQuery, + StreamMessageEditingController messageEditingController, + ); /// The query to determine the autocomplete options. class StreamAutocompleteQuery { @@ -148,8 +151,7 @@ class StreamAutocompleteTrigger { final cursorPosition = textEditingValue.selection.baseOffset; // Find the first [trigger] location before the input cursor. - final firstTriggerIndexBeforeCursor = - text.substring(0, cursorPosition).lastIndexOf(trigger); + final firstTriggerIndexBeforeCursor = text.substring(0, cursorPosition).lastIndexOf(trigger); // If the [trigger] is not found before the cursor, then it's not a trigger. if (firstTriggerIndexBeforeCursor == -1) return null; @@ -164,9 +166,7 @@ class StreamAutocompleteTrigger { // valid examples: "@user", "Hello @user" // invalid examples: "Hello@user" final textBeforeTrigger = text.substring(0, firstTriggerIndexBeforeCursor); - if (triggerOnlyAfterSpace && - textBeforeTrigger.isNotEmpty && - !textBeforeTrigger.endsWith(' ')) { + if (triggerOnlyAfterSpace && textBeforeTrigger.isNotEmpty && !textBeforeTrigger.endsWith(' ')) { return null; } @@ -287,10 +287,7 @@ class _StreamAutocompleteState extends State { // True if the state indicates that the options should be visible. bool get _shouldShowOptions { - return !_hideOptions && - _focusNode.hasFocus && - _currentQuery != null && - _currentTrigger != null; + return !_hideOptions && _focusNode.hasFocus && _currentQuery != null && _currentTrigger != null; } /// Accepts and replaces the current query with the given [option] and closes @@ -467,8 +464,7 @@ class _StreamAutocompleteState extends State { @override void initState() { super.initState(); - _messageEditingController = - widget.messageEditingController ?? StreamMessageEditingController(); + _messageEditingController = widget.messageEditingController ?? StreamMessageEditingController(); _messageEditingController.addListener(_onChangedField); _focusNode = widget.focusNode ?? FocusNode(); _focusNode.addListener(_onChangedFocus); diff --git a/packages/stream_chat_flutter/lib/src/autocomplete/stream_command_autocomplete_options.dart b/packages/stream_chat_flutter/lib/src/autocomplete/stream_command_autocomplete_options.dart index d2556eb55a..49184fcf4a 100644 --- a/packages/stream_chat_flutter/lib/src/autocomplete/stream_command_autocomplete_options.dart +++ b/packages/stream_chat_flutter/lib/src/autocomplete/stream_command_autocomplete_options.dart @@ -79,9 +79,7 @@ class StreamCommandAutocompleteOptions extends StatelessWidget { ), ], ), - onTap: onCommandSelected == null - ? null - : () => onCommandSelected!(command), + onTap: onCommandSelected == null ? null : () => onCommandSelected!(command), ); }, ); diff --git a/packages/stream_chat_flutter/lib/src/autocomplete/stream_mention_autocomplete_options.dart b/packages/stream_chat_flutter/lib/src/autocomplete/stream_mention_autocomplete_options.dart index 955e02ae34..e528ab4832 100644 --- a/packages/stream_chat_flutter/lib/src/autocomplete/stream_mention_autocomplete_options.dart +++ b/packages/stream_chat_flutter/lib/src/autocomplete/stream_mention_autocomplete_options.dart @@ -16,14 +16,14 @@ class StreamMentionAutocompleteOptions extends StatefulWidget { this.mentionAllAppUsers = false, this.mentionsTileBuilder, this.onMentionUserTap, - }) : assert( - channel.state != null, - 'Channel ${channel.cid} is not yet initialized', - ), - assert( - !mentionAllAppUsers || (mentionAllAppUsers && client != null), - 'StreamChatClient is required in order to use mentionAllAppUsers', - ); + }) : assert( + channel.state != null, + 'Channel ${channel.cid} is not yet initialized', + ), + assert( + !mentionAllAppUsers || (mentionAllAppUsers && client != null), + 'StreamChatClient is required in order to use mentionAllAppUsers', + ); /// Query for searching users. final String query; @@ -49,12 +49,10 @@ class StreamMentionAutocompleteOptions extends StatefulWidget { final ValueSetter? onMentionUserTap; @override - _StreamMentionAutocompleteOptionsState createState() => - _StreamMentionAutocompleteOptionsState(); + _StreamMentionAutocompleteOptionsState createState() => _StreamMentionAutocompleteOptionsState(); } -class _StreamMentionAutocompleteOptionsState - extends State { +class _StreamMentionAutocompleteOptionsState extends State { late Future> userMentionsFuture; @override @@ -90,11 +88,8 @@ class _StreamMentionAutocompleteOptionsState return Material( color: colorTheme.barsBg, child: InkWell( - onTap: widget.onMentionUserTap == null - ? null - : () => widget.onMentionUserTap!(user), - child: widget.mentionsTileBuilder?.call(context, user) ?? - StreamUserMentionTile(user), + onTap: widget.onMentionUserTap == null ? null : () => widget.onMentionUserTap!(user), + child: widget.mentionsTileBuilder?.call(context, user) ?? StreamUserMentionTile(user), ), ); }, @@ -131,18 +126,13 @@ class _StreamMentionAutocompleteOptionsState } final result = await _queryMembers(query); - return result - .map((it) => it.user) - .whereType() - .toList(growable: false); + return result.map((it) => it.user).whereType().toList(growable: false); } Future> _queryMembers(String query) async { final response = await widget.channel.queryMembers( pagination: PaginationParams(limit: widget.limit), - filter: query.isEmpty - ? const Filter.empty() - : Filter.autoComplete('name', query), + filter: query.isEmpty ? const Filter.empty() : Filter.autoComplete('name', query), ); return response.members; } diff --git a/packages/stream_chat_flutter/lib/src/avatars/group_avatar.dart b/packages/stream_chat_flutter/lib/src/avatars/group_avatar.dart index 146a664318..8b164f8401 100644 --- a/packages/stream_chat_flutter/lib/src/avatars/group_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/avatars/group_avatar.dart @@ -2,12 +2,13 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// WidgetBuilder for [StreamGroupAvatar]. -typedef StreamGroupAvatarBuilder = Widget Function( - BuildContext context, - List members, - // ignore: avoid_positional_boolean_parameters - bool isSelected, -); +typedef StreamGroupAvatarBuilder = + Widget Function( + BuildContext context, + List members, + // ignore: avoid_positional_boolean_parameters + bool isSelected, + ); /// {@template streamGroupAvatar} /// Widget for constructing a group of images @@ -66,8 +67,7 @@ class StreamGroupAvatar extends StatelessWidget { Widget avatar = GestureDetector( onTap: onTap, child: ClipRRect( - borderRadius: - borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero, + borderRadius: borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero, child: Container( constraints: constraints ?? previewTheme?.constraints, decoration: BoxDecoration(color: colorTheme.accentPrimary), @@ -136,8 +136,7 @@ class StreamGroupAvatar extends StatelessWidget { ), ), initialData: member, - builder: (context, member) => - StreamUserAvatar( + builder: (context, member) => StreamUserAvatar( showOnlineStatus: false, user: member.user!, borderRadius: BorderRadius.zero, @@ -158,7 +157,8 @@ class StreamGroupAvatar extends StatelessWidget { if (selected) { avatar = ClipRRect( - borderRadius: BorderRadius.circular(selectionThickness) + + borderRadius: + BorderRadius.circular(selectionThickness) + (borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero), child: Container( constraints: constraints ?? previewTheme?.constraints, diff --git a/packages/stream_chat_flutter/lib/src/avatars/user_avatar.dart b/packages/stream_chat_flutter/lib/src/avatars/user_avatar.dart index f62e40bcff..f5bc3e4732 100644 --- a/packages/stream_chat_flutter/lib/src/avatars/user_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/avatars/user_avatar.dart @@ -3,12 +3,13 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// WidgetBuilder for [StreamUserAvatar]. -typedef StreamUserAvatarBuilder = Widget Function( - BuildContext context, - User user, - // ignore: avoid_positional_boolean_parameters - bool isSelected, -); +typedef StreamUserAvatarBuilder = + Widget Function( + BuildContext context, + User user, + // ignore: avoid_positional_boolean_parameters + bool isSelected, + ); /// {@template streamUserAvatar} /// Displays a user's avatar. @@ -145,8 +146,7 @@ class StreamUserAvatar extends StatelessWidget { if (selected) { avatar = ClipRRect( - borderRadius: (effectiveBorderRadius ?? BorderRadius.zero) + - BorderRadius.circular(selectionThickness), + borderRadius: (effectiveBorderRadius ?? BorderRadius.zero) + BorderRadius.circular(selectionThickness), child: Container( constraints: constraints ?? avatarTheme?.constraints, color: selectionColor ?? colorTheme.accentPrimary, @@ -172,7 +172,8 @@ class StreamUserAvatar extends StatelessWidget { color: colorTheme.barsBg, child: Container( margin: const EdgeInsets.all(2), - constraints: onlineIndicatorConstraints ?? + constraints: + onlineIndicatorConstraints ?? const BoxConstraints.tightFor( width: 8, height: 8, diff --git a/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart b/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart index c1cb4ad060..72ef51b101 100644 --- a/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart @@ -13,9 +13,9 @@ class StreamChannelInfoBottomSheet extends StatelessWidget { this.onDeleteConversationTap, this.onCancelTap, }) : assert( - channel.state != null, - 'Channel ${channel.id} is not initialized', - ); + channel.state != null, + 'Channel ${channel.id} is not initialized', + ); /// The [Channel] to show information about. final Channel channel; @@ -104,9 +104,7 @@ class StreamChannelInfoBottomSheet extends StatelessWidget { onlineIndicatorConstraints: BoxConstraints.tight( const Size(12, 12), ), - onTap: onMemberTap != null - ? (_) => onMemberTap!(member) - : null, + onTap: onMemberTap != null ? (_) => onMemberTap!(member) : null, ), const SizedBox(height: 6), Text( @@ -259,30 +257,29 @@ Future showChannelInfoModalBottomSheet({ VoidCallback? onLeaveChannelTap, VoidCallback? onDeleteConversationTap, VoidCallback? onCancelTap, -}) => - showModalBottomSheet( - context: context, - backgroundColor: backgroundColor, - elevation: elevation, - shape: shape, - clipBehavior: clipBehavior, - constraints: constraints, - barrierColor: barrierColor, - isScrollControlled: isScrollControlled, - useRootNavigator: useRootNavigator, - isDismissible: isDismissible, - enableDrag: enableDrag, - routeSettings: routeSettings, - transitionAnimationController: transitionAnimationController, - builder: (BuildContext context) => StreamChannelInfoBottomSheet( - channel: channel, - onMemberTap: onMemberTap, - onViewInfoTap: onViewInfoTap, - onLeaveChannelTap: onLeaveChannelTap, - onDeleteConversationTap: onDeleteConversationTap, - onCancelTap: onCancelTap, - ), - ); +}) => showModalBottomSheet( + context: context, + backgroundColor: backgroundColor, + elevation: elevation, + shape: shape, + clipBehavior: clipBehavior, + constraints: constraints, + barrierColor: barrierColor, + isScrollControlled: isScrollControlled, + useRootNavigator: useRootNavigator, + isDismissible: isDismissible, + enableDrag: enableDrag, + routeSettings: routeSettings, + transitionAnimationController: transitionAnimationController, + builder: (BuildContext context) => StreamChannelInfoBottomSheet( + channel: channel, + onMemberTap: onMemberTap, + onViewInfoTap: onViewInfoTap, + onLeaveChannelTap: onLeaveChannelTap, + onDeleteConversationTap: onDeleteConversationTap, + onCancelTap: onCancelTap, + ), +); /// Shows a material design bottom sheet in the nearest [Scaffold] ancestor. If /// you wish to show a persistent bottom sheet, use [Scaffold.bottomSheet]. @@ -339,21 +336,20 @@ PersistentBottomSheetController showChannelInfoBottomSheet({ VoidCallback? onLeaveChannelTap, VoidCallback? onDeleteConversationTap, VoidCallback? onCancelTap, -}) => - showBottomSheet( - context: context, - backgroundColor: backgroundColor, - elevation: elevation, - shape: shape, - clipBehavior: clipBehavior, - constraints: constraints, - transitionAnimationController: transitionAnimationController, - builder: (BuildContext context) => StreamChannelInfoBottomSheet( - channel: channel, - onMemberTap: onMemberTap, - onViewInfoTap: onViewInfoTap, - onLeaveChannelTap: onLeaveChannelTap, - onDeleteConversationTap: onDeleteConversationTap, - onCancelTap: onCancelTap, - ), - ); +}) => showBottomSheet( + context: context, + backgroundColor: backgroundColor, + elevation: elevation, + shape: shape, + clipBehavior: clipBehavior, + constraints: constraints, + transitionAnimationController: transitionAnimationController, + builder: (BuildContext context) => StreamChannelInfoBottomSheet( + channel: channel, + onMemberTap: onMemberTap, + onViewInfoTap: onViewInfoTap, + onLeaveChannelTap: onLeaveChannelTap, + onDeleteConversationTap: onDeleteConversationTap, + onCancelTap: onCancelTap, + ), +); diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_header.dart b/packages/stream_chat_flutter/lib/src/channel/channel_header.dart index 20dd2d5ae3..679b102c78 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_header.dart @@ -50,8 +50,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// and the [StreamChatThemeData.channelHeaderTheme] property. Modify it to /// change the widget's appearance. /// {@endtemplate} -class StreamChannelHeader extends StatelessWidget - implements PreferredSizeWidget { +class StreamChannelHeader extends StatelessWidget implements PreferredSizeWidget { /// {@macro streamChannelHeader} const StreamChannelHeader({ super.key, @@ -141,7 +140,8 @@ class StreamChannelHeader extends StatelessWidget final channel = StreamChannel.of(context).channel; final channelHeaderTheme = StreamChannelHeaderTheme.of(context); - final leadingWidget = leading ?? + final leadingWidget = + leading ?? (showBackButton ? StreamBackButton( onPressed: onBackPressed, @@ -183,17 +183,16 @@ class StreamChannelHeader extends StatelessWidget bottom: bottom, bottomOpacity: bottomOpacity, backgroundColor: backgroundColor ?? channelHeaderTheme.color, - actions: actions ?? + actions: + actions ?? [ Padding( padding: const EdgeInsets.only(right: 10), child: Center( child: StreamChannelAvatar( channel: channel, - borderRadius: - channelHeaderTheme.avatarTheme?.borderRadius, - constraints: - channelHeaderTheme.avatarTheme?.constraints, + borderRadius: channelHeaderTheme.avatarTheme?.borderRadius, + constraints: channelHeaderTheme.avatarTheme?.constraints, onTap: onImageTap, ), ), @@ -206,9 +205,7 @@ class StreamChannelHeader extends StatelessWidget height: preferredSize.height, child: Column( mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: effectiveCenterTitle - ? CrossAxisAlignment.center - : CrossAxisAlignment.stretch, + crossAxisAlignment: effectiveCenterTitle ? CrossAxisAlignment.center : CrossAxisAlignment.stretch, children: [ title ?? StreamChannelName( diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel/channel_info.dart index d74d26673b..ae81af0ab1 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_info.dart @@ -83,8 +83,7 @@ class _ConnectedTitleState extends StatelessWidget { final memberCount = channel.memberCount; if (memberCount != null && memberCount > 2) { var text = context.translations.membersCountText(memberCount); - final onlineCount = - members?.where((m) => m.user?.online == true).length ?? 0; + final onlineCount = members?.where((m) => m.user?.online == true).length ?? 0; if (onlineCount > 0) { text += ', ${context.translations.watchersCountText(onlineCount)}'; } diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart b/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart index 2c3338425e..ddbc9105ee 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart @@ -37,8 +37,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// the [StreamChannelListHeaderThemeData] property. Modify it to change the /// widget's appearance. /// {@endtemplate} -class StreamChannelListHeader extends StatelessWidget - implements PreferredSizeWidget { +class StreamChannelListHeader extends StatelessWidget implements PreferredSizeWidget { /// {@macro streamChannelListHeader} const StreamChannelListHeader({ super.key, @@ -124,8 +123,7 @@ class StreamChannelListHeader extends StatelessWidget } final chatThemeData = StreamChatTheme.of(context); - final channelListHeaderThemeData = - StreamChannelListHeaderTheme.of(context); + final channelListHeaderThemeData = StreamChannelListHeaderTheme.of(context); final theme = Theme.of(context); return StreamInfoTile( showMessage: showConnectionStateTile && showStatus, @@ -137,36 +135,35 @@ class StreamChannelListHeader extends StatelessWidget ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark, elevation: elevation, - backgroundColor: - backgroundColor ?? channelListHeaderThemeData.color, + backgroundColor: backgroundColor ?? channelListHeaderThemeData.color, centerTitle: centerTitle, - leading: leading ?? + leading: + leading ?? Center( child: user != null ? StreamUserAvatar( user: user, showOnlineStatus: false, - onTap: onUserAvatarTap ?? + onTap: + onUserAvatarTap ?? (_) { preNavigationCallback?.call(); Scaffold.of(context).openDrawer(); }, - borderRadius: channelListHeaderThemeData - .avatarTheme?.borderRadius, - constraints: channelListHeaderThemeData - .avatarTheme?.constraints, + borderRadius: channelListHeaderThemeData.avatarTheme?.borderRadius, + constraints: channelListHeaderThemeData.avatarTheme?.constraints, ) : const Empty(), ), - actions: actions ?? + actions: + actions ?? [ StreamNeumorphicButton( child: IconButton( icon: StreamConnectionStatusBuilder( statusBuilder: (context, status) { final color = switch (status) { - ConnectionStatus.connected => - chatThemeData.colorTheme.accentPrimary, + ConnectionStatus.connected => chatThemeData.colorTheme.accentPrimary, ConnectionStatus.connecting => Colors.grey, ConnectionStatus.disconnected => Colors.grey, }; @@ -239,9 +236,9 @@ class _ConnectingTitleState extends StatelessWidget { Text( context.translations.searchingForNetworkText, style: StreamChannelListHeaderTheme.of(context).titleStyle?.copyWith( - fontSize: 16, - fontWeight: FontWeight.bold, - ), + fontSize: 16, + fontWeight: FontWeight.bold, + ), ), ], ); diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_name.dart b/packages/stream_chat_flutter/lib/src/channel/channel_name.dart index ddccdb7200..79aedaddbd 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_name.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_name.dart @@ -87,8 +87,7 @@ class _NameGenerator extends StatelessWidget { } }); - final exceedingMembers = - otherMembers.length - currentMembers.length; + final exceedingMembers = otherMembers.length - currentMembers.length; channelName = '${currentMembers.map((e) => e.user?.name).join(', ')} ' '${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; diff --git a/packages/stream_chat_flutter/lib/src/channel/stream_channel_avatar.dart b/packages/stream_chat_flutter/lib/src/channel/stream_channel_avatar.dart index 2f7665939f..59ba01a8c2 100644 --- a/packages/stream_chat_flutter/lib/src/channel/stream_channel_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/channel/stream_channel_avatar.dart @@ -58,9 +58,9 @@ class StreamChannelAvatar extends StatelessWidget { this.oneToOneAvatarBuilder, this.groupAvatarBuilder, }) : assert( - channel.state != null, - 'Channel ${channel.id} is not initialized', - ); + channel.state != null, + 'Channel ${channel.id} is not initialized', + ); /// [BorderRadius] to display the widget final BorderRadius? borderRadius; @@ -121,8 +121,7 @@ class StreamChannelAvatar extends StatelessWidget { initialData: channel.image, builder: (context, channelImage) { Widget child = ClipRRect( - borderRadius: - borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero, + borderRadius: borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero, child: Container( constraints: constraints ?? previewTheme?.constraints, decoration: BoxDecoration(color: colorTheme.accentPrimary), @@ -158,10 +157,9 @@ class StreamChannelAvatar extends StatelessWidget { if (selected) { child = ClipRRect( key: const Key('selectedImage'), - borderRadius: BorderRadius.circular(selectionThickness) + - (borderRadius ?? - previewTheme?.borderRadius ?? - BorderRadius.zero), + borderRadius: + BorderRadius.circular(selectionThickness) + + (borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero), child: Container( constraints: constraints ?? previewTheme?.constraints, color: selectionColor ?? colorTheme.accentPrimary, @@ -176,9 +174,7 @@ class StreamChannelAvatar extends StatelessWidget { }, noDataBuilder: (context) { final currentUser = client.currentUser!; - final otherMembers = channel.state!.members - .where((it) => it.userId != currentUser.id) - .toList(growable: false); + final otherMembers = channel.state!.members.where((it) => it.userId != currentUser.id).toList(growable: false); // our own space, no other members if (otherMembers.isEmpty) { diff --git a/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart b/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart index 273b5b37e8..7b1d963f9f 100644 --- a/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart +++ b/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart @@ -13,9 +13,9 @@ class StreamChannelName extends StatelessWidget { this.textStyle, this.textOverflow = TextOverflow.ellipsis, }) : assert( - channel.state != null, - 'Channel ${channel.id} is not initialized', - ); + channel.state != null, + 'Channel ${channel.id} is not initialized', + ); /// The [Channel] to show the name for. final Channel channel; @@ -28,63 +28,60 @@ class StreamChannelName extends StatelessWidget { @override Widget build(BuildContext context) => BetterStreamBuilder( - stream: channel.nameStream, - initialData: channel.name, - builder: (context, channelName) => Text( - channelName, - style: textStyle, - overflow: textOverflow, - ), - noDataBuilder: (context) => _generateName( - channel.client.state.currentUser!, - channel.state!.members, - ), - ); + stream: channel.nameStream, + initialData: channel.name, + builder: (context, channelName) => Text( + channelName, + style: textStyle, + overflow: textOverflow, + ), + noDataBuilder: (context) => _generateName( + channel.client.state.currentUser!, + channel.state!.members, + ), + ); Widget _generateName( User currentUser, List members, - ) => - LayoutBuilder( - builder: (context, constraints) { - var channelName = context.translations.noTitleText; - final otherMembers = members.where( - (member) => member.userId != currentUser.id, - ); - - if (otherMembers.isNotEmpty) { - if (otherMembers.length == 1) { - final user = otherMembers.first.user; - if (user != null) { - channelName = user.name; - } - } else { - final maxWidth = constraints.maxWidth; - final maxChars = maxWidth / (textStyle?.fontSize ?? 1); - var currentChars = 0; - final currentMembers = []; - otherMembers.forEach((element) { - final newLength = - currentChars + (element.user?.name.length ?? 0); - if (newLength < maxChars) { - currentChars = newLength; - currentMembers.add(element); - } - }); + ) => LayoutBuilder( + builder: (context, constraints) { + var channelName = context.translations.noTitleText; + final otherMembers = members.where( + (member) => member.userId != currentUser.id, + ); - final exceedingMembers = - otherMembers.length - currentMembers.length; - channelName = - '${currentMembers.map((e) => e.user?.name).join(', ')} ' - '${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; - } + if (otherMembers.isNotEmpty) { + if (otherMembers.length == 1) { + final user = otherMembers.first.user; + if (user != null) { + channelName = user.name; } + } else { + final maxWidth = constraints.maxWidth; + final maxChars = maxWidth / (textStyle?.fontSize ?? 1); + var currentChars = 0; + final currentMembers = []; + otherMembers.forEach((element) { + final newLength = currentChars + (element.user?.name.length ?? 0); + if (newLength < maxChars) { + currentChars = newLength; + currentMembers.add(element); + } + }); + + final exceedingMembers = otherMembers.length - currentMembers.length; + channelName = + '${currentMembers.map((e) => e.user?.name).join(', ')} ' + '${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; + } + } - return Text( - channelName, - style: textStyle, - overflow: textOverflow, - ); - }, + return Text( + channelName, + style: textStyle, + overflow: textOverflow, ); + }, + ); } diff --git a/packages/stream_chat_flutter/lib/src/context_menu/context_menu.dart b/packages/stream_chat_flutter/lib/src/context_menu/context_menu.dart index 12b9e68e25..3636cae319 100644 --- a/packages/stream_chat_flutter/lib/src/context_menu/context_menu.dart +++ b/packages/stream_chat_flutter/lib/src/context_menu/context_menu.dart @@ -7,10 +7,11 @@ const double _kContextMenuWidth = 222; /// /// This builder can be used to customize the appearance of the menu /// container by wrapping [child] in additional UI elements. -typedef ContextMenuBuilder = Widget Function( - BuildContext context, - Widget child, -); +typedef ContextMenuBuilder = + Widget Function( + BuildContext context, + Widget child, + ); /// A widget that displays a context menu anchored to a specific [Offset]. /// diff --git a/packages/stream_chat_flutter/lib/src/context_menu/context_menu_region.dart b/packages/stream_chat_flutter/lib/src/context_menu/context_menu_region.dart index d3554889dc..fa43d085ed 100644 --- a/packages/stream_chat_flutter/lib/src/context_menu/context_menu_region.dart +++ b/packages/stream_chat_flutter/lib/src/context_menu/context_menu_region.dart @@ -6,10 +6,11 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// /// The function receives the [BuildContext] and the [Offset] where /// the menu should appear. -typedef ContextMenuBuilder = Widget Function( - BuildContext context, - Offset offset, -); +typedef ContextMenuBuilder = + Widget Function( + BuildContext context, + Offset offset, + ); /// Displays a custom context menu as a general dialog. /// diff --git a/packages/stream_chat_flutter/lib/src/dialogs/channel_info_dialog.dart b/packages/stream_chat_flutter/lib/src/dialogs/channel_info_dialog.dart index 974a1c2fc6..3f1a061ebb 100644 --- a/packages/stream_chat_flutter/lib/src/dialogs/channel_info_dialog.dart +++ b/packages/stream_chat_flutter/lib/src/dialogs/channel_info_dialog.dart @@ -39,9 +39,7 @@ class ChannelInfoDialog extends StatelessWidget { children: [ StreamChannelInfo( channel: channel, - textStyle: StreamChatTheme.of(context) - .channelPreviewTheme - .subtitleStyle, + textStyle: StreamChatTheme.of(context).channelPreviewTheme.subtitleStyle, ), ], ), @@ -60,8 +58,7 @@ class ChannelInfoDialog extends StatelessWidget { width: 64, ), borderRadius: BorderRadius.circular(32), - onlineIndicatorConstraints: - BoxConstraints.tight(const Size(12, 12)), + onlineIndicatorConstraints: BoxConstraints.tight(const Size(12, 12)), ), const SizedBox(height: 6), Text( diff --git a/packages/stream_chat_flutter/lib/src/dialogs/message_dialog.dart b/packages/stream_chat_flutter/lib/src/dialogs/message_dialog.dart index f8b9930c43..24daa59d48 100644 --- a/packages/stream_chat_flutter/lib/src/dialogs/message_dialog.dart +++ b/packages/stream_chat_flutter/lib/src/dialogs/message_dialog.dart @@ -33,8 +33,7 @@ class MessageDialog extends StatelessWidget { title: Text(titleText ?? context.translations.somethingWentWrongError), content: messageText != null ? Text( - messageText ?? - context.translations.operationCouldNotBeCompletedText, + messageText ?? context.translations.operationCouldNotBeCompletedText, ) : null, actions: [ diff --git a/packages/stream_chat_flutter/lib/src/fullscreen_media/fsm_stub.dart b/packages/stream_chat_flutter/lib/src/fullscreen_media/fsm_stub.dart index 1bf7c435b3..1dd6ac10c3 100644 --- a/packages/stream_chat_flutter/lib/src/fullscreen_media/fsm_stub.dart +++ b/packages/stream_chat_flutter/lib/src/fullscreen_media/fsm_stub.dart @@ -15,5 +15,4 @@ FullScreenMediaWidget getFsm({ ReplyMessageCallback? onReplyMessage, AttachmentActionsBuilder? attachmentActionsModalBuilder, bool? autoplayVideos, -}) => - throw UnsupportedError('Cannot create FullScreenMedia'); +}) => throw UnsupportedError('Cannot create FullScreenMedia'); diff --git a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media.dart b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media.dart index eb95489c24..b9c72ceb45 100644 --- a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media.dart @@ -81,17 +81,16 @@ class _FullScreenMediaState extends State { return; } - final currentAttachment = - widget.mediaAttachmentPackages[widget.startIndex].attachment; + final currentAttachment = widget.mediaAttachmentPackages[widget.startIndex].attachment; - await Future.wait(videoPackages.values.map( - (it) => it.initialize(), - )); + await Future.wait( + videoPackages.values.map( + (it) => it.initialize(), + ), + ); - if (widget.autoplayVideos && - currentAttachment.type == AttachmentType.video) { - final package = videoPackages.values - .firstWhere((e) => e._attachment == currentAttachment); + if (widget.autoplayVideos && currentAttachment.type == AttachmentType.video) { + final package = videoPackages.values.firstWhere((e) => e._attachment == currentAttachment); package._chewieController?.play(); } setState(() {}); // ignore: no-empty-block @@ -115,8 +114,7 @@ class _FullScreenMediaState extends State { body: ValueListenableBuilder( valueListenable: _currentPage, builder: (context, currentPage, child) { - final _currentAttachmentPackage = - widget.mediaAttachmentPackages[currentPage]; + final _currentAttachmentPackage = widget.mediaAttachmentPackages[currentPage]; final _currentMessage = _currentAttachmentPackage.message; final _currentAttachment = _currentAttachmentPackage.attachment; return Stack( @@ -130,8 +128,7 @@ class _FullScreenMediaState extends State { return AnimatedPositionedDirectional( duration: kThemeAnimationDuration, curve: Curves.easeInOut, - top: - isDisplayingDetail ? 0 : -(topPadding + kToolbarHeight), + top: isDisplayingDetail ? 0 : -(topPadding + kToolbarHeight), start: 0, end: 0, height: topPadding + kToolbarHeight, @@ -163,8 +160,7 @@ class _FullScreenMediaState extends State { ); } : null, - attachmentActionsModalBuilder: - widget.attachmentActionsModalBuilder, + attachmentActionsModalBuilder: widget.attachmentActionsModalBuilder, ), ); }, @@ -178,9 +174,7 @@ class _FullScreenMediaState extends State { return AnimatedPositionedDirectional( duration: kThemeAnimationDuration, curve: Curves.easeInOut, - bottom: isDisplayingDetail - ? 0 - : -(bottomPadding + kToolbarHeight), + bottom: isDisplayingDetail ? 0 : -(bottomPadding + kToolbarHeight), start: 0, end: 0, height: bottomPadding + kToolbarHeight, @@ -246,8 +240,7 @@ class _FullScreenMediaState extends State { } }, onRightArrowKeypress: () { - if (_currentPage.value < - widget.mediaAttachmentPackages.length - 1) { + if (_currentPage.value < widget.mediaAttachmentPackages.length - 1) { _currentPage.value++; _pageController.nextPage( duration: const Duration(milliseconds: 300), @@ -261,22 +254,19 @@ class _FullScreenMediaState extends State { onPageChanged: (val) { _currentPage.value = val; if (videoPackages.isEmpty) return; - final currentAttachment = - widget.mediaAttachmentPackages[val].attachment; + final currentAttachment = widget.mediaAttachmentPackages[val].attachment; for (final e in videoPackages.values) { if (e._attachment != currentAttachment) { e._chewieController?.pause(); } } - if (widget.autoplayVideos && - currentAttachment.type == AttachmentType.video) { + if (widget.autoplayVideos && currentAttachment.type == AttachmentType.video) { final controller = videoPackages[currentAttachment.id]!; controller._chewieController?.play(); } }, itemBuilder: (context, index) { - final currentAttachmentPackage = - widget.mediaAttachmentPackages[index]; + final currentAttachmentPackage = widget.mediaAttachmentPackages[index]; final attachment = currentAttachmentPackage.attachment; return ValueListenableBuilder( valueListenable: _isDisplayingDetail, @@ -298,8 +288,7 @@ class _FullScreenMediaState extends State { }, child: Builder( builder: (context) { - if (attachment.type == AttachmentType.image || - attachment.type == AttachmentType.giphy) { + if (attachment.type == AttachmentType.image || attachment.type == AttachmentType.giphy) { return PhotoView.customChild( maxScale: PhotoViewComputedScale.covered, minScale: PhotoViewComputedScale.contained, @@ -345,15 +334,15 @@ class VideoPackage { this._attachment, { bool showControls = false, bool autoInitialize = true, - }) : _showControls = showControls, - _autoInitialize = autoInitialize, - _videoPlayerController = _attachment.localUri != null - ? VideoPlayerController.file( - File.fromUri(_attachment.localUri!), - ) - : VideoPlayerController.networkUrl( - Uri.parse(_attachment.assetUrl!), - ); + }) : _showControls = showControls, + _autoInitialize = autoInitialize, + _videoPlayerController = _attachment.localUri != null + ? VideoPlayerController.file( + File.fromUri(_attachment.localUri!), + ) + : VideoPlayerController.networkUrl( + Uri.parse(_attachment.assetUrl!), + ); final Attachment _attachment; final bool _showControls; @@ -384,12 +373,10 @@ class VideoPackage { } /// Add a listener to video player controller - void addListener(VoidCallback listener) => - _videoPlayerController.addListener(listener); + void addListener(VoidCallback listener) => _videoPlayerController.addListener(listener); /// Remove a listener to video player controller - void removeListener(VoidCallback listener) => - _videoPlayerController.removeListener(listener); + void removeListener(VoidCallback listener) => _videoPlayerController.removeListener(listener); /// Dispose controllers Future dispose() { diff --git a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_builder.dart b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_builder.dart index f1919db192..1609445cd8 100644 --- a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_builder.dart +++ b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_builder.dart @@ -1,7 +1,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/fullscreen_media/fsm_stub.dart' - if (dart.library.io) 'full_screen_media_desktop.dart' as desktop_fsm; + if (dart.library.io) 'full_screen_media_desktop.dart' + as desktop_fsm; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@template fsmBuilder} diff --git a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart index 53a5dac471..4b449732bd 100644 --- a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart +++ b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart @@ -109,8 +109,7 @@ class _FullScreenMediaDesktopState extends State { @override Widget build(BuildContext context) { - final containsOnlyVideos = - widget.mediaAttachmentPackages.length == videoPackages.length; + final containsOnlyVideos = widget.mediaAttachmentPackages.length == videoPackages.length; return Scaffold( resizeToAvoidBottomInset: false, @@ -160,8 +159,7 @@ class _FullScreenMediaDesktopState extends State { return ValueListenableBuilder( valueListenable: _currentPage, builder: (context, currentPage, child) { - final _currentAttachmentPackage = - widget.mediaAttachmentPackages[currentPage]; + final _currentAttachmentPackage = widget.mediaAttachmentPackages[currentPage]; final _currentMessage = _currentAttachmentPackage.message; final _currentAttachment = _currentAttachmentPackage.attachment; return Stack( @@ -194,8 +192,7 @@ class _FullScreenMediaDesktopState extends State { StreamChannel.of(context).channel, ); }, - attachmentActionsModalBuilder: - widget.attachmentActionsModalBuilder, + attachmentActionsModalBuilder: widget.attachmentActionsModalBuilder, ), ); }, @@ -209,9 +206,7 @@ class _FullScreenMediaDesktopState extends State { return AnimatedPositionedDirectional( duration: kThemeAnimationDuration, curve: Curves.easeInOut, - bottom: isDisplayingDetail - ? 0 - : -(bottomPadding + kToolbarHeight), + bottom: isDisplayingDetail ? 0 : -(bottomPadding + kToolbarHeight), start: 0, end: 0, height: bottomPadding + kToolbarHeight, @@ -277,8 +272,7 @@ class _FullScreenMediaDesktopState extends State { } }, onRightArrowKeypress: () { - if (_currentPage.value < - widget.mediaAttachmentPackages.length - 1) { + if (_currentPage.value < widget.mediaAttachmentPackages.length - 1) { _currentPage.value++; _pageController.nextPage( duration: const Duration(milliseconds: 300), @@ -292,22 +286,19 @@ class _FullScreenMediaDesktopState extends State { onPageChanged: (val) { _currentPage.value = val; if (videoPackages.isEmpty) return; - final currentAttachment = - widget.mediaAttachmentPackages[val].attachment; + final currentAttachment = widget.mediaAttachmentPackages[val].attachment; for (final p in videoPackages.values) { if (p.attachment != currentAttachment) { p.player.pause(); } } - if (widget.autoplayVideos && - currentAttachment.type == AttachmentType.video) { + if (widget.autoplayVideos && currentAttachment.type == AttachmentType.video) { final package = videoPackages[currentAttachment.id]!; package.player.play(); } }, itemBuilder: (context, index) { - final currentAttachmentPackage = - widget.mediaAttachmentPackages[index]; + final currentAttachmentPackage = widget.mediaAttachmentPackages[index]; final attachment = currentAttachmentPackage.attachment; return ValueListenableBuilder( @@ -330,8 +321,7 @@ class _FullScreenMediaDesktopState extends State { }, child: Builder( builder: (context) { - if (attachment.type == AttachmentType.image || - attachment.type == AttachmentType.giphy) { + if (attachment.type == AttachmentType.image || attachment.type == AttachmentType.giphy) { return PhotoView.customChild( maxScale: PhotoViewComputedScale.covered, minScale: PhotoViewComputedScale.contained, diff --git a/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart b/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart index ffcfef6cd2..73fff05323 100644 --- a/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart +++ b/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart @@ -9,8 +9,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@template streamGalleryFooter} /// Footer widget for media display /// {@endtemplate} -class StreamGalleryFooter extends StatefulWidget - implements PreferredSizeWidget { +class StreamGalleryFooter extends StatefulWidget implements PreferredSizeWidget { /// {@macro streamGalleryFooter} const StreamGalleryFooter({ super.key, @@ -73,10 +72,8 @@ class _StreamGalleryFooterState extends State { context: context, removeTop: true, child: BottomAppBar( - surfaceTintColor: - widget.backgroundColor ?? galleryFooterThemeData.backgroundColor, - color: - widget.backgroundColor ?? galleryFooterThemeData.backgroundColor, + surfaceTintColor: widget.backgroundColor ?? galleryFooterThemeData.backgroundColor, + color: widget.backgroundColor ?? galleryFooterThemeData.backgroundColor, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -91,28 +88,20 @@ class _StreamGalleryFooterState extends State { color: galleryFooterThemeData.shareIconColor, ), onPressed: () async { - final attachment = widget - .mediaAttachmentPackages[widget.currentPage].attachment; - final url = attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl!; - final type = attachment.type == AttachmentType.image - ? 'jpg' - : url.split('?').first.split('.').last; + final attachment = widget.mediaAttachmentPackages[widget.currentPage].attachment; + final url = attachment.imageUrl ?? attachment.assetUrl ?? attachment.thumbUrl!; + final type = attachment.type == AttachmentType.image ? 'jpg' : url.split('?').first.split('.').last; final request = await HttpClient().getUrl(Uri.parse(url)); final response = await request.close(); - final bytes = - await consolidateHttpClientResponseBytes(response); + final bytes = await consolidateHttpClientResponseBytes(response); final tmpPath = await getTemporaryDirectory(); final filePath = '${tmpPath.path}/${attachment.id}.$type'; final file = File(filePath); await file.writeAsBytes(bytes); - final box = - shareButtonKey.currentContext?.findRenderObject(); + final box = shareButtonKey.currentContext?.findRenderObject(); final size = shareButtonKey.currentContext?.size; - final position = - (box! as RenderBox).localToGlobal(Offset.zero); + final position = (box! as RenderBox).localToGlobal(Offset.zero); await SharePlus.instance.share( ShareParams( @@ -176,8 +165,7 @@ class _StreamGalleryFooterState extends State { builder: (context) { return DraggableScrollableSheet( expand: false, - initialChildSize: - (CurrentPlatform.isAndroid || CurrentPlatform.isIos) ? 0.3 : 0.5, + initialChildSize: (CurrentPlatform.isAndroid || CurrentPlatform.isIos) ? 0.3 : 0.5, minChildSize: 0.3, maxChildSize: 0.7, builder: (context, scrollController) => Column( @@ -189,8 +177,7 @@ class _StreamGalleryFooterState extends State { padding: const EdgeInsets.all(16), child: Text( context.translations.photosLabel, - style: - galleryFooterThemeData.bottomSheetPhotosTextStyle, + style: galleryFooterThemeData.bottomSheetPhotosTextStyle, ), ), ), @@ -219,8 +206,7 @@ class _StreamGalleryFooterState extends State { ), itemBuilder: (context, index) { Widget media; - final attachmentPackage = - widget.mediaAttachmentPackages[index]; + final attachmentPackage = widget.mediaAttachmentPackages[index]; final attachment = attachmentPackage.attachment; final message = attachmentPackage.message; if (attachment.type == AttachmentType.video) { @@ -268,8 +254,7 @@ class _StreamGalleryFooterState extends State { boxShadow: [ BoxShadow( blurRadius: 8, - color: chatThemeData - .colorTheme.textHighEmphasis + color: chatThemeData.colorTheme.textHighEmphasis // ignore: deprecated_member_use .withOpacity(0.3), ), @@ -277,8 +262,7 @@ class _StreamGalleryFooterState extends State { ), child: StreamUserAvatar( user: message.user!, - constraints: - BoxConstraints.tight(const Size(24, 24)), + constraints: BoxConstraints.tight(const Size(24, 24)), showOnlineStatus: false, ), ), diff --git a/packages/stream_chat_flutter/lib/src/gallery/gallery_header.dart b/packages/stream_chat_flutter/lib/src/gallery/gallery_header.dart index 679c1c3d72..f87983b829 100644 --- a/packages/stream_chat_flutter/lib/src/gallery/gallery_header.dart +++ b/packages/stream_chat_flutter/lib/src/gallery/gallery_header.dart @@ -11,8 +11,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// {@template streamGalleryHeader} /// Header/AppBar widget for media display screen /// {@endtemplate} -class StreamGalleryHeader extends StatelessWidget - implements PreferredSizeWidget { +class StreamGalleryHeader extends StatelessWidget implements PreferredSizeWidget { /// {@macro streamGalleryHeader} const StreamGalleryHeader({ super.key, @@ -83,9 +82,7 @@ class StreamGalleryHeader extends StatelessWidget return AppBar( toolbarTextStyle: theme.textTheme.bodyMedium, titleTextStyle: theme.textTheme.titleLarge, - systemOverlayStyle: theme.brightness == Brightness.dark - ? SystemUiOverlayStyle.light - : SystemUiOverlayStyle.dark, + systemOverlayStyle: theme.brightness == Brightness.dark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark, elevation: elevation, leading: showBackButton ? IconButton( @@ -97,10 +94,8 @@ class StreamGalleryHeader extends StatelessWidget onPressed: onBackPressed, ) : const Empty(), - surfaceTintColor: - backgroundColor ?? galleryHeaderThemeData.backgroundColor, - backgroundColor: - backgroundColor ?? galleryHeaderThemeData.backgroundColor, + surfaceTintColor: backgroundColor ?? galleryHeaderThemeData.backgroundColor, + backgroundColor: backgroundColor ?? galleryHeaderThemeData.backgroundColor, actions: [ if (!message.isEphemeral) IconButton( @@ -143,8 +138,7 @@ class StreamGalleryHeader extends StatelessWidget Future _showMessageActionModalBottomSheet(BuildContext context) async { final channel = StreamChannel.of(context).channel; - final galleryHeaderThemeData = - StreamChatTheme.of(context).galleryHeaderTheme; + final galleryHeaderThemeData = StreamChatTheme.of(context).galleryHeaderTheme; final defaultModal = AttachmentActionsModal( attachment: attachment, @@ -153,7 +147,8 @@ class StreamGalleryHeader extends StatelessWidget onReply: onReplyMessage, ); - final effectiveModal = attachmentActionsModalBuilder?.call( + final effectiveModal = + attachmentActionsModalBuilder?.call( context, attachment, defaultModal, diff --git a/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.g.dart b/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.g.dart index c6a7fc2001..f9f154eb59 100644 --- a/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.g.dart +++ b/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.g.dart @@ -503,16 +503,14 @@ abstract final class StreamSvgIcons { ); /// Stream SVG icon named 'filetypePresentationStandard'. - static const StreamSvgIconData filetypePresentationStandard = - StreamSvgIconData( + static const StreamSvgIconData filetypePresentationStandard = StreamSvgIconData( 'lib/assets/icons/colored/icon_filetype_presentation_standard.svg', package: package, preserveColors: true, ); /// Stream SVG icon named 'filetypePresentationSpecial'. - static const StreamSvgIconData filetypePresentationSpecial = - StreamSvgIconData( + static const StreamSvgIconData filetypePresentationSpecial = StreamSvgIconData( 'lib/assets/icons/colored/icon_filetype_presentation_special.svg', package: package, preserveColors: true, @@ -652,8 +650,7 @@ abstract final class StreamSvgIcons { ); /// Stream SVG icon named 'filetypeSpreadsheetStandard'. - static const StreamSvgIconData filetypeSpreadsheetStandard = - StreamSvgIconData( + static const StreamSvgIconData filetypeSpreadsheetStandard = StreamSvgIconData( 'lib/assets/icons/colored/icon_filetype_spreadsheet_standard.svg', package: package, preserveColors: true, @@ -828,8 +825,7 @@ abstract final class StreamSvgIcons { ); /// Stream SVG icon named 'filetypeCompressionStandard'. - static const StreamSvgIconData filetypeCompressionStandard = - StreamSvgIconData( + static const StreamSvgIconData filetypeCompressionStandard = StreamSvgIconData( 'lib/assets/icons/colored/icon_filetype_compression_standard.svg', package: package, preserveColors: true, diff --git a/packages/stream_chat_flutter/lib/src/indicators/typing_indicator.dart b/packages/stream_chat_flutter/lib/src/indicators/typing_indicator.dart index fdf8e3a001..b3cf76d0d4 100644 --- a/packages/stream_chat_flutter/lib/src/indicators/typing_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/indicators/typing_indicator.dart @@ -34,17 +34,15 @@ class StreamTypingIndicator extends StatelessWidget { @override Widget build(BuildContext context) { - final channelState = - channel?.state ?? StreamChannel.of(context).channel.state!; + final channelState = channel?.state ?? StreamChannel.of(context).channel.state!; final altWidget = alternativeWidget ?? const Empty(); return BetterStreamBuilder>( initialData: channelState.typingEvents.keys, - stream: channelState.typingEventsStream.map((typingEvents) => typingEvents - .entries - .where((element) => element.value.parentId == parentId) - .map((e) => e.key)), + stream: channelState.typingEventsStream.map( + (typingEvents) => typingEvents.entries.where((element) => element.value.parentId == parentId).map((e) => e.key), + ), builder: (context, users) => AnimatedSwitcher( layoutBuilder: (currentChild, previousChildren) => Stack( children: [ diff --git a/packages/stream_chat_flutter/lib/src/indicators/unread_indicator.dart b/packages/stream_chat_flutter/lib/src/indicators/unread_indicator.dart index f035edb700..0ed9d5dbc0 100644 --- a/packages/stream_chat_flutter/lib/src/indicators/unread_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/indicators/unread_indicator.dart @@ -37,25 +37,25 @@ class StreamUnreadIndicator extends StatelessWidget { final stream = switch (_unreadType) { _TotalUnreadCount() => client.state.totalUnreadCountStream, _UnreadChannels(cid: final cid) => switch (cid) { - final cid? => client.state.channels[cid]?.state?.unreadCountStream, - _ => client.state.unreadChannelsStream, - }, + final cid? => client.state.channels[cid]?.state?.unreadCountStream, + _ => client.state.unreadChannelsStream, + }, _UnreadThreads(id: final id) => switch (id) { - // TODO: Handle id once it's supported - _ => client.state.unreadThreadsStream, - } + // TODO: Handle id once it's supported + _ => client.state.unreadThreadsStream, + }, }; final initialData = switch (_unreadType) { _TotalUnreadCount() => client.state.totalUnreadCount, _UnreadChannels(cid: final cid) => switch (cid) { - final cid? => client.state.channels[cid]?.state?.unreadCount, - _ => client.state.unreadChannels, - }, + final cid? => client.state.channels[cid]?.state?.unreadCount, + _ => client.state.unreadChannels, + }, _UnreadThreads(id: final id) => switch (id) { - // TODO: Handle id once it's supported - _ => client.state.unreadThreads, - } + // TODO: Handle id once it's supported + _ => client.state.unreadThreads, + }, }; return IgnorePointer( diff --git a/packages/stream_chat_flutter/lib/src/indicators/upload_progress_indicator.dart b/packages/stream_chat_flutter/lib/src/indicators/upload_progress_indicator.dart index 7a6b289814..c28dba83b6 100644 --- a/packages/stream_chat_flutter/lib/src/indicators/upload_progress_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/indicators/upload_progress_indicator.dart @@ -59,7 +59,8 @@ class StreamUploadProgressIndicator extends StatelessWidget { const SizedBox(width: 8), Text( '${_percentage.toInt()}%', - style: textStyle ?? + style: + textStyle ?? theme.textTheme.footnote.copyWith( color: theme.colorTheme.barsBg, ), diff --git a/packages/stream_chat_flutter/lib/src/keyboard_shortcuts/keyboard_shortcut_runner.dart b/packages/stream_chat_flutter/lib/src/keyboard_shortcuts/keyboard_shortcut_runner.dart index 706adad454..fa2383a17b 100644 --- a/packages/stream_chat_flutter/lib/src/keyboard_shortcuts/keyboard_shortcut_runner.dart +++ b/packages/stream_chat_flutter/lib/src/keyboard_shortcuts/keyboard_shortcut_runner.dart @@ -37,8 +37,7 @@ class KeyboardShortcutRunner extends StatelessWidget { shortcuts: { if (onEnterKeypress != null) enterKeySet: EnterKeyIntent(), if (onEscapeKeypress != null) escapeKeySet: EscapeKeyIntent(), - if (onRightArrowKeypress != null) - rightArrowKeySet: RightArrowKeyIntent(), + if (onRightArrowKeypress != null) rightArrowKeySet: RightArrowKeyIntent(), if (onLeftArrowKeypress != null) leftArrowKeySet: LeftArrowKeyIntent(), }, actions: { diff --git a/packages/stream_chat_flutter/lib/src/localization/stream_chat_localizations.dart b/packages/stream_chat_flutter/lib/src/localization/stream_chat_localizations.dart index 66c09e8847..bc7da0a593 100644 --- a/packages/stream_chat_flutter/lib/src/localization/stream_chat_localizations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/stream_chat_localizations.dart @@ -1,6 +1,5 @@ import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/localization/translations.dart' - show Translations; +import 'package:stream_chat_flutter/src/localization/translations.dart' show Translations; /// Defines the localized resource values used by the StreamChatFlutter widgets. /// diff --git a/packages/stream_chat_flutter/lib/src/localization/translations.dart b/packages/stream_chat_flutter/lib/src/localization/translations.dart index 21320fc960..823bead692 100644 --- a/packages/stream_chat_flutter/lib/src/localization/translations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/translations.dart @@ -609,8 +609,7 @@ class DefaultTranslations implements Translations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Uploading $remaining/$total ...'; + }) => 'Uploading $remaining/$total ...'; @override String pinnedByUserText({ @@ -623,8 +622,7 @@ class DefaultTranslations implements Translations { } @override - String get sendMessagePermissionError => - "You don't have permission to send messages"; + String get sendMessagePermissionError => "You don't have permission to send messages"; @override String get emptyMessagesText => 'There are no messages currently'; @@ -697,8 +695,7 @@ class DefaultTranslations implements Translations { 'The file is too large to upload. The file size limit is $limitInMB MB.'; @override - String get couldNotReadBytesFromFileError => - 'Could not read bytes from file.'; + String get couldNotReadBytesFromFileError => 'Could not read bytes from file.'; @override String get addAFileLabel => 'Add a file'; @@ -752,8 +749,7 @@ class DefaultTranslations implements Translations { String get flagMessageSuccessfulLabel => 'Message flagged'; @override - String get flagMessageSuccessfulText => - 'The message has been reported to a moderator.'; + String get flagMessageSuccessfulText => 'The message has been reported to a moderator.'; @override String get deleteLabel => 'DELETE'; @@ -762,12 +758,10 @@ class DefaultTranslations implements Translations { String get deleteMessageLabel => 'Delete Message'; @override - String get deleteMessageQuestion => - 'Are you sure you want to permanently delete this message?'; + String get deleteMessageQuestion => 'Are you sure you want to permanently delete this message?'; @override - String get operationCouldNotBeCompletedText => - "The operation couldn't be completed."; + String get operationCouldNotBeCompletedText => "The operation couldn't be completed."; @override String get replyLabel => 'Reply'; @@ -845,8 +839,7 @@ class DefaultTranslations implements Translations { String get letsStartChattingLabel => 'Let’s start chatting!'; @override - String get sendingFirstMessageLabel => - 'How about sending your first message to a friend?'; + String get sendingFirstMessageLabel => 'How about sending your first message to a friend?'; @override String get startAChatLabel => 'Start a chat'; @@ -858,8 +851,7 @@ class DefaultTranslations implements Translations { String get deleteConversationLabel => 'Delete Conversation'; @override - String get deleteConversationQuestion => - 'Are you sure you want to delete this conversation?'; + String get deleteConversationQuestion => 'Are you sure you want to delete this conversation?'; @override String get streamChatLabel => 'Stream Chat'; @@ -898,8 +890,7 @@ class DefaultTranslations implements Translations { String get leaveConversationLabel => 'Leave conversation'; @override - String get leaveConversationQuestion => - 'Are you sure you want to leave this conversation?'; + String get leaveConversationQuestion => 'Are you sure you want to leave this conversation?'; @override String get showInChatLabel => 'Show in Chat'; @@ -935,8 +926,7 @@ class DefaultTranslations implements Translations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} of $totalPages'; + }) => '${currentPage + 1} of $totalPages'; @override String get fileText => 'File'; @@ -1003,8 +993,7 @@ Attachment limit exceeded: it's not possible to add more than $limit attachments } @override - String get linkDisabledDetails => - 'Sending links is not allowed in this conversation.'; + String get linkDisabledDetails => 'Sending links is not allowed in this conversation.'; @override String get linkDisabledError => 'Links are disabled'; @@ -1013,7 +1002,8 @@ Attachment limit exceeded: it's not possible to add more than $limit attachments String unreadMessagesSeparatorText() => 'New messages'; @override - String get enableFileAccessMessage => 'Please enable access to files' + String get enableFileAccessMessage => + 'Please enable access to files' '\nso you can share them with friends.'; @override @@ -1114,15 +1104,13 @@ Attachment limit exceeded: it's not possible to add more than $limit attachments String get enterYourCommentLabel => 'Enter your comment'; @override - String get endVoteConfirmationText => - 'Are you sure you want to end the vote?'; + String get endVoteConfirmationText => 'Are you sure you want to end the vote?'; @override String get deletePollOptionLabel => 'Delete Option'; @override - String get deletePollOptionQuestion => - 'Are you sure you want to delete this option?'; + String get deletePollOptionQuestion => 'Are you sure you want to delete this option?'; @override String get createLabel => 'Create'; @@ -1166,10 +1154,10 @@ Attachment limit exceeded: it's not possible to add more than $limit attachments @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 votes', - 1 => '1 vote', - _ => '$count votes', - }; + null || < 1 => '0 votes', + 1 => '1 vote', + _ => '$count votes', + }; @override String get noPollVotesLabel => 'There are no poll votes currently'; @@ -1196,8 +1184,7 @@ Attachment limit exceeded: it's not possible to add more than $limit attachments String get sendAnywayLabel => 'Send Anyway'; @override - String get moderatedMessageBlockedText => - 'Message was blocked by moderation policies'; + String get moderatedMessageBlockedText => 'Message was blocked by moderation policies'; @override String get moderationReviewModalTitle => 'Are you sure?'; diff --git a/packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart b/packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart index 18bad5a42e..4523ccdf5a 100644 --- a/packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart +++ b/packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart @@ -197,7 +197,7 @@ class StreamMessageActionsBuilder { final action = switch (isPinned) { true => UnpinMessage(message: message), - false => PinMessage(message: message) + false => PinMessage(message: message), }; messageActions.add( @@ -232,8 +232,7 @@ class StreamMessageActionsBuilder { ); } - if (message.user case final messageUser? - when channel.config?.mutes == true && !isSentByCurrentUser) { + if (message.user case final messageUser? when channel.config?.mutes == true && !isSentByCurrentUser) { final mutedUsers = currentUser?.mutes.map((mute) => mute.target.id); final isMuted = mutedUsers?.contains(messageUser.id) ?? false; final label = context.translations.toggleMuteUnmuteUserText; diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart index c2914d2007..7d100766f8 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart @@ -248,8 +248,7 @@ extension StreamImagePickerX on StreamAttachmentPickerController { final image = await asset.originFile; if (image != null) { final tempDir = await getTemporaryDirectory(); - final cachedFile = - File('${tempDir.path}/${image.path.split('/').last}'); + final cachedFile = File('${tempDir.path}/${image.path.split('/').last}'); if (cachedFile.existsSync()) { cachedFile.deleteSync(); } diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart index 4c742b7a70..0444963628 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart @@ -79,12 +79,10 @@ class StreamTabbedAttachmentPickerBottomSheet extends StatefulWidget { final ValueSetter? onSendValue; @override - State createState() => - _StreamTabbedAttachmentPickerBottomSheetState(); + State createState() => _StreamTabbedAttachmentPickerBottomSheetState(); } -class _StreamTabbedAttachmentPickerBottomSheetState - extends State { +class _StreamTabbedAttachmentPickerBottomSheetState extends State { // The current option selected in the tabbed attachment picker. late var _currentOption = _calculateInitialOption(); TabbedAttachmentPickerOption _calculateInitialOption() { @@ -196,11 +194,11 @@ class _TabbedAttachmentPickerOptions extends StatelessWidget { final onPressed = switch (onSendValue) { final onSendValue? when isValueChanged => () { - final result = AttachmentsPicked( - attachments: value.attachments, - ); - return onSendValue(result); - }, + final result = AttachmentsPicked( + attachments: value.attachments, + ); + return onSendValue(result); + }, _ => null, }; @@ -223,11 +221,12 @@ class _TabbedAttachmentPickerOptions extends StatelessWidget { /// Signature used by [EndOfFrameCallbackWidget.errorBuilder] to create a /// replacement widget to render. -typedef EndOfFrameCallbackErrorWidgetBuilder = Widget Function( - BuildContext context, - Object error, - StackTrace? stackTrace, -); +typedef EndOfFrameCallbackErrorWidgetBuilder = + Widget Function( + BuildContext context, + Object error, + StackTrace? stackTrace, + ); /// Function signature for a callback that is called when the end of the frame /// is reached. @@ -255,8 +254,7 @@ class EndOfFrameCallbackWidget extends StatefulWidget { final EndOfFrameCallbackErrorWidgetBuilder? errorBuilder; @override - State createState() => - _EndOfFrameCallbackWidgetState(); + State createState() => _EndOfFrameCallbackWidgetState(); } class _EndOfFrameCallbackWidgetState extends State { diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart index cbfbe318f0..13d309ff64 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart @@ -11,8 +11,8 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// The function receives the [BuildContext] and a list of [defaultOptions] /// that can be modified or extended. /// {@endtemplate} -typedef AttachmentPickerOptionsBuilder - = List Function(BuildContext context, List defaultOptions); +typedef AttachmentPickerOptionsBuilder = + List Function(BuildContext context, List defaultOptions); /// Shows a modal bottom sheet with the Stream attachment picker. /// @@ -163,7 +163,8 @@ class StreamAttachmentPickerBottomSheetBuilder extends StatefulWidget { BuildContext context, StreamAttachmentPickerController controller, Widget? child, - ) builder; + ) + builder; /// The initial poll. final Poll? initialPoll; @@ -178,18 +179,17 @@ class StreamAttachmentPickerBottomSheetBuilder extends StatefulWidget { final StreamAttachmentPickerController? controller; @override - State createState() => - _StreamAttachmentPickerBottomSheetBuilderState(); + State createState() => _StreamAttachmentPickerBottomSheetBuilderState(); } -class _StreamAttachmentPickerBottomSheetBuilderState - extends State { +class _StreamAttachmentPickerBottomSheetBuilderState extends State { late StreamAttachmentPickerController _controller; @override void initState() { super.initState(); - _controller = widget.controller ?? + _controller = + widget.controller ?? StreamAttachmentPickerController( initialPoll: widget.initialPoll, initialAttachments: widget.initialAttachments, diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_controller.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_controller.dart index 30add6c40b..53821955f2 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_controller.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_controller.dart @@ -10,8 +10,7 @@ const kDefaultMaxAttachmentSize = 100 * 1024 * 1024; // 100MB in Bytes const kDefaultMaxAttachmentCount = 10; /// Controller class for [StreamAttachmentPicker]. -class StreamAttachmentPickerController - extends ValueNotifier { +class StreamAttachmentPickerController extends ValueNotifier { /// Creates a new instance of [StreamAttachmentPickerController]. factory StreamAttachmentPickerController({ Poll? initialPoll, @@ -35,11 +34,11 @@ class StreamAttachmentPickerController this.initialValue, { this.maxAttachmentSize = kDefaultMaxAttachmentSize, this.maxAttachmentCount = kDefaultMaxAttachmentCount, - }) : assert( - (initialValue.attachments.length) <= maxAttachmentCount, - '''The initial attachments count must be less than or equal to maxAttachmentCount''', - ), - super(initialValue); + }) : assert( + (initialValue.attachments.length) <= maxAttachmentCount, + '''The initial attachments count must be less than or equal to maxAttachmentCount''', + ), + super(initialValue); /// Initial value for the controller. final AttachmentPickerValue initialValue; @@ -106,14 +105,16 @@ class StreamAttachmentPickerController // Cache the attachment in a temporary file. final tempFilePath = await _saveToCache(file); - value = value.copyWith(attachments: [ - ...value.attachments, - attachment.copyWith( - file: file.copyWith( - path: tempFilePath, + value = value.copyWith( + attachments: [ + ...value.attachments, + attachment.copyWith( + file: file.copyWith( + path: tempFilePath, + ), ), - ), - ]); + ], + ); } /// Removes the specified [attachment] from the message. @@ -271,9 +272,9 @@ class AttachmentTooLargeError extends StreamChatError { required this.fileSize, required this.maxSize, }) : super( - 'The size of the attachment is $fileSize bytes, ' - 'but the maximum size allowed is $maxSize bytes.', - ); + 'The size of the attachment is $fileSize bytes, ' + 'but the maximum size allowed is $maxSize bytes.', + ); /// The actual size of the attachment in bytes. final int fileSize; @@ -285,7 +286,8 @@ class AttachmentTooLargeError extends StreamChatError { List get props => [...super.props, fileSize, maxSize]; @override - String toString() => 'AttachmentTooLargeError: ' + String toString() => + 'AttachmentTooLargeError: ' 'The size of the attachment is $fileSize bytes, ' 'but the maximum size allowed is $maxSize bytes.'; } @@ -309,6 +311,7 @@ class AttachmentLimitReachedError extends StreamChatError { List get props => [...super.props, maxCount]; @override - String toString() => 'AttachmentLimitReachedError: ' + String toString() => + 'AttachmentLimitReachedError: ' 'The maximum number of attachments is $maxCount.'; } diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_option.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_option.dart index 1eec1d4638..b5d7ca6d3b 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_option.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_option.dart @@ -3,16 +3,18 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/message_input/attachment_picker/stream_attachment_picker_controller.dart'; /// Function signature for building the attachment picker option view. -typedef AttachmentPickerOptionViewBuilder = Widget Function( - BuildContext context, - StreamAttachmentPickerController controller, -); +typedef AttachmentPickerOptionViewBuilder = + Widget Function( + BuildContext context, + StreamAttachmentPickerController controller, + ); /// Function signature for system attachment picker option callback. -typedef OnSystemAttachmentPickerOptionTap = Future Function( - BuildContext context, - StreamAttachmentPickerController controller, -); +typedef OnSystemAttachmentPickerOptionTap = + Future Function( + BuildContext context, + StreamAttachmentPickerController controller, + ); /// Base class for attachment picker options. abstract class AttachmentPickerOption { diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_result.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_result.dart index 1a2e681f33..dc4bbf563c 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_result.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_result.dart @@ -4,8 +4,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Signature for a function that is called when a attachment picker result /// is received. -typedef OnAttachmentPickerResult - = FutureOr Function(T result); +typedef OnAttachmentPickerResult = FutureOr Function(T result); /// {@template streamAttachmentPickerAction} /// A sealed class that represents different results that can be returned diff --git a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_controller.dart b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_controller.dart index 1c34f3b9fc..63b19a711c 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_controller.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_controller.dart @@ -30,9 +30,9 @@ class StreamAudioRecorderController extends ValueNotifier { config: switch (config) { final config? => config, _ => const RecordConfig( - numChannels: 1, - encoder: kIsWeb ? AudioEncoder.wav : AudioEncoder.aacLc, - ), + numChannels: 1, + encoder: kIsWeb ? AudioEncoder.wav : AudioEncoder.aacLc, + ), }, ); } @@ -44,8 +44,8 @@ class StreamAudioRecorderController extends ValueNotifier { required AudioRecorder recorder, AudioRecorderState initialState = const RecordStateIdle(), Duration amplitudeInterval = const Duration(milliseconds: 100), - }) : _recorder = recorder, - super(initialState) { + }) : _recorder = recorder, + super(initialState) { // Listen to the recorder amplitude changes _recorderAmplitudeSubscription = _recorder .onAmplitudeChanged(amplitudeInterval) // diff --git a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_feedback.dart b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_feedback.dart index 9bf98d2e1b..d33ce45657 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_feedback.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_feedback.dart @@ -175,13 +175,13 @@ class AudioRecorderFeedbackWrapper extends AudioRecorderFeedback { _FeedbackCallback? onCancel, _FeedbackCallback? onStartCancel, _FeedbackCallback? onStop, - }) : _onStop = onStop, - _onStartCancel = onStartCancel, - _onCancel = onCancel, - _onLock = onLock, - _onFinish = onFinish, - _onPause = onPause, - _onStart = onStart; + }) : _onStop = onStop, + _onStartCancel = onStartCancel, + _onCancel = onCancel, + _onLock = onLock, + _onFinish = onFinish, + _onPause = onPause, + _onStart = onStart; // Callback for when recording starts. final _FeedbackCallback? _onStart; diff --git a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_state.dart b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_state.dart index 95bdeff0bb..96bb1f00cc 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_state.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_state.dart @@ -51,13 +51,13 @@ sealed class RecordStateRecording extends AudioRecorderState { }) { return switch (this) { RecordStateRecordingHold() => RecordStateRecordingHold( - duration: duration ?? this.duration, - waveform: waveform ?? this.waveform, - ), + duration: duration ?? this.duration, + waveform: waveform ?? this.waveform, + ), RecordStateRecordingLocked() => RecordStateRecordingLocked( - duration: duration ?? this.duration, - waveform: waveform ?? this.waveform, - ), + duration: duration ?? this.duration, + waveform: waveform ?? this.waveform, + ), }; } } diff --git a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart index 241b1f5efb..66ddfe0394 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart @@ -21,11 +21,12 @@ import 'package:stream_chat_flutter/src/utils/extensions.dart'; /// - [StreamAudioRecorderButton], which uses this builder function. /// - [StreamAudioRecorderState], which provides the state of the recorder. /// {@endtemplate} -typedef AudioRecorderBuilder = Widget Function( - BuildContext, - AudioRecorderState, - Widget, -); +typedef AudioRecorderBuilder = + Widget Function( + BuildContext, + AudioRecorderState, + Widget, + ); /// {@template streamAudioRecorderButton} /// A configurable audio recording button with interactive states and gestures. @@ -177,44 +178,44 @@ class StreamAudioRecorderButton extends StatelessWidget { builder: (context, state, recordButton) => switch (state) { // Show only the record button if the recording is not in progress. RecordStateIdle() => RecordStateIdleContent( - state: state, - recordButton: recordButton, - ), + state: state, + recordButton: recordButton, + ), RecordStateRecordingHold() => RecordStateHoldRecordingContent( - state: state, - recordButton: recordButton, - cancelThreshold: cancelRecordThreshold, - ), + state: state, + recordButton: recordButton, + cancelThreshold: cancelRecordThreshold, + ), RecordStateRecordingLocked() => RecordStateLockedRecordingContent( - state: state, - onRecordEnd: () async { - await feedback.onRecordFinish(context); - return onRecordFinish?.call(); - }, - onRecordPause: () async { - await feedback.onRecordPause(context); - return onRecordPause?.call(); - }, - onRecordCancel: () async { - await feedback.onRecordCancel(context); - return onRecordCancel?.call(); - }, - onRecordStop: () async { - await feedback.onRecordStop(context); - return onRecordStop?.call(); - }, - ), + state: state, + onRecordEnd: () async { + await feedback.onRecordFinish(context); + return onRecordFinish?.call(); + }, + onRecordPause: () async { + await feedback.onRecordPause(context); + return onRecordPause?.call(); + }, + onRecordCancel: () async { + await feedback.onRecordCancel(context); + return onRecordCancel?.call(); + }, + onRecordStop: () async { + await feedback.onRecordStop(context); + return onRecordStop?.call(); + }, + ), RecordStateStopped() => RecordStateStoppedContent( - state: state, - onRecordCancel: () async { - await feedback.onRecordCancel(context); - return onRecordCancel?.call(); - }, - onRecordFinish: () async { - await feedback.onRecordFinish(context); - return onRecordFinish?.call(); - }, - ), + state: state, + onRecordCancel: () async { + await feedback.onRecordCancel(context); + return onRecordCancel?.call(); + }, + onRecordFinish: () async { + await feedback.onRecordFinish(context); + return onRecordFinish?.call(); + }, + ), }, ), ); @@ -461,7 +462,7 @@ class RecordStateLockedRecordingContent extends StatelessWidget { icon: const StreamSvgIcon(icon: StreamSvgIcons.checkSend), color: theme.colorTheme.accentPrimary, onPressed: onRecordEnd, - ) + ), ], ), ], @@ -498,8 +499,7 @@ class RecordStateStoppedContent extends StatefulWidget { final VoidCallback? onRecordFinish; @override - State createState() => - _RecordStateStoppedContentState(); + State createState() => _RecordStateStoppedContentState(); } class _RecordStateStoppedContentState extends State { @@ -615,7 +615,7 @@ class _RecordStateStoppedContentState extends State { icon: const StreamSvgIcon(icon: StreamSvgIcons.checkSend), color: theme.colorTheme.accentPrimary, onPressed: widget.onRecordFinish, - ) + ), ], ), ], @@ -718,21 +718,21 @@ class PlaybackControlButton extends StatelessWidget { }, icon: switch (state) { TrackState.loading => Builder( - builder: (context) { - final iconTheme = IconTheme.of(context); - return SizedBox.fromSize( - size: Size.square(iconTheme.size!), - child: Padding( - padding: const EdgeInsets.all(8), - child: CircularProgressIndicator.adaptive( - valueColor: AlwaysStoppedAnimation( - theme.colorTheme.accentPrimary, - ), + builder: (context) { + final iconTheme = IconTheme.of(context); + return SizedBox.fromSize( + size: Size.square(iconTheme.size!), + child: Padding( + padding: const EdgeInsets.all(8), + child: CircularProgressIndicator.adaptive( + valueColor: AlwaysStoppedAnimation( + theme.colorTheme.accentPrimary, ), ), - ); - }, - ), + ), + ); + }, + ), TrackState.idle => const StreamSvgIcon(icon: StreamSvgIcons.play), TrackState.paused => const StreamSvgIcon(icon: StreamSvgIcons.play), TrackState.playing => const StreamSvgIcon(icon: StreamSvgIcons.pause), @@ -949,13 +949,16 @@ class _SlideTransitionWidgetState extends State @override Widget build(BuildContext context) { - final position = Tween( - begin: widget.begin, - end: widget.end, - ).animate(CurvedAnimation( - parent: _controller, - curve: widget.curve, - )); + final position = + Tween( + begin: widget.begin, + end: widget.end, + ).animate( + CurvedAnimation( + parent: _controller, + curve: widget.curve, + ), + ); return SlideTransition( position: position, @@ -985,8 +988,8 @@ class HoldToRecordInfoTooltip extends StatelessWidget { Widget build(BuildContext context) { final theme = StreamChatTheme.of(context); - const recordButtonWidth = kDefaultMessageInputIconSize + - kDefaultMessageInputIconPadding * 2; // right, left padding. + const recordButtonWidth = + kDefaultMessageInputIconSize + kDefaultMessageInputIconPadding * 2; // right, left padding. const arrowSize = Size(recordButtonWidth / 2, 6); diff --git a/packages/stream_chat_flutter/lib/src/message_input/dm_checkbox_list_tile.dart b/packages/stream_chat_flutter/lib/src/message_input/dm_checkbox_list_tile.dart index e9b0d7c372..6f3c41d08d 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/dm_checkbox_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/dm_checkbox_list_tile.dart @@ -50,9 +50,9 @@ class DmCheckboxListTile extends StatelessWidget { shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(3)), onChanged: switch (onChanged) { final onChanged? => (value) { - if (value == null) return; - return onChanged.call(value); - }, + if (value == null) return; + return onChanged.call(value); + }, _ => null, }, ), diff --git a/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart b/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart index 931ea553bc..20277f085b 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart @@ -113,11 +113,9 @@ class _QuotedMessage extends StatelessWidget { bool get _containsText => message.text?.isNotEmpty == true; - bool get _containsLinkAttachment => - message.attachments.any((it) => it.type == AttachmentType.urlPreview); + bool get _containsLinkAttachment => message.attachments.any((it) => it.type == AttachmentType.urlPreview); - bool get _isGiphy => message.attachments - .any((element) => element.type == AttachmentType.giphy); + bool get _isGiphy => message.attachments.any((element) => element.type == AttachmentType.giphy); bool get _isDeleted => message.isDeleted || message.deletedAt != null; @@ -176,19 +174,18 @@ class _QuotedMessage extends StatelessWidget { ), if (msg.text!.isNotEmpty && !_isGiphy) Flexible( - child: textBuilder?.call(context, msg) ?? + child: + textBuilder?.call(context, msg) ?? StreamMessageText( message: msg, messageTheme: isOnlyEmoji && _containsText ? messageTheme.copyWith( - messageTextStyle: - messageTheme.messageTextStyle?.copyWith( + messageTextStyle: messageTheme.messageTextStyle?.copyWith( fontSize: 32, ), ) : messageTheme.copyWith( - messageTextStyle: - messageTheme.messageTextStyle?.copyWith( + messageTextStyle: messageTheme.messageTextStyle?.copyWith( fontSize: 12, ), ), @@ -224,8 +221,7 @@ class _QuotedMessage extends StatelessWidget { child: Row( spacing: 8, mainAxisSize: MainAxisSize.min, - mainAxisAlignment: - reverse ? MainAxisAlignment.end : MainAxisAlignment.start, + mainAxisAlignment: reverse ? MainAxisAlignment.end : MainAxisAlignment.start, children: reverse ? children.reversed.toList() : children, ), ); @@ -270,8 +266,7 @@ class _ParseAttachments extends StatelessWidget { var clipBehavior = Clip.none; ShapeDecoration? decoration; - if (attachment.type != AttachmentType.file && - attachment.type != AttachmentType.voiceRecording) { + if (attachment.type != AttachmentType.file && attachment.type != AttachmentType.voiceRecording) { clipBehavior = Clip.hardEdge; decoration = ShapeDecoration( shape: RoundedRectangleBorder( diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart index 6d268935dc..d109eff1b8 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart @@ -21,10 +21,11 @@ const _kMentionTrigger = '@'; /// Signature for the function that determines if a [matchedUri] should be /// previewed as an OG Attachment. -typedef OgPreviewFilter = bool Function( - Uri matchedUri, - String messageText, -); +typedef OgPreviewFilter = + bool Function( + Uri matchedUri, + String messageText, + ); /// Different types of hints that can be shown in [StreamMessageInput]. enum HintType { @@ -47,10 +48,11 @@ enum HintType { typedef HintGetter = String? Function(BuildContext context, HintType type); /// The signature for the function that builds the list of actions. -typedef ActionsBuilder = List Function( - BuildContext context, - List defaultActions, -); +typedef ActionsBuilder = + List Function( + BuildContext context, + List defaultActions, + ); /// Inactive state: /// @@ -152,8 +154,7 @@ class StreamMessageInput extends StatefulWidget { this.onQuotedMessageCleared, this.enableActionAnimation = true, this.sendMessageKeyPredicate = _defaultSendMessageKeyPredicate, - this.clearQuotedMessageKeyPredicate = - _defaultClearQuotedMessageKeyPredicate, + this.clearQuotedMessageKeyPredicate = _defaultClearQuotedMessageKeyPredicate, this.ogPreviewFilter = _defaultOgPreviewFilter, this.hintGetter = _defaultHintGetter, this.contentInsertionConfiguration, @@ -307,8 +308,7 @@ class StreamMessageInput extends StatefulWidget { /// /// This is used to build the thumbnail for the attachment in the quoted /// message. - final Map? - quotedMessageAttachmentThumbnailBuilders; + final Map? quotedMessageAttachmentThumbnailBuilders; /// The focus node associated to the TextField. final FocusNode? focusNode; @@ -516,8 +516,7 @@ class StreamMessageInput extends StatefulWidget { } /// State of [StreamMessageInput] -class StreamMessageInputState extends State - with RestorationMixin { +class StreamMessageInputState extends State with RestorationMixin { bool get _commandEnabled => _effectiveController.message.command != null; bool _actionsShrunk = false; @@ -525,19 +524,16 @@ class StreamMessageInputState extends State late StreamChatThemeData _streamChatTheme; late StreamMessageInputThemeData _messageInputTheme; - bool get _hasQuotedMessage => - _effectiveController.message.quotedMessage != null; + bool get _hasQuotedMessage => _effectiveController.message.quotedMessage != null; bool get _isEditing => !_effectiveController.message.state.isInitial; late final _audioRecorderController = StreamAudioRecorderController(); - FocusNode get _effectiveFocusNode => - widget.focusNode ?? (_focusNode ??= FocusNode()); + FocusNode get _effectiveFocusNode => widget.focusNode ?? (_focusNode ??= FocusNode()); FocusNode? _focusNode; - StreamMessageInputController get _effectiveController => - widget.messageInputController ?? _controller!.value; + StreamMessageInputController get _effectiveController => widget.messageInputController ?? _controller!.value; StreamRestorableMessageInputController? _controller; void _createLocalController([Message? message]) { @@ -619,11 +615,9 @@ class StreamMessageInputState extends State @override void didUpdateWidget(covariant StreamMessageInput oldWidget) { super.didUpdateWidget(oldWidget); - if (widget.messageInputController == null && - oldWidget.messageInputController != null) { + if (widget.messageInputController == null && oldWidget.messageInputController != null) { _createLocalController(oldWidget.messageInputController!.message); - } else if (widget.messageInputController != null && - oldWidget.messageInputController == null) { + } else if (widget.messageInputController != null && oldWidget.messageInputController == null) { unregisterFromRestoration(_controller!); _controller!.dispose(); _controller = null; @@ -671,17 +665,17 @@ class StreamMessageInputState extends State final channel = StreamChannel.of(context).channel; final messageInput = switch (_buildAutocompleteMessageInput(context)) { final messageInput when channel.state != null => BetterStreamBuilder( - stream: channel.ownCapabilitiesStream.map(canSendOrUpdateMessage), - initialData: canSendOrUpdateMessage(channel.ownCapabilities), - builder: (context, enabled) { - // Allow the user to send messages if the user has the permission to - // send messages or if the user is editing a message. - if (enabled) return messageInput; - - // Otherwise, show the no permission message. - return _buildNoPermissionMessage(context); - }, - ), + stream: channel.ownCapabilitiesStream.map(canSendOrUpdateMessage), + initialData: canSendOrUpdateMessage(channel.ownCapabilities), + builder: (context, enabled) { + // Allow the user to send messages if the user has the permission to + // send messages or if the user is editing a message. + if (enabled) return messageInput; + + // Otherwise, show the no permission message. + return _buildNoPermissionMessage(context); + }, + ), final messageInput => messageInput, }; @@ -712,47 +706,48 @@ class StreamMessageInputState extends State StreamAutocompleteTrigger( trigger: _kCommandTrigger, triggerOnlyAtStart: true, - optionsViewBuilder: ( - context, - autocompleteQuery, - messageEditingController, - ) { - final query = autocompleteQuery.query; - return StreamCommandAutocompleteOptions( - query: query, - channel: StreamChannel.of(context).channel, - onCommandSelected: (command) { - _effectiveController.command = command.name; - // removing the overlay after the command is selected - StreamAutocomplete.of(context).closeSuggestions(); + optionsViewBuilder: + ( + context, + autocompleteQuery, + messageEditingController, + ) { + final query = autocompleteQuery.query; + return StreamCommandAutocompleteOptions( + query: query, + channel: StreamChannel.of(context).channel, + onCommandSelected: (command) { + _effectiveController.command = command.name; + // removing the overlay after the command is selected + StreamAutocomplete.of(context).closeSuggestions(); + }, + ); }, - ); - }, ), if (widget.enableMentionsOverlay) StreamAutocompleteTrigger( trigger: _kMentionTrigger, - optionsViewBuilder: ( - context, - autocompleteQuery, - messageEditingController, - ) { - final query = autocompleteQuery.query; - return StreamMentionAutocompleteOptions( - query: query, - channel: StreamChannel.of(context).channel, - mentionAllAppUsers: widget.mentionAllAppUsers, - mentionsTileBuilder: widget.userMentionsTileBuilder, - onMentionUserTap: (user) { - // adding the mentioned user to the controller. - _effectiveController.addMentionedUser(user); - - // accepting the autocomplete option. - StreamAutocomplete.of(context) - .acceptAutocompleteOption(user.name); + optionsViewBuilder: + ( + context, + autocompleteQuery, + messageEditingController, + ) { + final query = autocompleteQuery.query; + return StreamMentionAutocompleteOptions( + query: query, + channel: StreamChannel.of(context).channel, + mentionAllAppUsers: widget.mentionAllAppUsers, + mentionsTileBuilder: widget.userMentionsTileBuilder, + onMentionUserTap: (user) { + // adding the mentioned user to the controller. + _effectiveController.addMentionedUser(user); + + // accepting the autocomplete option. + StreamAutocomplete.of(context).acceptAutocompleteOption(user.name); + }, + ); }, - ); - }, ), ], ); @@ -838,17 +833,14 @@ class StreamMessageInputState extends State return Row( children: [ if (!isAudioRecordingFlowActive) ...[ - if (!_commandEnabled && - widget.actionsLocation == ActionsLocation.left) + if (!_commandEnabled && widget.actionsLocation == ActionsLocation.left) _buildExpandActionsButton(context), const SizedBox(width: 4), Expanded(child: _buildTextInput(context)), const SizedBox(width: 4), - if (!_commandEnabled && - widget.actionsLocation == ActionsLocation.right) + if (!_commandEnabled && widget.actionsLocation == ActionsLocation.right) _buildExpandActionsButton(context), - if (widget.sendButtonLocation == SendButtonLocation.outside) - _buildSendButton(context), + if (widget.sendButtonLocation == SendButtonLocation.outside) _buildSendButton(context), ], if (widget.enableVoiceRecording) Expanded( @@ -926,8 +918,8 @@ class StreamMessageInputState extends State firstChild: StreamMessageInputIconButton( color: _messageInputTheme.expandButtonColor, icon: Transform.rotate( - angle: (widget.actionsLocation == ActionsLocation.right || - widget.actionsLocation == ActionsLocation.rightInside) + angle: + (widget.actionsLocation == ActionsLocation.right || widget.actionsLocation == ActionsLocation.rightInside) ? pi : 0, child: const StreamSvgIcon(icon: StreamSvgIcons.emptyCircleRight), @@ -938,9 +930,7 @@ class StreamMessageInputState extends State } }, ), - secondChild: widget.disableAttachments && - !widget.showCommandsButton && - !(widget.actionsBuilder != null) + secondChild: widget.disableAttachments && !widget.showCommandsButton && !(widget.actionsBuilder != null) ? const Empty() : Row( spacing: widget.spaceBetweenActions, @@ -953,8 +943,7 @@ class StreamMessageInputState extends State List _actionsList() { final channel = StreamChannel.of(context).channel; final defaultActions = [ - if (!widget.disableAttachments && channel.canUploadFile) - _buildAttachmentButton(context), + if (!widget.disableAttachments && channel.canUploadFile) _buildAttachmentButton(context), if (widget.showCommandsButton && !_isEditing && channel.state != null && @@ -975,8 +964,7 @@ class StreamMessageInputState extends State onPressed: _onAttachmentButtonPressed, ); - return widget.attachmentButtonBuilder?.call(context, defaultButton) ?? - defaultButton; + return widget.attachmentButtonBuilder?.call(context, defaultButton) ?? defaultButton; } Future _onPollCreated(Poll poll) async { @@ -1016,8 +1004,7 @@ class StreamMessageInputState extends State final allowedTypes = _getAllowedAttachmentPickerTypes(); final messageInputTheme = StreamMessageInputTheme.of(context); - final useSystemPicker = widget.useSystemAttachmentPicker || - (messageInputTheme.useSystemAttachmentPicker ?? false); + final useSystemPicker = widget.useSystemAttachmentPicker || (messageInputTheme.useSystemAttachmentPicker ?? false); final result = await showStreamAttachmentPickerModalBottomSheet( context: context, @@ -1055,9 +1042,8 @@ class StreamMessageInputState extends State } Widget _buildTextInput(BuildContext context) { - final margin = (widget.sendButtonLocation == SendButtonLocation.inside - ? const EdgeInsets.only(right: 8) - : EdgeInsets.zero) + + final margin = + (widget.sendButtonLocation == SendButtonLocation.inside ? const EdgeInsets.only(right: 8) : EdgeInsets.zero) + (widget.actionsLocation != ActionsLocation.left || _commandEnabled ? const EdgeInsets.only(left: 8) : EdgeInsets.zero); @@ -1115,8 +1101,7 @@ class StreamMessageInputState extends State decoration: _getInputDecoration(context), textCapitalization: widget.textCapitalization, autocorrect: widget.autoCorrect, - contentInsertionConfiguration: - widget.contentInsertionConfiguration, + contentInsertionConfiguration: widget.contentInsertionConfiguration, ), ), ), @@ -1207,11 +1192,11 @@ class StreamMessageInputState extends State ), ) : (widget.actionsLocation == ActionsLocation.leftInside - ? Row( - mainAxisSize: MainAxisSize.min, - children: [_buildExpandActionsButton(context)], - ) - : null), + ? Row( + mainAxisSize: MainAxisSize.min, + children: [_buildExpandActionsButton(context)], + ) + : null), suffixIconConstraints: const BoxConstraints.tightFor(height: 40), prefixIconConstraints: const BoxConstraints.tightFor(height: 40), suffixIcon: Row( @@ -1227,11 +1212,9 @@ class StreamMessageInputState extends State onPressed: _effectiveController.clear, ), ), - if (!_commandEnabled && - widget.actionsLocation == ActionsLocation.rightInside) + if (!_commandEnabled && widget.actionsLocation == ActionsLocation.rightInside) _buildExpandActionsButton(context), - if (widget.sendButtonLocation == SendButtonLocation.inside) - _buildSendButton(context), + if (widget.sendButtonLocation == SendButtonLocation.inside) _buildSendButton(context), ].nonNulls.toList(), ), ).merge(passedDecoration); @@ -1306,8 +1289,7 @@ class StreamMessageInputState extends State final _parsedMatch = Uri.tryParse(it.group(0) ?? '')?.withScheme; if (_parsedMatch == null) return false; - return _parsedMatch.host.split('.').last.isValidTLD() && - widget.ogPreviewFilter.call(_parsedMatch, value); + return _parsedMatch.host.split('.').last.isValidTLD() && widget.ogPreviewFilter.call(_parsedMatch, value); }).toList(); // Reset the og attachment if the text doesn't contain any url @@ -1325,19 +1307,20 @@ class StreamMessageInputState extends State final client = StreamChat.maybeOf(context)?.client; if (client == null) return; - _enrichUrlOperation = CancelableOperation.fromFuture( - _enrichUrl(firstMatchedUrl, client), - ).then( - (ogAttachment) { - final attachment = Attachment.fromOGAttachment(ogAttachment); - _effectiveController.setOGAttachment(attachment); - }, - onError: (error, stackTrace) { - // Reset the ogAttachment if there was an error - _effectiveController.clearOGAttachment(); - widget.onError?.call(error, stackTrace); - }, - ); + _enrichUrlOperation = + CancelableOperation.fromFuture( + _enrichUrl(firstMatchedUrl, client), + ).then( + (ogAttachment) { + final attachment = Attachment.fromOGAttachment(ogAttachment); + _effectiveController.setOGAttachment(attachment); + }, + onError: (error, stackTrace) { + // Reset the ogAttachment if there was an error + _effectiveController.clearOGAttachment(); + widget.onError?.call(error, stackTrace); + }, + ); } final _ogAttachmentCache = {}; @@ -1380,16 +1363,17 @@ class StreamMessageInputState extends State message: quotedMessage, messageTheme: _streamChatTheme.otherMessageTheme, onQuotedMessageClear: widget.onQuotedMessageCleared, - attachmentThumbnailBuilders: - widget.quotedMessageAttachmentThumbnailBuilders, + attachmentThumbnailBuilders: widget.quotedMessageAttachmentThumbnailBuilders, ); } Widget _buildAttachments() { final attachments = _effectiveController.attachments; - final nonOGAttachments = attachments.where((it) { - return it.titleLink == null; - }).toList(growable: false); + final nonOGAttachments = attachments + .where((it) { + return it.titleLink == null; + }) + .toList(growable: false); // If there are no attachments, return an empty widget if (nonOGAttachments.isEmpty) return const Empty(); @@ -1415,8 +1399,7 @@ class StreamMessageInputState extends State voiceRecordingAttachmentBuilder: widget.voiceRecordingAttachmentBuilder, fileAttachmentBuilder: widget.fileAttachmentBuilder, mediaAttachmentBuilder: widget.mediaAttachmentBuilder, - voiceRecordingAttachmentListBuilder: - widget.voiceRecordingAttachmentListBuilder, + voiceRecordingAttachmentListBuilder: widget.voiceRecordingAttachmentListBuilder, ), ); } @@ -1442,8 +1425,8 @@ class StreamMessageInputState extends State color: s.isNotEmpty ? _streamChatTheme.colorTheme.disabled : (isCommandOptionsVisible - ? _messageInputTheme.actionButtonColor! - : _messageInputTheme.actionButtonIdleColor!), + ? _messageInputTheme.actionButtonColor! + : _messageInputTheme.actionButtonIdleColor!), onPressed: () async { // Clear the text if the commands options are already visible. if (isCommandOptionsVisible) { @@ -1460,8 +1443,7 @@ class StreamMessageInputState extends State }, ); - return widget.commandButtonBuilder?.call(context, defaultButton) ?? - defaultButton; + return widget.commandButtonBuilder?.call(context, defaultButton) ?? defaultButton; } /// Adds an attachment to the [messageInputController.attachments] map @@ -1497,8 +1479,9 @@ class StreamMessageInputState extends State var message = _effectiveController.value; if (!channel.canSendLinks && - _urlRegex.allMatches(message.text ?? '').any((element) => - element.group(0)?.split('.').last.isValidTLD() == true)) { + _urlRegex + .allMatches(message.text ?? '') + .any((element) => element.group(0)?.split('.').last.isValidTLD() == true)) { showInfoBottomSheet( context, icon: StreamSvgIcon( diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart index b3fa41e960..ede7608ab7 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart @@ -14,21 +14,23 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// /// see more: /// - [StreamMessageInputAttachmentList] -typedef AttachmentListBuilder = Widget Function( - BuildContext context, - List attachments, - ValueSetter? onRemovePressed, -); +typedef AttachmentListBuilder = + Widget Function( + BuildContext context, + List attachments, + ValueSetter? onRemovePressed, + ); /// WidgetBuilder used to build the message input attachment item. /// /// see more: /// - [StreamMessageInputAttachmentList] -typedef AttachmentItemBuilder = Widget Function( - BuildContext context, - Attachment attachment, - ValueSetter? onRemovePressed, -); +typedef AttachmentItemBuilder = + Widget Function( + BuildContext context, + Attachment attachment, + ValueSetter? onRemovePressed, + ); /// {@template stream_message_input_attachment_list} /// Widget used to display the list of attachments added to the message input. @@ -108,49 +110,50 @@ class StreamMessageInputAttachmentList extends StatelessWidget { padding: const EdgeInsets.only(top: 6), child: Column( mainAxisSize: MainAxisSize.min, - children: [ - if (media.isNotEmpty) - Flexible( - child: switch (mediaAttachmentListBuilder) { - final builder? => builder(context, media, onRemovePressed), - _ => MessageInputMediaAttachments( - attachments: media, - attachmentBuilder: mediaAttachmentBuilder, - onRemovePressed: onRemovePressed, - ), - }, - ), - if (voices.isNotEmpty) - Flexible( - child: switch (voiceRecordingAttachmentListBuilder) { - final builder? => builder(context, voices, onRemovePressed), - _ => MessageInputVoiceRecordingAttachments( - attachments: voices, - attachmentBuilder: voiceRecordingAttachmentBuilder, - onRemovePressed: onRemovePressed, - ), - }, - ), - if (files.isNotEmpty) - Flexible( - child: switch (fileAttachmentListBuilder) { - final builder? => builder(context, files, onRemovePressed), - _ => MessageInputFileAttachments( - attachments: files, - attachmentBuilder: fileAttachmentBuilder, - onRemovePressed: onRemovePressed, - ), - }, + children: + [ + if (media.isNotEmpty) + Flexible( + child: switch (mediaAttachmentListBuilder) { + final builder? => builder(context, media, onRemovePressed), + _ => MessageInputMediaAttachments( + attachments: media, + attachmentBuilder: mediaAttachmentBuilder, + onRemovePressed: onRemovePressed, + ), + }, + ), + if (voices.isNotEmpty) + Flexible( + child: switch (voiceRecordingAttachmentListBuilder) { + final builder? => builder(context, voices, onRemovePressed), + _ => MessageInputVoiceRecordingAttachments( + attachments: voices, + attachmentBuilder: voiceRecordingAttachmentBuilder, + onRemovePressed: onRemovePressed, + ), + }, + ), + if (files.isNotEmpty) + Flexible( + child: switch (fileAttachmentListBuilder) { + final builder? => builder(context, files, onRemovePressed), + _ => MessageInputFileAttachments( + attachments: files, + attachmentBuilder: fileAttachmentBuilder, + onRemovePressed: onRemovePressed, + ), + }, + ), + ].insertBetween( + Divider( + height: 16, + indent: 16, + endIndent: 16, + thickness: 1, + color: StreamChatTheme.of(context).colorTheme.disabled, + ), ), - ].insertBetween( - Divider( - height: 16, - indent: 16, - endIndent: 16, - thickness: 1, - color: StreamChatTheme.of(context).colorTheme.disabled, - ), - ), ), ); } @@ -183,33 +186,35 @@ class MessageInputFileAttachments extends StatelessWidget { shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.symmetric(horizontal: 8), - children: attachments.reversed.map( - (attachment) { - // If a custom builder is provided, use it. - final builder = attachmentBuilder; - if (builder != null) { - return builder(context, attachment, onRemovePressed); - } - - // Otherwise, use the default builder. - return StreamFileAttachment( - message: Message(), // Dummy message - file: attachment, - constraints: BoxConstraints.loose(Size( - MediaQuery.of(context).size.width * 0.65, - 56, - )), - trailing: Padding( - padding: const EdgeInsets.all(8), - child: RemoveAttachmentButton( - onPressed: onRemovePressed != null - ? () => onRemovePressed!(attachment) - : null, - ), - ), - ); - }, - ).insertBetween(const SizedBox(height: 8)), + children: attachments.reversed + .map( + (attachment) { + // If a custom builder is provided, use it. + final builder = attachmentBuilder; + if (builder != null) { + return builder(context, attachment, onRemovePressed); + } + + // Otherwise, use the default builder. + return StreamFileAttachment( + message: Message(), // Dummy message + file: attachment, + constraints: BoxConstraints.loose( + Size( + MediaQuery.of(context).size.width * 0.65, + 56, + ), + ), + trailing: Padding( + padding: const EdgeInsets.all(8), + child: RemoveAttachmentButton( + onPressed: onRemovePressed != null ? () => onRemovePressed!(attachment) : null, + ), + ), + ); + }, + ) + .insertBetween(const SizedBox(height: 8)), ); } } @@ -237,12 +242,10 @@ class MessageInputVoiceRecordingAttachments extends StatefulWidget { final ValueSetter? onRemovePressed; @override - State createState() => - _MessageInputVoiceRecordingAttachmentsState(); + State createState() => _MessageInputVoiceRecordingAttachmentsState(); } -class _MessageInputVoiceRecordingAttachmentsState - extends State { +class _MessageInputVoiceRecordingAttachmentsState extends State { late final _controller = StreamAudioPlaylistController( widget.attachments.toPlaylist(), ); @@ -370,20 +373,22 @@ class MessageInputMediaAttachments extends StatelessWidget { scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 8), cacheExtent: 104 * 10, // Cache 10 items ahead. - children: attachments.map( - (attachment) { - // If a custom builder is provided, use it. - final builder = attachmentBuilder; - if (builder != null) { - return builder(context, attachment, onRemovePressed); - } - - return StreamMediaAttachmentBuilder( - attachment: attachment, - onRemovePressed: onRemovePressed, - ); - }, - ).insertBetween(const SizedBox(width: 8)), + children: attachments + .map( + (attachment) { + // If a custom builder is provided, use it. + final builder = attachmentBuilder; + if (builder != null) { + return builder(context, attachment, onRemovePressed); + } + + return StreamMediaAttachmentBuilder( + attachment: attachment, + onRemovePressed: onRemovePressed, + ); + }, + ) + .insertBetween(const SizedBox(width: 8)), ), ); } @@ -392,8 +397,7 @@ class MessageInputMediaAttachments extends StatelessWidget { /// Widget used to display a media type attachment item. class StreamMediaAttachmentBuilder extends StatelessWidget { /// Creates a new media attachment item. - const StreamMediaAttachmentBuilder( - {super.key, required this.attachment, this.onRemovePressed}); + const StreamMediaAttachmentBuilder({super.key, required this.attachment, this.onRemovePressed}); /// The media attachment to display. final Attachment attachment; @@ -437,9 +441,7 @@ class StreamMediaAttachmentBuilder extends StatelessWidget { top: 8, right: 8, child: RemoveAttachmentButton( - onPressed: onRemovePressed != null - ? () => onRemovePressed!(attachment) - : null, + onPressed: onRemovePressed != null ? () => onRemovePressed!(attachment) : null, ), ), ], diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_text_field.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_text_field.dart index 0279be811d..b6e9391a79 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_text_field.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_text_field.dart @@ -9,12 +9,7 @@ import 'package:flutter/services.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; export 'package:flutter/services.dart' - show - TextInputType, - TextInputAction, - TextCapitalization, - SmartQuotesType, - SmartDashesType; + show TextInputType, TextInputAction, TextCapitalization, SmartQuotesType, SmartDashesType; /// A widget the wraps the [TextField] and adds some StreamChat specifics. class StreamMessageTextField extends StatefulWidget { @@ -119,42 +114,36 @@ class StreamMessageTextField extends StatefulWidget { this.scribbleEnabled = true, this.enableIMEPersonalizedLearning = true, this.contentInsertionConfiguration, - }) : assert(obscuringCharacter.length == 1, ''), - smartDashesType = smartDashesType ?? - (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled), - smartQuotesType = smartQuotesType ?? - (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled), - assert(maxLines == null || maxLines > 0, ''), - assert(minLines == null || minLines > 0, ''), - assert( - (maxLines == null) || (minLines == null) || (maxLines >= minLines), - "minLines can't be greater than maxLines", - ), - assert( - !expands || (maxLines == null && minLines == null), - 'minLines and maxLines must be null when expands is true.', - ), - assert(!obscureText || maxLines == 1, - 'Obscured fields cannot be multiline.'), - assert( - maxLength == null || - maxLength == TextField.noMaxLength || - maxLength > 0, - 'maxLength must be null or a positive integer.'), - - // Assert the following instead of setting it directly to avoid - // surprising the user by silently changing the value they set. - assert( - !identical(textInputAction, TextInputAction.newline) || - maxLines == 1 || - !identical(keyboardType, TextInputType.text), - 'Use keyboardType TextInputType.multiline when using ' - 'TextInputAction.newline on a multiline TextField.', - ), - keyboardType = keyboardType ?? - (maxLines == 1 ? TextInputType.text : TextInputType.multiline), - enableInteractiveSelection = - enableInteractiveSelection ?? (!readOnly || !obscureText); + }) : assert(obscuringCharacter.length == 1, ''), + smartDashesType = smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled), + smartQuotesType = smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled), + assert(maxLines == null || maxLines > 0, ''), + assert(minLines == null || minLines > 0, ''), + assert( + (maxLines == null) || (minLines == null) || (maxLines >= minLines), + "minLines can't be greater than maxLines", + ), + assert( + !expands || (maxLines == null && minLines == null), + 'minLines and maxLines must be null when expands is true.', + ), + assert(!obscureText || maxLines == 1, 'Obscured fields cannot be multiline.'), + assert( + maxLength == null || maxLength == TextField.noMaxLength || maxLength > 0, + 'maxLength must be null or a positive integer.', + ), + + // Assert the following instead of setting it directly to avoid + // surprising the user by silently changing the value they set. + assert( + !identical(textInputAction, TextInputAction.newline) || + maxLines == 1 || + !identical(keyboardType, TextInputType.text), + 'Use keyboardType TextInputType.multiline when using ' + 'TextInputAction.newline on a multiline TextField.', + ), + keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline), + enableInteractiveSelection = enableInteractiveSelection ?? (!readOnly || !obscureText); /// Controls the message being edited. /// @@ -522,93 +511,78 @@ class StreamMessageTextField extends StatefulWidget { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties - ..add(DiagnosticsProperty( - 'controller', controller, - defaultValue: null)) - ..add(DiagnosticsProperty('focusNode', focusNode, - defaultValue: null)) + ..add(DiagnosticsProperty('controller', controller, defaultValue: null)) + ..add(DiagnosticsProperty('focusNode', focusNode, defaultValue: null)) ..add(DiagnosticsProperty('enabled', enabled, defaultValue: null)) - ..add(DiagnosticsProperty('decoration', decoration, - defaultValue: const InputDecoration())) - ..add(DiagnosticsProperty('keyboardType', keyboardType, - defaultValue: TextInputType.text)) + ..add(DiagnosticsProperty('decoration', decoration, defaultValue: const InputDecoration())) + ..add(DiagnosticsProperty('keyboardType', keyboardType, defaultValue: TextInputType.text)) ..add(DiagnosticsProperty('style', style, defaultValue: null)) - ..add(DiagnosticsProperty('autofocus', autofocus, - defaultValue: false)) - ..add(DiagnosticsProperty( - 'obscuringCharacter', obscuringCharacter, - defaultValue: '•')) - ..add(DiagnosticsProperty('obscureText', obscureText, - defaultValue: false)) - ..add(DiagnosticsProperty('autocorrect', autocorrect, - defaultValue: true)) - ..add(EnumProperty('smartDashesType', smartDashesType, - defaultValue: - obscureText ? SmartDashesType.disabled : SmartDashesType.enabled)) - ..add(EnumProperty('smartQuotesType', smartQuotesType, - defaultValue: - obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled)) - ..add(DiagnosticsProperty('enableSuggestions', enableSuggestions, - defaultValue: true)) + ..add(DiagnosticsProperty('autofocus', autofocus, defaultValue: false)) + ..add(DiagnosticsProperty('obscuringCharacter', obscuringCharacter, defaultValue: '•')) + ..add(DiagnosticsProperty('obscureText', obscureText, defaultValue: false)) + ..add(DiagnosticsProperty('autocorrect', autocorrect, defaultValue: true)) + ..add( + EnumProperty( + 'smartDashesType', + smartDashesType, + defaultValue: obscureText ? SmartDashesType.disabled : SmartDashesType.enabled, + ), + ) + ..add( + EnumProperty( + 'smartQuotesType', + smartQuotesType, + defaultValue: obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled, + ), + ) + ..add(DiagnosticsProperty('enableSuggestions', enableSuggestions, defaultValue: true)) ..add(IntProperty('maxLines', maxLines, defaultValue: 1)) ..add(IntProperty('minLines', minLines, defaultValue: null)) ..add(DiagnosticsProperty('expands', expands, defaultValue: false)) ..add(IntProperty('maxLength', maxLength, defaultValue: null)) - ..add(EnumProperty( - 'maxLengthEnforcement', maxLengthEnforcement, - defaultValue: null)) - ..add(EnumProperty('textInputAction', textInputAction, - defaultValue: null)) - ..add(EnumProperty( - 'textCapitalization', textCapitalization, - defaultValue: TextCapitalization.none)) - ..add(EnumProperty('textAlign', textAlign, - defaultValue: TextAlign.start)) - ..add(DiagnosticsProperty( - 'textAlignVertical', textAlignVertical, - defaultValue: null)) - ..add(EnumProperty('textDirection', textDirection, - defaultValue: null)) + ..add(EnumProperty('maxLengthEnforcement', maxLengthEnforcement, defaultValue: null)) + ..add(EnumProperty('textInputAction', textInputAction, defaultValue: null)) + ..add( + EnumProperty( + 'textCapitalization', + textCapitalization, + defaultValue: TextCapitalization.none, + ), + ) + ..add(EnumProperty('textAlign', textAlign, defaultValue: TextAlign.start)) + ..add(DiagnosticsProperty('textAlignVertical', textAlignVertical, defaultValue: null)) + ..add(EnumProperty('textDirection', textDirection, defaultValue: null)) ..add(DoubleProperty('cursorWidth', cursorWidth, defaultValue: 2.0)) ..add(DoubleProperty('cursorHeight', cursorHeight, defaultValue: null)) - ..add(DiagnosticsProperty('cursorRadius', cursorRadius, - defaultValue: null)) + ..add(DiagnosticsProperty('cursorRadius', cursorRadius, defaultValue: null)) ..add(ColorProperty('cursorColor', cursorColor, defaultValue: null)) - ..add(DiagnosticsProperty( - 'keyboardAppearance', keyboardAppearance, - defaultValue: null)) - ..add(DiagnosticsProperty( - 'scrollPadding', scrollPadding, - defaultValue: const EdgeInsets.all(20))) - ..add(FlagProperty('selectionEnabled', - value: selectionEnabled, - defaultValue: true, - ifFalse: 'selection disabled')) - ..add(DiagnosticsProperty( - 'selectionControls', selectionControls, - defaultValue: null)) - ..add(DiagnosticsProperty( - 'scrollController', scrollController, - defaultValue: null)) - ..add(DiagnosticsProperty('scrollPhysics', scrollPhysics, - defaultValue: null)) - ..add(DiagnosticsProperty('clipBehavior', clipBehavior, - defaultValue: Clip.hardEdge)) - ..add(DiagnosticsProperty('scribbleEnabled', scribbleEnabled, - defaultValue: true)) - ..add(DiagnosticsProperty( - 'enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, - defaultValue: true)) - ..add(DiagnosticsProperty( - 'contentInsertionConfiguration', contentInsertionConfiguration, - defaultValue: null)); + ..add(DiagnosticsProperty('keyboardAppearance', keyboardAppearance, defaultValue: null)) + ..add( + DiagnosticsProperty('scrollPadding', scrollPadding, defaultValue: const EdgeInsets.all(20)), + ) + ..add( + FlagProperty('selectionEnabled', value: selectionEnabled, defaultValue: true, ifFalse: 'selection disabled'), + ) + ..add(DiagnosticsProperty('selectionControls', selectionControls, defaultValue: null)) + ..add(DiagnosticsProperty('scrollController', scrollController, defaultValue: null)) + ..add(DiagnosticsProperty('scrollPhysics', scrollPhysics, defaultValue: null)) + ..add(DiagnosticsProperty('clipBehavior', clipBehavior, defaultValue: Clip.hardEdge)) + ..add(DiagnosticsProperty('scribbleEnabled', scribbleEnabled, defaultValue: true)) + ..add( + DiagnosticsProperty('enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, defaultValue: true), + ) + ..add( + DiagnosticsProperty( + 'contentInsertionConfiguration', + contentInsertionConfiguration, + defaultValue: null, + ), + ); } } -class _StreamMessageTextFieldState extends State - with RestorationMixin { - StreamMessageInputController get _effectiveController => - widget.controller ?? _controller!.value; +class _StreamMessageTextFieldState extends State with RestorationMixin { + StreamMessageInputController get _effectiveController => widget.controller ?? _controller!.value; StreamRestorableMessageInputController? _controller; @override @@ -653,63 +627,62 @@ class _StreamMessageTextFieldState extends State @override Widget build(BuildContext context) => TextField( - controller: _effectiveController.textFieldController, - focusNode: widget.focusNode, - decoration: widget.decoration, - keyboardType: widget.keyboardType, - textInputAction: widget.textInputAction ?? - (widget.keyboardType == TextInputType.multiline - ? TextInputAction.newline - : TextInputAction.send), - textCapitalization: widget.textCapitalization, - style: widget.style, - strutStyle: widget.strutStyle, - textAlign: widget.textAlign, - textAlignVertical: widget.textAlignVertical, - textDirection: widget.textDirection, - readOnly: widget.readOnly, - showCursor: widget.showCursor, - autofocus: widget.autofocus, - obscuringCharacter: widget.obscuringCharacter, - obscureText: widget.obscureText, - autocorrect: widget.autocorrect, - smartDashesType: widget.smartDashesType, - smartQuotesType: widget.smartQuotesType, - enableSuggestions: widget.enableSuggestions, - maxLines: widget.maxLines, - minLines: widget.minLines, - expands: widget.expands, - maxLength: widget.maxLength, - maxLengthEnforcement: widget.maxLengthEnforcement, - onEditingComplete: widget.onEditingComplete, - onSubmitted: widget.onSubmitted, - onAppPrivateCommand: widget.onAppPrivateCommand, - inputFormatters: widget.inputFormatters, - enabled: widget.enabled, - cursorWidth: widget.cursorWidth, - cursorHeight: widget.cursorHeight, - cursorRadius: widget.cursorRadius, - cursorColor: widget.cursorColor, - selectionHeightStyle: widget.selectionHeightStyle, - selectionWidthStyle: widget.selectionWidthStyle, - keyboardAppearance: widget.keyboardAppearance, - scrollPadding: widget.scrollPadding, - dragStartBehavior: widget.dragStartBehavior, - enableInteractiveSelection: widget.enableInteractiveSelection, - selectionControls: widget.selectionControls, - onTap: widget.onTap, - mouseCursor: widget.mouseCursor, - buildCounter: widget.buildCounter, - scrollController: widget.scrollController, - scrollPhysics: widget.scrollPhysics, - autofillHints: widget.autofillHints, - clipBehavior: widget.clipBehavior, - restorationId: widget.restorationId, - // ignore: deprecated_member_use - scribbleEnabled: widget.scribbleEnabled, - enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, - contentInsertionConfiguration: widget.contentInsertionConfiguration, - ); + controller: _effectiveController.textFieldController, + focusNode: widget.focusNode, + decoration: widget.decoration, + keyboardType: widget.keyboardType, + textInputAction: + widget.textInputAction ?? + (widget.keyboardType == TextInputType.multiline ? TextInputAction.newline : TextInputAction.send), + textCapitalization: widget.textCapitalization, + style: widget.style, + strutStyle: widget.strutStyle, + textAlign: widget.textAlign, + textAlignVertical: widget.textAlignVertical, + textDirection: widget.textDirection, + readOnly: widget.readOnly, + showCursor: widget.showCursor, + autofocus: widget.autofocus, + obscuringCharacter: widget.obscuringCharacter, + obscureText: widget.obscureText, + autocorrect: widget.autocorrect, + smartDashesType: widget.smartDashesType, + smartQuotesType: widget.smartQuotesType, + enableSuggestions: widget.enableSuggestions, + maxLines: widget.maxLines, + minLines: widget.minLines, + expands: widget.expands, + maxLength: widget.maxLength, + maxLengthEnforcement: widget.maxLengthEnforcement, + onEditingComplete: widget.onEditingComplete, + onSubmitted: widget.onSubmitted, + onAppPrivateCommand: widget.onAppPrivateCommand, + inputFormatters: widget.inputFormatters, + enabled: widget.enabled, + cursorWidth: widget.cursorWidth, + cursorHeight: widget.cursorHeight, + cursorRadius: widget.cursorRadius, + cursorColor: widget.cursorColor, + selectionHeightStyle: widget.selectionHeightStyle, + selectionWidthStyle: widget.selectionWidthStyle, + keyboardAppearance: widget.keyboardAppearance, + scrollPadding: widget.scrollPadding, + dragStartBehavior: widget.dragStartBehavior, + enableInteractiveSelection: widget.enableInteractiveSelection, + selectionControls: widget.selectionControls, + onTap: widget.onTap, + mouseCursor: widget.mouseCursor, + buildCounter: widget.buildCounter, + scrollController: widget.scrollController, + scrollPhysics: widget.scrollPhysics, + autofillHints: widget.autofillHints, + clipBehavior: widget.clipBehavior, + restorationId: widget.restorationId, + // ignore: deprecated_member_use + scribbleEnabled: widget.scribbleEnabled, + enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, + contentInsertionConfiguration: widget.contentInsertionConfiguration, + ); @override void dispose() { diff --git a/packages/stream_chat_flutter/lib/src/message_input/tld.dart b/packages/stream_chat_flutter/lib/src/message_input/tld.dart index 2bfa52578f..f340af4719 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/tld.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/tld.dart @@ -2,9 +2,7 @@ extension TLDString on String { /// Returns true if the string is a valid TLD. bool isValidTLD() => - isNotEmpty && - tlds.containsKey(this[0].toUpperCase()) && - tlds[this[0].toUpperCase()]!.contains(toUpperCase()); + isNotEmpty && tlds.containsKey(this[0].toUpperCase()) && tlds[this[0].toUpperCase()]!.contains(toUpperCase()); } /// List of valid TLDs. diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/message_details.dart b/packages/stream_chat_flutter/lib/src/message_list_view/message_details.dart index 36ffd887bf..cc5c99eccc 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/message_details.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/message_details.dart @@ -13,10 +13,8 @@ class MessageDetails { this.index, ) { isMyMessage = message.user?.id == currentUserId; - isLastUser = index + 1 < messages.length && - message.user?.id == messages[index + 1].user?.id; - isNextUser = - index - 1 >= 0 && message.user!.id == messages[index - 1].user?.id; + isLastUser = index + 1 < messages.length && message.user?.id == messages[index + 1].user?.id; + isNextUser = index - 1 >= 0 && message.user!.id == messages[index - 1].user?.id; } /// True if the message belongs to the current user diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart index 5b5be446db..c4be763db1 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart @@ -207,7 +207,8 @@ class StreamMessageListView extends StatefulWidget { final Widget Function( int unreadCount, Future Function(int) scrollToBottomDefaultTapAction, - )? scrollToBottomBuilder; + )? + scrollToBottomBuilder; /// If true will show an indicator with number of unread messages /// that will scroll to latest read message when tapped and mark @@ -323,12 +324,10 @@ class StreamMessageListView extends StatefulWidget { final OnMessageLongPress? onMessageLongPress; /// Builder used to build the thread separator in case it's a thread view - final Function(BuildContext context, Message parentMessage)? - threadSeparatorBuilder; + final Function(BuildContext context, Message parentMessage)? threadSeparatorBuilder; /// Builder used to build the unread message separator - final Widget Function(BuildContext context, int unreadCount)? - unreadMessagesSeparatorBuilder; + final Widget Function(BuildContext context, int unreadCount)? unreadMessagesSeparatorBuilder; /// A [MessageListController] allows pagination. /// @@ -394,8 +393,7 @@ class _StreamMessageListViewState extends State { late final _defaultController = MessageListController(); - MessageListController get _messageListController => - widget.messageListController ?? _defaultController; + MessageListController get _messageListController => widget.messageListController ?? _defaultController; StreamSubscription? _messageNewListener; StreamSubscription? _userReadListener; @@ -407,10 +405,8 @@ class _StreamMessageListViewState extends State { super.initState(); _scrollController = widget.scrollController ?? ItemScrollController(); - _itemPositionListener = - widget.itemPositionListener ?? ItemPositionsListener.create(); - _itemPositionListener.itemPositions - .addListener(_handleItemPositionsChanged); + _itemPositionListener = widget.itemPositionListener ?? ItemPositionsListener.create(); + _itemPositionListener.itemPositions.addListener(_handleItemPositionsChanged); _getOnThreadTap(); } @@ -448,14 +444,12 @@ class _StreamMessageListViewState extends State { ); } - _messageNewListener = - streamChannel!.channel.on(EventType.messageNew).listen((event) { + _messageNewListener = streamChannel!.channel.on(EventType.messageNew).listen((event) { if (_upToDate) { _bottomPaginationActive = false; } if (event.message?.parentId == widget.parentMessage?.id && - event.message!.user!.id == - streamChannel!.channel.client.state.currentUser!.id) { + event.message!.user!.id == streamChannel!.channel.client.state.currentUser!.id) { setState(() => unreadCount = 0); WidgetsBinding.instance.addPostFrameCallback((_) { @@ -481,8 +475,7 @@ class _StreamMessageListViewState extends State { debouncedMarkThreadRead.cancel(); _messageNewListener?.cancel(); _userReadListener?.cancel(); - _itemPositionListener.itemPositions - .removeListener(_handleItemPositionsChanged); + _itemPositionListener.itemPositions.removeListener(_handleItemPositionsChanged); super.dispose(); } @@ -494,36 +487,38 @@ class _StreamMessageListViewState extends State { child: MessageListCore( paginationLimit: widget.paginationLimit, messageFilter: widget.messageFilter, - loadingBuilder: widget.loadingBuilder ?? + loadingBuilder: + widget.loadingBuilder ?? (context) => const Center( - child: CircularProgressIndicator.adaptive(), - ), - emptyBuilder: widget.emptyBuilder ?? + child: CircularProgressIndicator.adaptive(), + ), + emptyBuilder: + widget.emptyBuilder ?? (context) => Center( - child: Text( - context.translations.emptyChatMessagesText, - style: _streamTheme.textTheme.footnote.copyWith( - color: _streamTheme.colorTheme.textHighEmphasis - // ignore: deprecated_member_use - .withOpacity(0.5), - ), - ), + child: Text( + context.translations.emptyChatMessagesText, + style: _streamTheme.textTheme.footnote.copyWith( + color: _streamTheme.colorTheme.textHighEmphasis + // ignore: deprecated_member_use + .withOpacity(0.5), ), - messageListBuilder: widget.messageListBuilder ?? - (context, list) => _buildListView(list), + ), + ), + messageListBuilder: widget.messageListBuilder ?? (context, list) => _buildListView(list), messageListController: _messageListController, parentMessage: widget.parentMessage, - errorBuilder: widget.errorBuilder ?? + errorBuilder: + widget.errorBuilder ?? (BuildContext context, Object error) => Center( - child: Text( - context.translations.genericErrorText, - style: _streamTheme.textTheme.footnote.copyWith( - color: _streamTheme.colorTheme.textHighEmphasis - // ignore: deprecated_member_use - .withOpacity(0.5), - ), - ), + child: Text( + context.translations.genericErrorText, + style: _streamTheme.textTheme.footnote.copyWith( + color: _streamTheme.colorTheme.textHighEmphasis + // ignore: deprecated_member_use + .withOpacity(0.5), ), + ), + ), ), ), ); @@ -543,8 +538,7 @@ class _StreamMessageListViewState extends State { final first = _itemPositionListener.itemPositions.value.first; final diff = newMessagesListLength - _messageListLength!; if (diff > 0) { - if (messages[0].user?.id != - streamChannel!.channel.client.state.currentUser?.id) { + if (messages[0].user?.id != streamChannel!.channel.client.state.currentUser?.id) { initialIndex = first.index + diff; initialAlignment = first.itemLeadingEdge; } @@ -555,10 +549,11 @@ class _StreamMessageListViewState extends State { _messageListLength = newMessagesListLength; - final itemCount = messages.length + // total messages - 2 + // top + bottom loading indicator - 2 + // header + footer - 1 // parent message + final itemCount = + messages.length + // total messages + 2 + // top + bottom loading indicator + 2 + // header + footer + 1 // parent message ; final child = Stack( @@ -666,7 +661,6 @@ class _StreamMessageListViewState extends State { // BottomLoader -> 1 (count-7) // Separator(Footer -> 8??30) -> 0 (count-8) // Footer -> 0 (count-8) - separatorBuilder: (context, i) { Widget maybeBuildWithUnreadMessagesSeparator({ required Message message, @@ -693,8 +687,7 @@ class _StreamMessageListViewState extends State { } if (widget.threadSeparatorBuilder != null) { - return widget.threadSeparatorBuilder! - .call(context, widget.parentMessage!); + return widget.threadSeparatorBuilder!.call(context, widget.parentMessage!); } return ThreadSeparator( @@ -702,9 +695,7 @@ class _StreamMessageListViewState extends State { ); } if (i == itemCount - 3) { - if (widget.reverse - ? widget.headerBuilder == null - : widget.footerBuilder == null) { + if (widget.reverse ? widget.headerBuilder == null : widget.footerBuilder == null) { if (messages.isNotEmpty) { final message = messages.last; return maybeBuildWithUnreadMessagesSeparator( @@ -719,9 +710,7 @@ class _StreamMessageListViewState extends State { return const SizedBox(height: 8); } if (i == 0) { - if (widget.reverse - ? widget.footerBuilder == null - : widget.headerBuilder == null) { + if (widget.reverse ? widget.footerBuilder == null : widget.headerBuilder == null) { return const SizedBox(height: 30); } return const SizedBox(height: 8); @@ -740,8 +729,7 @@ class _StreamMessageListViewState extends State { Widget separator; - final isPartOfThread = message.replyCount! > 0 || - message.showInChannel == true; + final isPartOfThread = message.replyCount! > 0 || message.showInChannel == true; final createdAt = Jiffy.parseFromDateTime( message.createdAt.toLocal(), @@ -759,8 +747,7 @@ class _StreamMessageListViewState extends State { unit: Unit.minute, ); - final isNextUserSame = - message.user!.id == nextMessage.user?.id; + final isNextUserSame = message.user!.id == nextMessage.user?.id; final isDeleted = message.isDeleted; final spacingRules = [ @@ -795,16 +782,13 @@ class _StreamMessageListViewState extends State { if (i == itemCount - 2) { if (widget.reverse) { - return widget.headerBuilder?.call(context) ?? - const Empty(); + return widget.headerBuilder?.call(context) ?? const Empty(); } else { - return widget.footerBuilder?.call(context) ?? - const Empty(); + return widget.footerBuilder?.call(context) ?? const Empty(); } } - final indicatorBuilder = - widget.paginationLoadingIndicatorBuilder; + final indicatorBuilder = widget.paginationLoadingIndicatorBuilder; if (i == itemCount - 3) { return LoadingIndicator( @@ -828,11 +812,9 @@ class _StreamMessageListViewState extends State { if (i == 0) { if (widget.reverse) { - return widget.footerBuilder?.call(context) ?? - const Empty(); + return widget.footerBuilder?.call(context) ?? const Empty(); } else { - return widget.headerBuilder?.call(context) ?? - const Empty(); + return widget.headerBuilder?.call(context) ?? const Empty(); } } @@ -892,10 +874,8 @@ class _StreamMessageListViewState extends State { ], ); - final backgroundColor = - StreamMessageListViewTheme.of(context).backgroundColor; - final backgroundImage = - StreamMessageListViewTheme.of(context).backgroundImage; + final backgroundColor = StreamMessageListViewTheme.of(context).backgroundColor; + final backgroundImage = StreamMessageListViewTheme.of(context).backgroundImage; if (backgroundColor != null || backgroundImage != null) { return DecoratedBox( @@ -913,15 +893,14 @@ class _StreamMessageListViewState extends State { Widget _buildUnreadMessagesSeparator(int unreadCount) { final unreadMessagesSeparator = widget.unreadMessagesSeparatorBuilder?.call(context, unreadCount) ?? - UnreadMessagesSeparator(unreadCount: unreadCount); + UnreadMessagesSeparator(unreadCount: unreadCount); return unreadMessagesSeparator; } Future _paginateData( StreamChannelState? channel, QueryDirection direction, - ) => - _messageListController.paginateData!(direction: direction); + ) => _messageListController.paginateData!(direction: direction); Future scrollToBottomDefaultTapAction(int unreadCount) async { // If the channel is not up to date, we need to reload it before scrolling @@ -992,30 +971,27 @@ class _StreamMessageListViewState extends State { return switch (widget.dateDividerBuilder) { final builder? => builder(createdAt), _ => Padding( - padding: const EdgeInsets.symmetric(vertical: 12), - child: StreamDateDivider(dateTime: createdAt), - ), + padding: const EdgeInsets.symmetric(vertical: 12), + child: StreamDateDivider(dateTime: createdAt), + ), }; } Widget buildParentMessage( Message message, ) { - final isMyMessage = - message.user!.id == StreamChat.of(context).currentUser!.id; + final isMyMessage = message.user!.id == StreamChat.of(context).currentUser!.id; final isOnlyEmoji = message.text?.isOnlyEmoji ?? false; - final hasFileAttachment = - message.attachments.any((it) => it.type == AttachmentType.file); + final hasFileAttachment = message.attachments.any((it) => it.type == AttachmentType.file); - final hasUrlAttachment = - message.attachments.any((it) => it.type == AttachmentType.urlPreview); + final hasUrlAttachment = message.attachments.any((it) => it.type == AttachmentType.urlPreview); final attachmentBorderRadius = hasUrlAttachment ? 8.0 : hasFileAttachment - ? 12.0 - : 14.0; + ? 12.0 + : 14.0; final borderSide = isOnlyEmoji ? BorderSide.none : null; @@ -1038,8 +1014,8 @@ class _StreamMessageListViewState extends State { hasUrlAttachment ? 8 : hasFileAttachment - ? 4 - : 2, + ? 4 + : 2, ), attachmentShape: RoundedRectangleBorder( side: BorderSide( @@ -1048,13 +1024,9 @@ class _StreamMessageListViewState extends State { ), borderRadius: BorderRadius.only( topLeft: Radius.circular(attachmentBorderRadius), - bottomLeft: isMyMessage - ? Radius.circular(attachmentBorderRadius) - : Radius.zero, + bottomLeft: isMyMessage ? Radius.circular(attachmentBorderRadius) : Radius.zero, topRight: Radius.circular(attachmentBorderRadius), - bottomRight: isMyMessage - ? Radius.zero - : Radius.circular(attachmentBorderRadius), + bottomRight: isMyMessage ? Radius.zero : Radius.circular(attachmentBorderRadius), ), ), borderRadiusGeometry: BorderRadius.only( @@ -1069,9 +1041,7 @@ class _StreamMessageListViewState extends State { ), borderSide: borderSide, showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show, - messageTheme: isMyMessage - ? _streamTheme.ownMessageTheme - : _streamTheme.otherMessageTheme, + messageTheme: isMyMessage ? _streamTheme.ownMessageTheme : _streamTheme.otherMessageTheme, onMessageTap: widget.onMessageTap, onMessageLongPress: widget.onMessageLongPress, ); @@ -1103,10 +1073,11 @@ class _StreamMessageListViewState extends State { scrollToBottomDefaultTapAction, ); } - final showUnreadCount = unreadCount > 0 && - streamChannel!.channel.state!.members.any((e) => - e.userId == - streamChannel!.channel.client.state.currentUser!.id); + final showUnreadCount = + unreadCount > 0 && + streamChannel!.channel.state!.members.any( + (e) => e.userId == streamChannel!.channel.client.state.currentUser!.id, + ); return Positioned( bottom: 8, @@ -1139,8 +1110,7 @@ class _StreamMessageListViewState extends State { child: Center( child: Material( borderRadius: BorderRadius.circular(8), - color: - StreamChatTheme.of(context).colorTheme.accentPrimary, + color: StreamChatTheme.of(context).colorTheme.accentPrimary, child: Padding( padding: const EdgeInsets.only( left: 5, @@ -1215,8 +1185,7 @@ class _StreamMessageListViewState extends State { final userId = StreamChat.of(context).currentUser!.id; final isMyMessage = message.user?.id == userId; final nextMessage = index - 1 >= 0 ? messages[index - 1] : null; - final isNextUserSame = - nextMessage != null && message.user!.id == nextMessage.user!.id; + final isNextUserSame = nextMessage != null && message.user!.id == nextMessage.user!.id; var hasTimeDiff = false; if (nextMessage != null) { @@ -1228,47 +1197,40 @@ class _StreamMessageListViewState extends State { hasTimeDiff = !createdAt.isSame(nextCreatedAt, unit: Unit.minute); } - final hasVoiceRecordingAttachment = message.attachments - .any((it) => it.type == AttachmentType.voiceRecording); + final hasVoiceRecordingAttachment = message.attachments.any((it) => it.type == AttachmentType.voiceRecording); - final hasFileAttachment = - message.attachments.any((it) => it.type == AttachmentType.file); + final hasFileAttachment = message.attachments.any((it) => it.type == AttachmentType.file); - final hasUrlAttachment = - message.attachments.any((it) => it.type == AttachmentType.urlPreview); + final hasUrlAttachment = message.attachments.any((it) => it.type == AttachmentType.urlPreview); - final isThreadMessage = - message.parentId != null && message.showInChannel == true; + final isThreadMessage = message.parentId != null && message.showInChannel == true; final hasReplies = message.replyCount! > 0; final attachmentBorderRadius = hasUrlAttachment ? 8.0 : hasFileAttachment - ? 12.0 - : 14.0; + ? 12.0 + : 14.0; - final showTimeStamp = (!isThreadMessage || _isThreadConversation) && - !hasReplies && - (hasTimeDiff || !isNextUserSame); + final showTimeStamp = + (!isThreadMessage || _isThreadConversation) && !hasReplies && (hasTimeDiff || !isNextUserSame); - final showUsername = !isMyMessage && - (!isThreadMessage || _isThreadConversation) && - !hasReplies && - (hasTimeDiff || !isNextUserSame); + final showUsername = + !isMyMessage && (!isThreadMessage || _isThreadConversation) && !hasReplies && (hasTimeDiff || !isNextUserSame); - final showMarkUnread = streamChannel?.channel.config?.readEvents == true && + final showMarkUnread = + streamChannel?.channel.config?.readEvents == true && !isMyMessage && (!isThreadMessage || _isThreadConversation); final showUserAvatar = isMyMessage ? DisplayWidget.gone : (hasTimeDiff || !isNextUserSame) - ? DisplayWidget.show - : DisplayWidget.hide; + ? DisplayWidget.show + : DisplayWidget.hide; - final showSendingIndicator = - isMyMessage && (index == 0 || hasTimeDiff || !isNextUserSame); + final showSendingIndicator = isMyMessage && (index == 0 || hasTimeDiff || !isNextUserSame); final showInChannelIndicator = !_isThreadConversation && isThreadMessage; final showThreadReplyIndicator = !_isThreadConversation && hasReplies; @@ -1299,9 +1261,7 @@ class _StreamMessageListViewState extends State { alignment: 0.1, ); } else { - await streamChannel! - .loadChannelAtMessage(quotedMessageId) - .then((_) async { + await streamChannel!.loadChannelAtMessage(quotedMessageId).then((_) async { initialIndex = 21; // 19 + 2 | 19 is the index of the message initialAlignment = 0.1; }); @@ -1309,8 +1269,7 @@ class _StreamMessageListViewState extends State { }, showEditMessage: isMyMessage, showDeleteMessage: isMyMessage, - showThreadReplyMessage: - !isThreadMessage && streamChannel?.channel.canSendReply == true, + showThreadReplyMessage: !isThreadMessage && streamChannel?.channel.canSendReply == true, showFlagButton: !isMyMessage, borderSide: borderSide, onThreadTap: _onThreadTap, @@ -1325,10 +1284,7 @@ class _StreamMessageListViewState extends State { ? Radius.circular(attachmentBorderRadius) : Radius.circular( (hasTimeDiff || !isNextUserSame) && - !(hasReplies || - isThreadMessage || - hasFileAttachment || - hasVoiceRecordingAttachment) + !(hasReplies || isThreadMessage || hasFileAttachment || hasVoiceRecordingAttachment) ? 0 : attachmentBorderRadius, ), @@ -1336,10 +1292,7 @@ class _StreamMessageListViewState extends State { bottomRight: isMyMessage ? Radius.circular( (hasTimeDiff || !isNextUserSame) && - !(hasReplies || - isThreadMessage || - hasFileAttachment || - hasVoiceRecordingAttachment) + !(hasReplies || isThreadMessage || hasFileAttachment || hasVoiceRecordingAttachment) ? 0 : attachmentBorderRadius, ) @@ -1350,26 +1303,20 @@ class _StreamMessageListViewState extends State { hasUrlAttachment ? 8 : hasFileAttachment || hasVoiceRecordingAttachment - ? 4 - : 2, + ? 4 + : 2, ), borderRadiusGeometry: BorderRadius.only( topLeft: const Radius.circular(16), bottomLeft: isMyMessage ? const Radius.circular(16) : Radius.circular( - (hasTimeDiff || !isNextUserSame) && - !(hasReplies || isThreadMessage) - ? 0 - : 16, + (hasTimeDiff || !isNextUserSame) && !(hasReplies || isThreadMessage) ? 0 : 16, ), topRight: const Radius.circular(16), bottomRight: isMyMessage ? Radius.circular( - (hasTimeDiff || !isNextUserSame) && - !(hasReplies || isThreadMessage) - ? 0 - : 16, + (hasTimeDiff || !isNextUserSame) && !(hasReplies || isThreadMessage) ? 0 : 16, ) : const Radius.circular(16), ), @@ -1377,9 +1324,7 @@ class _StreamMessageListViewState extends State { vertical: 8, horizontal: isOnlyEmoji ? 0 : 16.0, ), - messageTheme: isMyMessage - ? _streamTheme.ownMessageTheme - : _streamTheme.otherMessageTheme, + messageTheme: isMyMessage ? _streamTheme.ownMessageTheme : _streamTheme.otherMessageTheme, onMessageTap: widget.onMessageTap, onMessageLongPress: widget.onMessageLongPress, ); @@ -1403,8 +1348,7 @@ class _StreamMessageListViewState extends State { widget.highlightInitialMessage && isInitialMessage(message.id, streamChannel)) { final colorTheme = _streamTheme.colorTheme; - final highlightColor = - widget.messageHighlightColor ?? colorTheme.highlight; + final highlightColor = widget.messageHighlightColor ?? colorTheme.highlight; child = TweenAnimationBuilder( tween: ColorTween( begin: highlightColor, @@ -1503,35 +1447,35 @@ class _StreamMessageListViewState extends State { // The created callback will use widget.onThreadTap, passing the result // of widget.threadBuilder (if provided) as the second argument. (final onThreadTap?, final threadBuilder) => (Message message) { - onThreadTap( - message, - threadBuilder?.call(context, message), - ); - }, + onThreadTap( + message, + threadBuilder?.call(context, message), + ); + }, // Case 2: widget.onThreadTap is null, but widget.threadBuilder is provided. // The created callback will perform the default navigation action, // using widget.threadBuilder to build the thread page. (null, final threadBuilder?) => (Message message) { - final threadPage = StreamChatConfiguration( - // This is needed to provide the nearest reaction icons to the - // StreamMessageReactionsModal. - data: StreamChatConfiguration.of(context), - child: StreamChannel( - channel: streamChannel!.channel, - child: BetterStreamBuilder( - initialData: message, - stream: streamChannel!.channel.state?.messagesStream.map( - (it) => it.firstWhere((m) => m.id == message.id), - ), - builder: (_, data) => threadBuilder(context, data), + final threadPage = StreamChatConfiguration( + // This is needed to provide the nearest reaction icons to the + // StreamMessageReactionsModal. + data: StreamChatConfiguration.of(context), + child: StreamChannel( + channel: streamChannel!.channel, + child: BetterStreamBuilder( + initialData: message, + stream: streamChannel!.channel.state?.messagesStream.map( + (it) => it.firstWhere((m) => m.id == message.id), ), + builder: (_, data) => threadBuilder(context, data), ), - ); + ), + ); - Navigator.of(context).push( - MaterialPageRoute(builder: (_) => threadPage), - ); - }, + Navigator.of(context).push( + MaterialPageRoute(builder: (_) => threadPage), + ); + }, _ => null, }; } diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/mlv_utils.dart b/packages/stream_chat_flutter/lib/src/message_list_view/mlv_utils.dart index 00177e0fb5..228c22318f 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/mlv_utils.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/mlv_utils.dart @@ -16,8 +16,7 @@ int getInitialIndex( if (currentUser == null) return 0; final messages = [ - ...channelState.channel.state!.messages - .where(messageFilter ?? defaultMessageFilter(currentUser.id)) + ...channelState.channel.state!.messages.where(messageFilter ?? defaultMessageFilter(currentUser.id)), ].reversed.toList(growable: false); // Return the initial message index if available. diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/unread_indicator_button.dart b/packages/stream_chat_flutter/lib/src/message_list_view/unread_indicator_button.dart index 6c66c118ce..1745566112 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/unread_indicator_button.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/unread_indicator_button.dart @@ -18,11 +18,12 @@ typedef OnUnreadIndicatorTap = Future Function(String? lastReadMessageId); /// [unreadCount] is the number of unread messages. /// [onTap] is called when the indicator is tapped. /// [onDismissTap] is called when the dismiss action is triggered. -typedef UnreadIndicatorBuilder = Widget Function( - int unreadCount, - OnUnreadIndicatorTap onTap, - OnUnreadIndicatorDismissTap onDismissTap, -); +typedef UnreadIndicatorBuilder = + Widget Function( + int unreadCount, + OnUnreadIndicatorTap onTap, + OnUnreadIndicatorDismissTap onDismissTap, + ); /// {@template unreadIndicatorButton} /// A button that displays the number of unread messages in a channel. diff --git a/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart index 02e929b26f..4487487733 100644 --- a/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart @@ -79,8 +79,8 @@ class StreamMessageActionsModal extends StatelessWidget { final onReactionPicked = switch (onActionTap) { null => null, final onActionTap => (reaction) => onActionTap( - SelectReaction(message: message, reaction: reaction), - ), + SelectReaction(message: message, reaction: reaction), + ), }; return StreamMessageDialog( diff --git a/packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart b/packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart index 96920b3e9c..1cdb41d5d2 100644 --- a/packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart @@ -72,10 +72,10 @@ class StreamMessageReactionsModal extends StatelessWidget { final onReactionPicked = switch (this.onReactionPicked) { null => null, final onPicked => (reaction) { - return onPicked.call( - SelectReaction(message: message, reaction: reaction), - ); - }, + return onPicked.call( + SelectReaction(message: message, reaction: reaction), + ); + }, }; return StreamMessageDialog( diff --git a/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart b/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart index 0b408aa750..05a7b70b1c 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart @@ -123,33 +123,29 @@ class BottomRow extends StatelessWidget { void Function(Message)? onThreadTap, Widget Function(BuildContext, Message)? usernameBuilder, Widget Function(BuildContext, Message)? sendingIndicatorBuilder, - }) => - BottomRow( - key: key ?? this.key, - isDeleted: isDeleted ?? this.isDeleted, - message: message ?? this.message, - showThreadReplyIndicator: - showThreadReplyIndicator ?? this.showThreadReplyIndicator, - showInChannel: showInChannel ?? this.showInChannel, - showTimeStamp: showTimeStamp ?? this.showTimeStamp, - showUsername: showUsername ?? this.showUsername, - showEditedLabel: showEditedLabel ?? this.showEditedLabel, - reverse: reverse ?? this.reverse, - showSendingIndicator: showSendingIndicator ?? this.showSendingIndicator, - hasUrlAttachments: hasUrlAttachments ?? this.hasUrlAttachments, - isGiphy: isGiphy ?? this.isGiphy, - isOnlyEmoji: isOnlyEmoji ?? this.isOnlyEmoji, - messageTheme: messageTheme ?? this.messageTheme, - streamChatTheme: streamChatTheme ?? this.streamChatTheme, - hasNonUrlAttachments: hasNonUrlAttachments ?? this.hasNonUrlAttachments, - streamChat: streamChat ?? this.streamChat, - deletedBottomRowBuilder: - deletedBottomRowBuilder ?? this.deletedBottomRowBuilder, - onThreadTap: onThreadTap ?? this.onThreadTap, - usernameBuilder: usernameBuilder ?? this.usernameBuilder, - sendingIndicatorBuilder: - sendingIndicatorBuilder ?? this.sendingIndicatorBuilder, - ); + }) => BottomRow( + key: key ?? this.key, + isDeleted: isDeleted ?? this.isDeleted, + message: message ?? this.message, + showThreadReplyIndicator: showThreadReplyIndicator ?? this.showThreadReplyIndicator, + showInChannel: showInChannel ?? this.showInChannel, + showTimeStamp: showTimeStamp ?? this.showTimeStamp, + showUsername: showUsername ?? this.showUsername, + showEditedLabel: showEditedLabel ?? this.showEditedLabel, + reverse: reverse ?? this.reverse, + showSendingIndicator: showSendingIndicator ?? this.showSendingIndicator, + hasUrlAttachments: hasUrlAttachments ?? this.hasUrlAttachments, + isGiphy: isGiphy ?? this.isGiphy, + isOnlyEmoji: isOnlyEmoji ?? this.isOnlyEmoji, + messageTheme: messageTheme ?? this.messageTheme, + streamChatTheme: streamChatTheme ?? this.streamChatTheme, + hasNonUrlAttachments: hasNonUrlAttachments ?? this.hasNonUrlAttachments, + streamChat: streamChat ?? this.streamChat, + deletedBottomRowBuilder: deletedBottomRowBuilder ?? this.deletedBottomRowBuilder, + onThreadTap: onThreadTap ?? this.onThreadTap, + usernameBuilder: usernameBuilder ?? this.usernameBuilder, + sendingIndicatorBuilder: sendingIndicatorBuilder ?? this.sendingIndicatorBuilder, + ); @override Widget build(BuildContext context) { @@ -190,21 +186,21 @@ class BottomRow extends StatelessWidget { switch (sendingIndicatorBuilder) { final builder? => builder(context, message), _ => SendingIndicatorBuilder( - messageTheme: messageTheme, - message: message, - hasNonUrlAttachments: hasNonUrlAttachments, - streamChat: streamChat, - streamChatTheme: streamChatTheme, - ), + messageTheme: messageTheme, + message: message, + hasNonUrlAttachments: hasNonUrlAttachments, + streamChat: streamChat, + streamChatTheme: streamChatTheme, + ), }, if (showUsername) switch (usernameBuilder) { final builder? => builder(context, message), _ => Username( - key: usernameKey, - message: message, - messageTheme: messageTheme, - ), + key: usernameKey, + message: message, + messageTheme: messageTheme, + ), }, if (showEditedLabel && isEdited) Text( @@ -225,8 +221,7 @@ class BottomRow extends StatelessWidget { ), ]; - final showThreadTail = - (showThreadReplyIndicator || showInChannel) && !isOnlyEmoji; + final showThreadTail = (showThreadReplyIndicator || showInChannel) && !isOnlyEmoji; final threadIndicatorWidgets = [ if (showThreadTail) @@ -236,8 +231,7 @@ class BottomRow extends StatelessWidget { builder: (context) { return Padding( padding: EdgeInsets.only( - bottom: context.textScaleFactor * - ((messageTheme.repliesStyle?.fontSize ?? 1) / 2), + bottom: context.textScaleFactor * ((messageTheme.repliesStyle?.fontSize ?? 1) / 2), ), child: CustomPaint( size: const Size(16, 32) * context.textScaleFactor, diff --git a/packages/stream_chat_flutter/lib/src/message_widget/deleted_message.dart b/packages/stream_chat_flutter/lib/src/message_widget/deleted_message.dart index 10004802fa..a184af5463 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/deleted_message.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/deleted_message.dart @@ -34,10 +34,12 @@ class StreamDeletedMessage extends StatelessWidget { Widget build(BuildContext context) { return Material( color: messageTheme.messageBackgroundColor, - shape: shape ?? + shape: + shape ?? RoundedRectangleBorder( borderRadius: borderRadiusGeometry ?? BorderRadius.zero, - side: borderSide ?? + side: + borderSide ?? BorderSide( color: messageTheme.messageBorderColor ?? Colors.transparent, ), diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_card.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_card.dart index f41b20cd7a..07ad1a8c72 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_card.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_card.dart @@ -166,11 +166,11 @@ class _MessageCardState extends State { children: [ if (widget.hasQuotedMessage) InkWell( - onTap: !widget.message.quotedMessage!.isDeleted && - onQuotedMessageTap != null + onTap: !widget.message.quotedMessage!.isDeleted && onQuotedMessageTap != null ? () => onQuotedMessageTap(widget.message.quotedMessageId) : null, - child: quotedMessageBuilder?.call( + child: + quotedMessageBuilder?.call( context, widget.message.quotedMessage!, ) ?? @@ -221,12 +221,12 @@ class _MessageCardState extends State { shape: switch (widget.shape) { final shape? => shape, _ => RoundedRectangleBorder( - borderRadius: borderRadius, - side: switch (widget.borderSide) { - final side? => side, - _ => BorderSide(color: borderColor), - }, - ), + borderRadius: borderRadius, + side: switch (widget.borderSide) { + final side? => side, + _ => BorderSide(color: borderColor), + }, + ), }, ); } @@ -236,8 +236,7 @@ class _MessageCardState extends State { return theme.messageBackgroundColor; } - final containsOnlyUrlAttachment = - widget.hasUrlAttachments && !widget.hasNonUrlAttachments; + final containsOnlyUrlAttachment = widget.hasUrlAttachments && !widget.hasNonUrlAttachments; if (containsOnlyUrlAttachment) { return theme.urlAttachmentBackgroundColor; diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_text.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_text.dart index e46e4fc420..789e2ed7e2 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_text.dart @@ -35,38 +35,34 @@ class StreamMessageText extends StatelessWidget { stream: streamChat.currentUserStream.map((it) => it!.language ?? 'en'), initialData: streamChat.currentUser!.language ?? 'en', builder: (context, language) { - final messageText = message - .translate(language) - .replaceMentions() - .text - ?.replaceAll('\n', '\n\n') - .trim(); + final messageText = message.translate(language).replaceMentions().text?.replaceAll('\n', '\n\n').trim(); return StreamMarkdownMessage( data: messageText ?? '', messageTheme: messageTheme, selectable: isDesktopDeviceOrWeb, - onTapLink: ( - String text, - String? href, - String title, - ) { - if (text.startsWith('@')) { - final mentionedUser = message.mentionedUsers.firstWhereOrNull( - (u) => '@${u.name}' == text, - ); + onTapLink: + ( + String text, + String? href, + String title, + ) { + if (text.startsWith('@')) { + final mentionedUser = message.mentionedUsers.firstWhereOrNull( + (u) => '@${u.name}' == text, + ); - if (mentionedUser == null) return; + if (mentionedUser == null) return; - onMentionTap?.call(mentionedUser); - } else if (href != null) { - if (onLinkTap != null) { - onLinkTap!(href); - } else { - launchURL(context, href); - } - } - }, + onMentionTap?.call(mentionedUser); + } else if (href != null) { + if (onLinkTap != null) { + onLinkTap!(href); + } else { + launchURL(context, href); + } + } + }, ); }, ); diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart index 685a98abd4..0c0961c0ef 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart @@ -401,8 +401,7 @@ class StreamMessageWidget extends StatefulWidget { /// Crop type of the image attachment thumbnail. /// /// Defaults to [center] - final String /*center|top|bottom|left|right*/ - imageAttachmentThumbnailCropType; + final String /*center|top|bottom|left|right*/ imageAttachmentThumbnailCropType; /// {@template copyWith} /// Creates a copy of [StreamMessageWidget] with specified attributes @@ -481,17 +480,13 @@ class StreamMessageWidget extends StatefulWidget { onThreadTap: onThreadTap ?? this.onThreadTap, onReplyTap: onReplyTap ?? this.onReplyTap, onConfirmDeleteTap: onConfirmDeleteTap ?? this.onConfirmDeleteTap, - editMessageInputBuilder: - editMessageInputBuilder ?? this.editMessageInputBuilder, + editMessageInputBuilder: editMessageInputBuilder ?? this.editMessageInputBuilder, textBuilder: textBuilder ?? this.textBuilder, quotedMessageBuilder: quotedMessageBuilder ?? this.quotedMessageBuilder, - deletedMessageBuilder: - deletedMessageBuilder ?? this.deletedMessageBuilder, - bottomRowBuilderWithDefaultWidget: bottomRowBuilderWithDefaultWidget ?? - this.bottomRowBuilderWithDefaultWidget, + deletedMessageBuilder: deletedMessageBuilder ?? this.deletedMessageBuilder, + bottomRowBuilderWithDefaultWidget: bottomRowBuilderWithDefaultWidget ?? this.bottomRowBuilderWithDefaultWidget, onMessageActions: onMessageActions ?? this.onMessageActions, - onBouncedErrorMessageActions: - onBouncedErrorMessageActions ?? this.onBouncedErrorMessageActions, + onBouncedErrorMessageActions: onBouncedErrorMessageActions ?? this.onBouncedErrorMessageActions, message: message ?? this.message, messageTheme: messageTheme ?? this.messageTheme, reverse: reverse ?? this.reverse, @@ -507,10 +502,8 @@ class StreamMessageWidget extends StatefulWidget { showSendingIndicator: showSendingIndicator ?? this.showSendingIndicator, showEditedLabel: showEditedLabel ?? this.showEditedLabel, showReactions: showReactions ?? this.showReactions, - showThreadReplyIndicator: - showThreadReplyIndicator ?? this.showThreadReplyIndicator, - showInChannelIndicator: - showInChannelIndicator ?? this.showInChannelIndicator, + showThreadReplyIndicator: showThreadReplyIndicator ?? this.showThreadReplyIndicator, + showInChannelIndicator: showInChannelIndicator ?? this.showInChannelIndicator, onUserAvatarTap: onUserAvatarTap ?? this.onUserAvatarTap, onLinkTap: onLinkTap ?? this.onLinkTap, showReactionPicker: showReactionPicker ?? this.showReactionPicker, @@ -518,8 +511,7 @@ class StreamMessageWidget extends StatefulWidget { showUsername: showUsername ?? this.showUsername, showTimestamp: showTimestamp ?? this.showTimestamp, showReplyMessage: showReplyMessage ?? this.showReplyMessage, - showThreadReplyMessage: - showThreadReplyMessage ?? this.showThreadReplyMessage, + showThreadReplyMessage: showThreadReplyMessage ?? this.showThreadReplyMessage, showEditMessage: showEditMessage ?? this.showEditMessage, showCopyMessage: showCopyMessage ?? this.showCopyMessage, showDeleteMessage: showDeleteMessage ?? this.showDeleteMessage, @@ -527,8 +519,7 @@ class StreamMessageWidget extends StatefulWidget { showFlagButton: showFlagButton ?? this.showFlagButton, showPinButton: showPinButton ?? this.showPinButton, showPinHighlight: showPinHighlight ?? this.showPinHighlight, - showMarkUnreadMessage: - showMarkUnreadMessage ?? this.showMarkUnreadMessage, + showMarkUnreadMessage: showMarkUnreadMessage ?? this.showMarkUnreadMessage, attachmentBuilders: attachmentBuilders ?? this.attachmentBuilders, translateUserAvatar: translateUserAvatar ?? this.translateUserAvatar, onQuotedMessageTap: onQuotedMessageTap ?? this.onQuotedMessageTap, @@ -540,18 +531,12 @@ class StreamMessageWidget extends StatefulWidget { onCustomActionTap: onCustomActionTap ?? this.onCustomActionTap, onAttachmentTap: onAttachmentTap ?? this.onAttachmentTap, userAvatarBuilder: userAvatarBuilder ?? this.userAvatarBuilder, - imageAttachmentThumbnailSize: - imageAttachmentThumbnailSize ?? this.imageAttachmentThumbnailSize, - imageAttachmentThumbnailResizeType: imageAttachmentThumbnailResizeType ?? - this.imageAttachmentThumbnailResizeType, - imageAttachmentThumbnailCropType: imageAttachmentThumbnailCropType ?? - this.imageAttachmentThumbnailCropType, - attachmentActionsModalBuilder: - attachmentActionsModalBuilder ?? this.attachmentActionsModalBuilder, - reactionPickerBuilder: - reactionPickerBuilder ?? this.reactionPickerBuilder, - reactionIndicatorBuilder: - reactionIndicatorBuilder ?? this.reactionIndicatorBuilder, + imageAttachmentThumbnailSize: imageAttachmentThumbnailSize ?? this.imageAttachmentThumbnailSize, + imageAttachmentThumbnailResizeType: imageAttachmentThumbnailResizeType ?? this.imageAttachmentThumbnailResizeType, + imageAttachmentThumbnailCropType: imageAttachmentThumbnailCropType ?? this.imageAttachmentThumbnailCropType, + attachmentActionsModalBuilder: attachmentActionsModalBuilder ?? this.attachmentActionsModalBuilder, + reactionPickerBuilder: reactionPickerBuilder ?? this.reactionPickerBuilder, + reactionIndicatorBuilder: reactionIndicatorBuilder ?? this.reactionIndicatorBuilder, ); } @@ -594,14 +579,12 @@ class _StreamMessageWidgetState extends State /// Whether the message has failed to be sent, updated, deleted or is bounced /// back with the message type as error. /// {@endtemplate} - bool get isFailedState => - isSendFailed || isUpdateFailed || isDeleteFailed || isBouncedWithError; + bool get isFailedState => isSendFailed || isUpdateFailed || isDeleteFailed || isBouncedWithError; /// {@template isGiphy} /// `true` if any of the [message]'s attachments are a giphy. /// {@endtemplate} - bool get isGiphy => widget.message.attachments - .any((element) => element.type == AttachmentType.giphy); + bool get isGiphy => widget.message.attachments.any((element) => element.type == AttachmentType.giphy); /// {@template isOnlyEmoji} /// `true` if [message.text] contains only emoji. @@ -612,15 +595,13 @@ class _StreamMessageWidgetState extends State /// `true` if any of the [message]'s attachments are a giphy and do not /// have a [Attachment.titleLink]. /// {@endtemplate} - bool get hasNonUrlAttachments => widget.message.attachments - .any((it) => it.type != AttachmentType.urlPreview); + bool get hasNonUrlAttachments => widget.message.attachments.any((it) => it.type != AttachmentType.urlPreview); /// {@template hasUrlAttachments} /// `true` if any of the [message]'s attachments are a giphy with a /// [Attachment.titleLink]. /// {@endtemplate} - bool get hasUrlAttachments => widget.message.attachments - .any((it) => it.type == AttachmentType.urlPreview); + bool get hasUrlAttachments => widget.message.attachments.any((it) => it.type == AttachmentType.urlPreview); /// {@template showBottomRow} /// Show the [BottomRow] widget if any of the following are `true`: @@ -649,9 +630,7 @@ class _StreamMessageWidgetState extends State /// `true`, if there are reactions to show, and if the message is not deleted. /// {@endtemplate} bool get shouldShowReactions => - widget.showReactions && - (widget.message.latestReactions?.isNotEmpty == true) && - !widget.message.isDeleted; + widget.showReactions && (widget.message.latestReactions?.isNotEmpty == true) && !widget.message.isDeleted; @override bool get wantKeepAlive => widget.message.attachments.isNotEmpty; @@ -662,10 +641,8 @@ class _StreamMessageWidgetState extends State final theme = StreamChatTheme.of(context); final streamChat = StreamChat.of(context); - final avatarWidth = - widget.messageTheme.avatarTheme?.constraints.maxWidth ?? 40; - final bottomRowPadding = - widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 0.5; + final avatarWidth = widget.messageTheme.avatarTheme?.constraints.maxWidth ?? 40; + final bottomRowPadding = widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 0.5; return Portal( child: Material( @@ -743,8 +720,7 @@ class _StreamMessageWidgetState extends State onReplyTap: widget.onReplyTap, onThreadTap: widget.onThreadTap, onShowMessage: widget.onShowMessage, - attachmentActionsModalBuilder: - widget.attachmentActionsModalBuilder, + attachmentActionsModalBuilder: widget.attachmentActionsModalBuilder, avatarWidth: avatarWidth, bottomRowPadding: bottomRowPadding, isFailedState: isFailedState, @@ -773,8 +749,7 @@ class _StreamMessageWidgetState extends State onLinkTap: widget.onLinkTap, onMentionTap: widget.onMentionTap, onQuotedMessageTap: widget.onQuotedMessageTap, - bottomRowBuilderWithDefaultWidget: - widget.bottomRowBuilderWithDefaultWidget, + bottomRowBuilderWithDefaultWidget: widget.bottomRowBuilderWithDefaultWidget, onUserAvatarTap: widget.onUserAvatarTap, userAvatarBuilder: widget.userAvatarBuilder, reactionIndicatorBuilder: widget.reactionIndicatorBuilder, @@ -805,26 +780,27 @@ class _StreamMessageWidgetState extends State OwnUser? currentUser, List? customActions, }) { - final actions = StreamMessageActionsBuilder.buildActions( - context: context, - message: message, - channel: channel, - currentUser: currentUser, - customActions: customActions, - )..retainWhere( - (it) => switch (it.action) { - QuotedReply() => widget.showReplyMessage, - ThreadReply() => widget.showThreadReplyMessage, - MarkUnread() => widget.showMarkUnreadMessage, - ResendMessage() => widget.showResendMessage, - EditMessage() => widget.showEditMessage, - CopyMessage() => widget.showCopyMessage, - FlagMessage() => widget.showFlagButton, - PinMessage() => widget.showPinButton, - DeleteMessage() => widget.showDeleteMessage, - _ => true, // Retain all the remaining actions. - }, - ); + final actions = + StreamMessageActionsBuilder.buildActions( + context: context, + message: message, + channel: channel, + currentUser: currentUser, + customActions: customActions, + )..retainWhere( + (it) => switch (it.action) { + QuotedReply() => widget.showReplyMessage, + ThreadReply() => widget.showThreadReplyMessage, + MarkUnread() => widget.showMarkUnreadMessage, + ResendMessage() => widget.showResendMessage, + EditMessage() => widget.showEditMessage, + CopyMessage() => widget.showCopyMessage, + FlagMessage() => widget.showFlagButton, + PinMessage() => widget.showPinButton, + DeleteMessage() => widget.showDeleteMessage, + _ => true, // Retain all the remaining actions. + }, + ); return actions; } @@ -1076,8 +1052,7 @@ class _StreamMessageWidgetState extends State MessageAction action, ) async { return switch (action) { - SelectReaction() => - _selectReaction(context, action.message, channel, action.reaction), + SelectReaction() => _selectReaction(context, action.message, channel, action.reaction), CopyMessage() => _copyMessage(action.message, channel), DeleteMessage() => _maybeDeleteMessage(context, action.message, channel), HardDeleteMessage() => channel.deleteMessage(action.message, hard: true), diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart index 6acf726283..be20f0eca1 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart @@ -10,11 +10,12 @@ typedef BottomRowBuilder = Widget Function(BuildContext, Message); /// Signature for the builder function that will be called when the message /// bottom row is built. Includes the [Message] and the default [BottomRow]. -typedef BottomRowBuilderWithDefaultWidget = Widget Function( - BuildContext, - Message, - BottomRow, -); +typedef BottomRowBuilderWithDefaultWidget = + Widget Function( + BuildContext, + Message, + BottomRow, + ); /// {@template messageWidgetContent} /// The main content of a [StreamMessageWidget]. @@ -231,15 +232,12 @@ class MessageWidgetContent extends StatelessWidget { @override Widget build(BuildContext context) { return Column( - crossAxisAlignment: - reverse ? CrossAxisAlignment.end : CrossAxisAlignment.start, + crossAxisAlignment: reverse ? CrossAxisAlignment.end : CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Stack( clipBehavior: Clip.none, - alignment: reverse - ? AlignmentDirectional.bottomEnd - : AlignmentDirectional.bottomStart, + alignment: reverse ? AlignmentDirectional.bottomEnd : AlignmentDirectional.bottomStart, children: [ if (showBottomRow) Padding( @@ -255,8 +253,7 @@ class MessageWidgetContent extends StatelessWidget { bottom: isPinned && showPinHighlight ? 8.0 : 0.0, ), child: Column( - crossAxisAlignment: - reverse ? CrossAxisAlignment.end : CrossAxisAlignment.start, + crossAxisAlignment: reverse ? CrossAxisAlignment.end : CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ if (isPinned && message.pinnedBy != null && showPinHighlight) @@ -299,12 +296,12 @@ class MessageWidgetContent extends StatelessWidget { DisplayWidget.gone => null, DisplayWidget.hide => SizedBox(width: avatarWidth), DisplayWidget.show => UserAvatarTransform( - onUserAvatarTap: onUserAvatarTap, - userAvatarBuilder: userAvatarBuilder, - translateUserAvatar: translateUserAvatar, - messageTheme: messageTheme, - message: message, - ), + onUserAvatarTap: onUserAvatarTap, + userAvatarBuilder: userAvatarBuilder, + translateUserAvatar: translateUserAvatar, + messageTheme: messageTheme, + message: message, + ), }, ), ], @@ -314,9 +311,9 @@ class MessageWidgetContent extends StatelessWidget { padding: switch (showUserAvatar) { DisplayWidget.gone => EdgeInsets.zero, _ => EdgeInsets.only( - left: avatarWidth + 4, - right: avatarWidth + 4, - ) + left: avatarWidth + 4, + right: avatarWidth + 4, + ), }, child: DesktopReactionsBuilder( message: message, diff --git a/packages/stream_chat_flutter/lib/src/message_widget/parse_attachments.dart b/packages/stream_chat_flutter/lib/src/message_widget/parse_attachments.dart index b788a73b6c..b0c9080cd5 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/parse_attachments.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/parse_attachments.dart @@ -29,11 +29,12 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// ) /// ``` /// {@endtemplate} -typedef OnAttachmentWidgetTap = FutureOr Function( - BuildContext context, - Message message, - Attachment attachment, -); +typedef OnAttachmentWidgetTap = + FutureOr Function( + BuildContext context, + Message message, + Attachment attachment, + ); /// {@template parseAttachments} /// Parses the attachments of a [StreamMessageWidget]. @@ -185,7 +186,7 @@ extension on Message { attachment: it, message: this, ); - }) + }), ]; } } diff --git a/packages/stream_chat_flutter/lib/src/message_widget/quoted_message.dart b/packages/stream_chat_flutter/lib/src/message_widget/quoted_message.dart index b6b7837b98..bd9cef05bb 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/quoted_message.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/quoted_message.dart @@ -30,13 +30,10 @@ class QuotedMessage extends StatelessWidget { final chatThemeData = StreamChatTheme.of(context); final isMyMessage = message.user?.id == streamChat.currentUser?.id; - final isMyQuotedMessage = - message.quotedMessage?.user?.id == streamChat.currentUser?.id; + final isMyQuotedMessage = message.quotedMessage?.user?.id == streamChat.currentUser?.id; return StreamQuotedMessageWidget( message: message.quotedMessage!, - messageTheme: isMyMessage - ? chatThemeData.otherMessageTheme - : chatThemeData.ownMessageTheme, + messageTheme: isMyMessage ? chatThemeData.otherMessageTheme : chatThemeData.ownMessageTheme, reverse: !isMyQuotedMessage, textBuilder: textBuilder, padding: EdgeInsets.only( diff --git a/packages/stream_chat_flutter/lib/src/message_widget/user_avatar_transform.dart b/packages/stream_chat_flutter/lib/src/message_widget/user_avatar_transform.dart index 275d63e05e..a215a442fb 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/user_avatar_transform.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/user_avatar_transform.dart @@ -37,11 +37,10 @@ class UserAvatarTransform extends StatelessWidget { return Transform.translate( offset: Offset( 0, - translateUserAvatar - ? (messageTheme.avatarTheme?.constraints.maxHeight ?? 40) / 2 - : 0, + translateUserAvatar ? (messageTheme.avatarTheme?.constraints.maxHeight ?? 40) / 2 : 0, ), - child: userAvatarBuilder?.call(context, message.user!) ?? + child: + userAvatarBuilder?.call(context, message.user!) ?? StreamUserAvatar( user: message.user!, onTap: onUserAvatarTap, diff --git a/packages/stream_chat_flutter/lib/src/misc/adaptive_dialog_action.dart b/packages/stream_chat_flutter/lib/src/misc/adaptive_dialog_action.dart index 159c55b557..0a674e949d 100644 --- a/packages/stream_chat_flutter/lib/src/misc/adaptive_dialog_action.dart +++ b/packages/stream_chat_flutter/lib/src/misc/adaptive_dialog_action.dart @@ -47,25 +47,25 @@ class AdaptiveDialogAction extends StatelessWidget { return switch (Theme.of(context).platform) { TargetPlatform.iOS || TargetPlatform.macOS => CupertinoTheme( - data: CupertinoTheme.of(context).copyWith( - primaryColor: theme.colorTheme.accentPrimary, - ), - child: CupertinoDialogAction( - onPressed: onPressed, - isDefaultAction: isDefaultAction, - isDestructiveAction: isDestructiveAction, - child: child, - ), + data: CupertinoTheme.of(context).copyWith( + primaryColor: theme.colorTheme.accentPrimary, ), - _ => TextButton( + child: CupertinoDialogAction( onPressed: onPressed, - style: TextButton.styleFrom( - textStyle: theme.textTheme.body, - foregroundColor: theme.colorTheme.accentPrimary, - disabledForegroundColor: theme.colorTheme.disabled, - ), + isDefaultAction: isDefaultAction, + isDestructiveAction: isDestructiveAction, child: child, ), + ), + _ => TextButton( + onPressed: onPressed, + style: TextButton.styleFrom( + textStyle: theme.textTheme.body, + foregroundColor: theme.colorTheme.accentPrimary, + disabledForegroundColor: theme.colorTheme.disabled, + ), + child: child, + ), }; } } diff --git a/packages/stream_chat_flutter/lib/src/misc/audio_waveform.dart b/packages/stream_chat_flutter/lib/src/misc/audio_waveform.dart index e35f9277a5..05bcea0085 100644 --- a/packages/stream_chat_flutter/lib/src/misc/audio_waveform.dart +++ b/packages/stream_chat_flutter/lib/src/misc/audio_waveform.dart @@ -100,8 +100,7 @@ class StreamAudioWaveformSlider extends StatefulWidget { final Color? thumbBorderColor; @override - State createState() => - _StreamAudioWaveformSliderState(); + State createState() => _StreamAudioWaveformSliderState(); } class _StreamAudioWaveformSliderState extends State { @@ -310,13 +309,13 @@ class _WaveformPainter extends CustomPainter { double spacingRatio = 0.3, this.heightScale = 1, this.inverse = true, - }) : waveform = [ - ...waveform.take(limit), - if (waveform.length < limit) - // Fill the remaining bars with 0 value - ...List.filled(limit - waveform.length, 0) - ], - spacingRatio = spacingRatio.clamp(0, 1); + }) : waveform = [ + ...waveform.take(limit), + if (waveform.length < limit) + // Fill the remaining bars with 0 value + ...List.filled(limit - waveform.length, 0), + ], + spacingRatio = spacingRatio.clamp(0, 1); final List waveform; final Color color; diff --git a/packages/stream_chat_flutter/lib/src/misc/connection_status_builder.dart b/packages/stream_chat_flutter/lib/src/misc/connection_status_builder.dart index e6703420b4..3a44e1465e 100644 --- a/packages/stream_chat_flutter/lib/src/misc/connection_status_builder.dart +++ b/packages/stream_chat_flutter/lib/src/misc/connection_status_builder.dart @@ -29,13 +29,11 @@ class StreamConnectionStatusBuilder extends StatelessWidget { final WidgetBuilder? loadingBuilder; /// The builder that will be used in case of data - final Widget Function(BuildContext context, ConnectionStatus status) - statusBuilder; + final Widget Function(BuildContext context, ConnectionStatus status) statusBuilder; @override Widget build(BuildContext context) { - final stream = connectionStatusStream ?? - StreamChat.of(context).client.wsConnectionStatusStream; + final stream = connectionStatusStream ?? StreamChat.of(context).client.wsConnectionStatusStream; final client = StreamChat.of(context).client; return BetterStreamBuilder( initialData: client.wsConnectionStatus, diff --git a/packages/stream_chat_flutter/lib/src/misc/flex_grid.dart b/packages/stream_chat_flutter/lib/src/misc/flex_grid.dart index 5210cf9aa1..ca0e8955a6 100644 --- a/packages/stream_chat_flutter/lib/src/misc/flex_grid.dart +++ b/packages/stream_chat_flutter/lib/src/misc/flex_grid.dart @@ -80,19 +80,19 @@ class FlexGrid extends StatelessWidget { this.reverse = false, this.spacing = 2.0, this.runSpacing = 2.0, - }) : assert( - pattern.count == children.length, - 'The number of children must match the number of cells in the matrix', - ), - assert( - maxChildren == null || maxChildren <= pattern.count, - 'The number of maxChildren must be less than or equal to the number ' - 'of cells in the matrix', - ), - assert( - maxChildren == null || overlayBuilder != null, - 'overlayBuilder must be provided when maxChildren is not null', - ); + }) : assert( + pattern.count == children.length, + 'The number of children must match the number of cells in the matrix', + ), + assert( + maxChildren == null || maxChildren <= pattern.count, + 'The number of maxChildren must be less than or equal to the number ' + 'of cells in the matrix', + ), + assert( + maxChildren == null || overlayBuilder != null, + 'overlayBuilder must be provided when maxChildren is not null', + ); /// The pattern of the grid. /// diff --git a/packages/stream_chat_flutter/lib/src/misc/flexible_fractionally_sized_box.dart b/packages/stream_chat_flutter/lib/src/misc/flexible_fractionally_sized_box.dart index 86da240000..c95ba8fae1 100644 --- a/packages/stream_chat_flutter/lib/src/misc/flexible_fractionally_sized_box.dart +++ b/packages/stream_chat_flutter/lib/src/misc/flexible_fractionally_sized_box.dart @@ -13,8 +13,8 @@ class FlexibleFractionallySizedBox extends StatelessWidget { this.widthFactor, this.heightFactor, this.child, - }) : assert(widthFactor == null || widthFactor >= 0.0, ''), - assert(heightFactor == null || heightFactor >= 0.0, ''); + }) : assert(widthFactor == null || widthFactor >= 0.0, ''), + assert(heightFactor == null || heightFactor >= 0.0, ''); /// The widget below this widget in the tree. /// diff --git a/packages/stream_chat_flutter/lib/src/misc/gradient_box_border.dart b/packages/stream_chat_flutter/lib/src/misc/gradient_box_border.dart index 7a881edd16..2ecf925e5d 100644 --- a/packages/stream_chat_flutter/lib/src/misc/gradient_box_border.dart +++ b/packages/stream_chat_flutter/lib/src/misc/gradient_box_border.dart @@ -4,15 +4,19 @@ import 'dart:ui' as ui show lerpDouble; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -const _kDefaultGradient = LinearGradient(colors: [ - Color(0xFF000000), - Color(0xFF000000), -]); - -const _kTransparentGradient = LinearGradient(colors: [ - Color(0x00000000), - Color(0x00000000), -]); +const _kDefaultGradient = LinearGradient( + colors: [ + Color(0xFF000000), + Color(0xFF000000), + ], +); + +const _kTransparentGradient = LinearGradient( + colors: [ + Color(0x00000000), + Color(0x00000000), + ], +); /// {@template gradientBoxBorder} /// A border that draws a gradient instead of a solid color. @@ -109,8 +113,7 @@ class GradientBoxBorder extends BoxBorder { /// Two sides can be merged if one or both are zero-width with /// [GradientBoxBorder.none], or if they both have the same gradient and style bool canMerge(GradientBoxBorder b) { - if ((style == BorderStyle.none && width == 0.0) || - (b.style == BorderStyle.none && b.width == 0.0)) { + if ((style == BorderStyle.none && width == 0.0) || (b.style == BorderStyle.none && b.width == 0.0)) { return true; } return style == b.style && gradient == b.gradient; @@ -209,8 +212,7 @@ class GradientBoxBorder extends BoxBorder { /// Linearly interpolate between two gradient borders. /// /// {@macro dart.ui.shadow.lerp} - static GradientBoxBorder? lerp( - GradientBoxBorder? a, GradientBoxBorder? b, double t) { + static GradientBoxBorder? lerp(GradientBoxBorder? a, GradientBoxBorder? b, double t) { if (identical(a, b)) return a; if (a == null) return b!.scale(t); if (b == null) return a.scale(1.0 - t); @@ -270,10 +272,9 @@ class GradientBoxBorder extends BoxBorder { return switch (shape) { BoxShape.circle => _paintUniformBorderWithCircle(canvas, rect), BoxShape.rectangle => switch (borderRadius) { - final radius? when radius != BorderRadius.zero => - _paintUniformBorderWithRadius(canvas, rect, radius), - _ => _paintUniformBorderWithRectangle(canvas, rect), - }, + final radius? when radius != BorderRadius.zero => _paintUniformBorderWithRadius(canvas, rect, radius), + _ => _paintUniformBorderWithRectangle(canvas, rect), + }, }; } @@ -307,14 +308,16 @@ class GradientBoxBorder extends BoxBorder { Paint _getPaint(Rect rect) { return switch (style) { - BorderStyle.solid => Paint() - ..strokeWidth = width - ..style = PaintingStyle.stroke - ..shader = gradient.createShader(rect), - BorderStyle.none => Paint() - ..strokeWidth = 0.0 - ..style = PaintingStyle.stroke - ..shader = _kTransparentGradient.createShader(rect), + BorderStyle.solid => + Paint() + ..strokeWidth = width + ..style = PaintingStyle.stroke + ..shader = gradient.createShader(rect), + BorderStyle.none => + Paint() + ..strokeWidth = 0.0 + ..style = PaintingStyle.stroke + ..shader = _kTransparentGradient.createShader(rect), }; } diff --git a/packages/stream_chat_flutter/lib/src/misc/info_tile.dart b/packages/stream_chat_flutter/lib/src/misc/info_tile.dart index fd6106491a..0cd7ff9d52 100644 --- a/packages/stream_chat_flutter/lib/src/misc/info_tile.dart +++ b/packages/stream_chat_flutter/lib/src/misc/info_tile.dart @@ -50,13 +50,15 @@ class StreamInfoTile extends StatelessWidget { ), portalFollower: Container( height: 25, - color: backgroundColor ?? + color: + backgroundColor ?? // ignore: deprecated_member_use chatThemeData.colorTheme.textLowEmphasis.withOpacity(0.9), child: Center( child: Text( message, - style: textStyle ?? + style: + textStyle ?? chatThemeData.textTheme.body.copyWith( color: Colors.white, ), diff --git a/packages/stream_chat_flutter/lib/src/misc/markdown_message.dart b/packages/stream_chat_flutter/lib/src/misc/markdown_message.dart index 9b0926ed08..5977692118 100644 --- a/packages/stream_chat_flutter/lib/src/misc/markdown_message.dart +++ b/packages/stream_chat_flutter/lib/src/misc/markdown_message.dart @@ -87,22 +87,23 @@ class StreamMarkdownMessage extends StatelessWidget { syntaxHighlighter: syntaxHighlighter, builders: builders, paddingBuilders: paddingBuilders, - styleSheet: MarkdownStyleSheet.fromTheme( - themeData.copyWith( - textTheme: themeData.textTheme.apply( - bodyColor: messageTheme?.messageTextStyle?.color, - decoration: messageTheme?.messageTextStyle?.decoration, - decorationColor: messageTheme?.messageTextStyle?.decorationColor, - decorationStyle: messageTheme?.messageTextStyle?.decorationStyle, - fontFamily: messageTheme?.messageTextStyle?.fontFamily, - ), - ), - ) - .copyWith( - a: messageTheme?.messageLinksStyle, - p: messageTheme?.messageTextStyle, - ) - .merge(styleSheet), + styleSheet: + MarkdownStyleSheet.fromTheme( + themeData.copyWith( + textTheme: themeData.textTheme.apply( + bodyColor: messageTheme?.messageTextStyle?.color, + decoration: messageTheme?.messageTextStyle?.decoration, + decorationColor: messageTheme?.messageTextStyle?.decorationColor, + decorationStyle: messageTheme?.messageTextStyle?.decorationStyle, + fontFamily: messageTheme?.messageTextStyle?.fontFamily, + ), + ), + ) + .copyWith( + a: messageTheme?.messageLinksStyle, + p: messageTheme?.messageTextStyle, + ) + .merge(styleSheet), ); } } diff --git a/packages/stream_chat_flutter/lib/src/misc/option_list_tile.dart b/packages/stream_chat_flutter/lib/src/misc/option_list_tile.dart index 2c093c9db3..3307a31bbb 100644 --- a/packages/stream_chat_flutter/lib/src/misc/option_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/misc/option_list_tile.dart @@ -60,15 +60,13 @@ class StreamOptionListTile extends StatelessWidget { onTap: onTap, child: Row( children: [ - if (leading != null) - Center(child: leading) - else - const SizedBox(width: 16), + if (leading != null) Center(child: leading) else const SizedBox(width: 16), Expanded( flex: 4, child: Text( title, - style: titleTextStyle ?? + style: + titleTextStyle ?? (titleColor == null ? chatThemeData.textTheme.bodyBold : chatThemeData.textTheme.bodyBold.copyWith( diff --git a/packages/stream_chat_flutter/lib/src/misc/reaction_icon.dart b/packages/stream_chat_flutter/lib/src/misc/reaction_icon.dart index fb5ccde92e..c3c6cd3d34 100644 --- a/packages/stream_chat_flutter/lib/src/misc/reaction_icon.dart +++ b/packages/stream_chat_flutter/lib/src/misc/reaction_icon.dart @@ -8,11 +8,12 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// {@template reactionIconBuilder} /// Signature for a function that builds a reaction icon. /// {@endtemplate} -typedef ReactionIconBuilder = Widget Function( - BuildContext context, - bool isHighlighted, - double iconSize, -); +typedef ReactionIconBuilder = + Widget Function( + BuildContext context, + bool isHighlighted, + double iconSize, + ); /// {@template streamReactionIcon} /// Reaction icon data @@ -26,10 +27,7 @@ class StreamReactionIcon { }); /// Creates a reaction icon with a default unknown icon. - const StreamReactionIcon.unknown() - : type = 'unknown', - emojiCode = null, - builder = _unknownBuilder; + const StreamReactionIcon.unknown() : type = 'unknown', emojiCode = null, builder = _unknownBuilder; /// Converts this [StreamReactionIcon] to a [Reaction] object. Reaction toReaction() => Reaction(type: type, emojiCode: emojiCode); diff --git a/packages/stream_chat_flutter/lib/src/misc/separated_reorderable_list_view.dart b/packages/stream_chat_flutter/lib/src/misc/separated_reorderable_list_view.dart index 24e09455d8..bd0e672eb6 100644 --- a/packages/stream_chat_flutter/lib/src/misc/separated_reorderable_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/misc/separated_reorderable_list_view.dart @@ -33,53 +33,51 @@ class SeparatedReorderableListView extends ReorderableListView { super.restorationId, super.clipBehavior, }) : super.builder( - buildDefaultDragHandles: false, - itemCount: math.max(0, itemCount * 2 - 1), - itemBuilder: (BuildContext context, int index) { - final itemIndex = index ~/ 2; - if (index.isEven) { - final listItem = itemBuilder(context, itemIndex); - return ReorderableDelayedDragStartListener( - key: listItem.key, - index: index, - child: listItem, - ); - } + buildDefaultDragHandles: false, + itemCount: math.max(0, itemCount * 2 - 1), + itemBuilder: (BuildContext context, int index) { + final itemIndex = index ~/ 2; + if (index.isEven) { + final listItem = itemBuilder(context, itemIndex); + return ReorderableDelayedDragStartListener( + key: listItem.key, + index: index, + child: listItem, + ); + } - final separator = separatorBuilder(context, itemIndex); - if (separator.key == null) { - return KeyedSubtree( - key: ValueKey('reorderable_separator_$itemIndex'), - child: IgnorePointer(child: separator), - ); - } + final separator = separatorBuilder(context, itemIndex); + if (separator.key == null) { + return KeyedSubtree( + key: ValueKey('reorderable_separator_$itemIndex'), + child: IgnorePointer(child: separator), + ); + } - return separator; - }, - onReorder: (int oldIndex, int newIndex) { - // Adjust the indexes due to an issue in the ReorderableListView - // which isn't going to be fixed in the near future. - // - // issue: https://github.com/flutter/flutter/issues/24786 - if (newIndex > oldIndex) { - newIndex -= 1; - } + return separator; + }, + onReorder: (int oldIndex, int newIndex) { + // Adjust the indexes due to an issue in the ReorderableListView + // which isn't going to be fixed in the near future. + // + // issue: https://github.com/flutter/flutter/issues/24786 + if (newIndex > oldIndex) { + newIndex -= 1; + } - // Ideally should never happen as separators are wrapped in the - // IgnorePointer widget. This is just a safety check. - if (oldIndex % 2 == 1) return; + // Ideally should never happen as separators are wrapped in the + // IgnorePointer widget. This is just a safety check. + if (oldIndex % 2 == 1) return; - // The item moved behind the top/bottom separator we should not - // reorder it. - if ((oldIndex - newIndex).abs() == 1) return; + // The item moved behind the top/bottom separator we should not + // reorder it. + if ((oldIndex - newIndex).abs() == 1) return; - // Calculate the updated indexes - final updatedOldIndex = oldIndex ~/ 2; - final updatedNewIndex = oldIndex > newIndex && newIndex % 2 == 1 - ? (newIndex + 1) ~/ 2 - : newIndex ~/ 2; + // Calculate the updated indexes + final updatedOldIndex = oldIndex ~/ 2; + final updatedNewIndex = oldIndex > newIndex && newIndex % 2 == 1 ? (newIndex + 1) ~/ 2 : newIndex ~/ 2; - return onReorder(updatedOldIndex, updatedNewIndex); - }, - ); + return onReorder(updatedOldIndex, updatedNewIndex); + }, + ); } diff --git a/packages/stream_chat_flutter/lib/src/misc/simple_safe_area.dart b/packages/stream_chat_flutter/lib/src/misc/simple_safe_area.dart index 7aeeb8615c..b43c1216e5 100644 --- a/packages/stream_chat_flutter/lib/src/misc/simple_safe_area.dart +++ b/packages/stream_chat_flutter/lib/src/misc/simple_safe_area.dart @@ -20,10 +20,10 @@ class SimpleSafeArea extends StatelessWidget { this.minimum = EdgeInsets.zero, this.maintainBottomViewPadding = false, required this.child, - }) : left = enabled ?? true, - top = enabled ?? true, - right = enabled ?? true, - bottom = enabled ?? true; + }) : left = enabled ?? true, + top = enabled ?? true, + right = enabled ?? true, + bottom = enabled ?? true; /// Creates a [SimpleSafeArea] that avoids system intrusions only on the /// specified sides. diff --git a/packages/stream_chat_flutter/lib/src/misc/swipeable.dart b/packages/stream_chat_flutter/lib/src/misc/swipeable.dart index 45b9b2dceb..8fd78e8c9e 100644 --- a/packages/stream_chat_flutter/lib/src/misc/swipeable.dart +++ b/packages/stream_chat_flutter/lib/src/misc/swipeable.dart @@ -13,10 +13,11 @@ typedef SwipeDirectionCallback = void Function(SwipeDirection direction); /// dismissing action. /// /// Used by [Swipeable.backgroundBuilder]. -typedef BackgroundWidgetBuilder = Widget Function( - BuildContext context, - SwipeUpdateDetails details, -); +typedef BackgroundWidgetBuilder = + Widget Function( + BuildContext context, + SwipeUpdateDetails details, + ); /// The direction in which a [Swipeable] can be swiped. enum SwipeDirection { @@ -32,7 +33,7 @@ enum SwipeDirection { startToEnd, /// The [Swipeable] cannot be swiped by dragging. - none + none, } /// A widget that can be swiped in a specified direction. @@ -95,9 +96,9 @@ class Swipeable extends StatefulWidget { this.dragStartBehavior = DragStartBehavior.start, this.behavior = HitTestBehavior.opaque, }) : assert( - swipeThreshold >= 0.0 && swipeThreshold <= 1.0, - 'swipeThreshold must be between 0.0 and 1.0', - ); + swipeThreshold >= 0.0 && swipeThreshold <= 1.0, + 'swipeThreshold must be between 0.0 and 1.0', + ); /// The widget below this widget in the tree. /// @@ -225,8 +226,7 @@ class _SwipeableClipper extends CustomClipper { } } -class _SwipeableState extends State - with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin { +class _SwipeableState extends State with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin { @override void initState() { super.initState(); @@ -261,13 +261,9 @@ class _SwipeableState extends State } switch (Directionality.of(context)) { case TextDirection.rtl: - return extent < 0 - ? SwipeDirection.startToEnd - : SwipeDirection.endToStart; + return extent < 0 ? SwipeDirection.startToEnd : SwipeDirection.endToStart; case TextDirection.ltr: - return extent > 0 - ? SwipeDirection.startToEnd - : SwipeDirection.endToStart; + return extent > 0 ? SwipeDirection.startToEnd : SwipeDirection.endToStart; } } @@ -280,8 +276,7 @@ class _SwipeableState extends State void _handleDragStart(DragStartDetails details) { _dragUnderway = true; if (_moveController!.isAnimating) { - _dragExtent = - _moveController!.value * _overallDragAxisExtent * _dragExtent.sign; + _dragExtent = _moveController!.value * _overallDragAxisExtent * _dragExtent.sign; _moveController!.stop(); } else { _dragExtent = 0.0; diff --git a/packages/stream_chat_flutter/lib/src/misc/thread_header.dart b/packages/stream_chat_flutter/lib/src/misc/thread_header.dart index 1142ac4507..e4a93fb4d4 100644 --- a/packages/stream_chat_flutter/lib/src/misc/thread_header.dart +++ b/packages/stream_chat_flutter/lib/src/misc/thread_header.dart @@ -57,8 +57,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// and the [ChannelTheme.channelHeaderTheme] property. Modify it to change /// the widget's appearance. /// {@endtemplate} -class StreamThreadHeader extends StatelessWidget - implements PreferredSizeWidget { +class StreamThreadHeader extends StatelessWidget implements PreferredSizeWidget { /// {@macro streamThreadHeader} const StreamThreadHeader({ super.key, @@ -128,7 +127,8 @@ class StreamThreadHeader extends StatelessWidget final channelHeaderTheme = StreamChannelHeaderTheme.of(context); - final defaultSubtitle = subtitle ?? + final defaultSubtitle = + subtitle ?? Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, @@ -151,11 +151,10 @@ class StreamThreadHeader extends StatelessWidget automaticallyImplyLeading: false, toolbarTextStyle: theme.textTheme.bodyMedium, titleTextStyle: theme.textTheme.titleLarge, - systemOverlayStyle: theme.brightness == Brightness.dark - ? SystemUiOverlayStyle.light - : SystemUiOverlayStyle.dark, + systemOverlayStyle: theme.brightness == Brightness.dark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark, elevation: elevation, - leading: leading ?? + leading: + leading ?? (showBackButton ? StreamBackButton( channelId: StreamChannel.of(context).channel.cid, @@ -173,9 +172,7 @@ class StreamThreadHeader extends StatelessWidget width: 250, child: Column( mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: effectiveCenterTitle - ? CrossAxisAlignment.center - : CrossAxisAlignment.stretch, + crossAxisAlignment: effectiveCenterTitle ? CrossAxisAlignment.center : CrossAxisAlignment.stretch, children: [ title ?? Text( diff --git a/packages/stream_chat_flutter/lib/src/poll/creator/poll_option_reorderable_list_view.dart b/packages/stream_chat_flutter/lib/src/poll/creator/poll_option_reorderable_list_view.dart index cff3c96253..8229d6ccd8 100644 --- a/packages/stream_chat_flutter/lib/src/poll/creator/poll_option_reorderable_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/poll/creator/poll_option_reorderable_list_view.dart @@ -116,9 +116,9 @@ class PollOptionListItem extends StatelessWidget { contentPadding: const EdgeInsets.symmetric(vertical: 18), onChanged: switch (onChanged) { final onChanged? => (text) { - final updated = option.copyWith(text: text); - return onChanged.call(updated); - }, + final updated = option.copyWith(text: text); + return onChanged.call(updated); + }, _ => null, }, ), @@ -181,12 +181,10 @@ class PollOptionReorderableListView extends StatefulWidget { final ValueSetter>? onOptionsChanged; @override - State createState() => - _PollOptionReorderableListViewState(); + State createState() => _PollOptionReorderableListViewState(); } -class _PollOptionReorderableListViewState - extends State { +class _PollOptionReorderableListViewState extends State { late Map _focusNodes; late Map _options; diff --git a/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_dialog.dart b/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_dialog.dart index f9d2a76777..e4532c67e3 100644 --- a/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_dialog.dart +++ b/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_dialog.dart @@ -92,8 +92,7 @@ class StreamPollCreatorDialog extends StatefulWidget { final EdgeInsets padding; @override - State createState() => - _StreamPollCreatorDialogState(); + State createState() => _StreamPollCreatorDialogState(); } class _StreamPollCreatorDialogState extends State { @@ -202,12 +201,10 @@ class StreamPollCreatorFullScreenDialog extends StatefulWidget { final EdgeInsets padding; @override - State createState() => - _StreamPollCreatorFullScreenDialogState(); + State createState() => _StreamPollCreatorFullScreenDialogState(); } -class _StreamPollCreatorFullScreenDialogState - extends State { +class _StreamPollCreatorFullScreenDialogState extends State { late final _controller = StreamPollController( poll: widget.poll, config: widget.config, diff --git a/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_widget.dart b/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_widget.dart index dcb9edce7c..93e4872ce9 100644 --- a/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_widget.dart +++ b/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_widget.dart @@ -67,12 +67,10 @@ class StreamPollCreatorWidget extends StatelessWidget { allowDuplicate: config.allowDuplicateOptions, optionsRange: config.optionsRange, initialOptions: [ - for (final option in poll.options) - PollOptionItem(id: option.id, text: option.text), + for (final option in poll.options) PollOptionItem(id: option.id, text: option.text), ], onOptionsChanged: (options) => controller.options = [ - for (final option in options) - PollOption(id: option.id, text: option.text), + for (final option in options) PollOption(id: option.id, text: option.text), ], ), const SizedBox(height: 32), @@ -118,7 +116,8 @@ class StreamPollCreatorWidget extends StatelessWidget { PollSwitchListTile( title: translations.anonymousPollLabel, value: poll.votingVisibility == VotingVisibility.anonymous, - onChanged: (anon) => controller.votingVisibility = anon // + onChanged: (anon) => controller.votingVisibility = + anon // ? VotingVisibility.anonymous : VotingVisibility.public, ), diff --git a/packages/stream_chat_flutter/lib/src/poll/interactor/poll_add_comment_dialog.dart b/packages/stream_chat_flutter/lib/src/poll/interactor/poll_add_comment_dialog.dart index 47c67a7b76..293b10cb7b 100644 --- a/packages/stream_chat_flutter/lib/src/poll/interactor/poll_add_comment_dialog.dart +++ b/packages/stream_chat_flutter/lib/src/poll/interactor/poll_add_comment_dialog.dart @@ -12,14 +12,13 @@ import 'package:stream_chat_flutter/src/utils/extensions.dart'; Future showPollAddCommentDialog({ required BuildContext context, String initialValue = '', -}) => - showDialog( - context: context, - barrierDismissible: false, - builder: (_) => PollAddCommentDialog( - initialValue: initialValue, - ), - ); +}) => showDialog( + context: context, + barrierDismissible: false, + builder: (_) => PollAddCommentDialog( + initialValue: initialValue, + ), +); /// {@template pollAddCommentDialog} /// A dialog that allows the user to add or update a poll comment. diff --git a/packages/stream_chat_flutter/lib/src/poll/interactor/poll_footer.dart b/packages/stream_chat_flutter/lib/src/poll/interactor/poll_footer.dart index 807e5071e0..e03cb45788 100644 --- a/packages/stream_chat_flutter/lib/src/poll/interactor/poll_footer.dart +++ b/packages/stream_chat_flutter/lib/src/poll/interactor/poll_footer.dart @@ -109,8 +109,7 @@ class PollFooter extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - if (visibleOptionCount case final count? - when count < poll.options.length) + if (visibleOptionCount case final count? when count < poll.options.length) PollFooterButton( title: translations.seeAllOptionsLabel(count: poll.options.length), onPressed: onSeeMoreOptions, diff --git a/packages/stream_chat_flutter/lib/src/poll/interactor/poll_options_list_view.dart b/packages/stream_chat_flutter/lib/src/poll/interactor/poll_options_list_view.dart index 2aa627a176..6bb6ccd03b 100644 --- a/packages/stream_chat_flutter/lib/src/poll/interactor/poll_options_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/poll/interactor/poll_options_list_view.dart @@ -198,9 +198,7 @@ class PollOptionItem extends StatelessWidget { if (showProgressBar) OptionVotesProgressBar( value: poll.voteRatioFor(option), - borderRadius: - theme.pollOptionVotesProgressBarBorderRadius ?? - BorderRadius.circular(4), + borderRadius: theme.pollOptionVotesProgressBarBorderRadius ?? BorderRadius.circular(4), trackColor: theme.pollOptionVotesProgressBarTrackColor, valueColor: switch (poll.isOptionWinner(option)) { true => theme.pollOptionVotesProgressBarWinnerColor, @@ -209,7 +207,7 @@ class PollOptionItem extends StatelessWidget { ), ], ), - ) + ), ], ), ), @@ -230,9 +228,9 @@ class OptionVoters extends StatelessWidget { this.overlap = 0.5, required this.voters, }) : assert( - overlap >= 0 && overlap <= 1, - 'Overlap must be between 0 and 1', - ); + overlap >= 0 && overlap <= 1, + 'Overlap must be between 0 and 1', + ); /// The radius of the avatars. final double radius; diff --git a/packages/stream_chat_flutter/lib/src/poll/interactor/poll_suggest_option_dialog.dart b/packages/stream_chat_flutter/lib/src/poll/interactor/poll_suggest_option_dialog.dart index fd4c02f934..35442842e6 100644 --- a/packages/stream_chat_flutter/lib/src/poll/interactor/poll_suggest_option_dialog.dart +++ b/packages/stream_chat_flutter/lib/src/poll/interactor/poll_suggest_option_dialog.dart @@ -12,14 +12,13 @@ import 'package:stream_chat_flutter/src/utils/extensions.dart'; Future showPollSuggestOptionDialog({ required BuildContext context, String initialOption = '', -}) => - showDialog( - context: context, - barrierDismissible: false, - builder: (_) => PollSuggestOptionDialog( - initialOption: initialOption, - ), - ); +}) => showDialog( + context: context, + barrierDismissible: false, + builder: (_) => PollSuggestOptionDialog( + initialOption: initialOption, + ), +); /// {@template pollSuggestOptionDialog} /// A dialog that allows the user to suggest an option for a poll. @@ -39,8 +38,7 @@ class PollSuggestOptionDialog extends StatefulWidget { final String initialOption; @override - State createState() => - _PollSuggestOptionDialogState(); + State createState() => _PollSuggestOptionDialogState(); } class _PollSuggestOptionDialogState extends State { diff --git a/packages/stream_chat_flutter/lib/src/poll/stream_poll_comments_dialog.dart b/packages/stream_chat_flutter/lib/src/poll/stream_poll_comments_dialog.dart index eb6ba6db92..deecc7c6f0 100644 --- a/packages/stream_chat_flutter/lib/src/poll/stream_poll_comments_dialog.dart +++ b/packages/stream_chat_flutter/lib/src/poll/stream_poll_comments_dialog.dart @@ -77,8 +77,7 @@ class StreamPollCommentsDialog extends StatefulWidget { final VoidCallback? onUpdateComment; @override - State createState() => - _StreamPollCommentsDialogState(); + State createState() => _StreamPollCommentsDialogState(); } class _StreamPollCommentsDialogState extends State { diff --git a/packages/stream_chat_flutter/lib/src/poll/stream_poll_option_votes_dialog.dart b/packages/stream_chat_flutter/lib/src/poll/stream_poll_option_votes_dialog.dart index a22e994c57..61f2ae9c9f 100644 --- a/packages/stream_chat_flutter/lib/src/poll/stream_poll_option_votes_dialog.dart +++ b/packages/stream_chat_flutter/lib/src/poll/stream_poll_option_votes_dialog.dart @@ -66,12 +66,10 @@ class StreamPollOptionVotesDialog extends StatefulWidget { final int? pollVotesCount; @override - State createState() => - _StreamPollOptionVotesDialogState(); + State createState() => _StreamPollOptionVotesDialogState(); } -class _StreamPollOptionVotesDialogState - extends State { +class _StreamPollOptionVotesDialogState extends State { late StreamPollVoteListController _controller; @override @@ -83,8 +81,7 @@ class _StreamPollOptionVotesDialogState @override void didUpdateWidget(covariant StreamPollOptionVotesDialog oldWidget) { super.didUpdateWidget(oldWidget); - if (oldWidget.poll.id != widget.poll.id || - oldWidget.option.id != widget.option.id) { + if (oldWidget.poll.id != widget.poll.id || oldWidget.option.id != widget.option.id) { _controller.dispose(); // Dispose the old controller. _initializeController(); // Initialize a new controller. } @@ -141,9 +138,7 @@ class _StreamPollOptionVotesDialogState context.translations.voteCountLabel( count: widget.pollVotesCount, ), - style: isOptionWinner - ? theme.pollOptionWinnerVoteCountTextStyle - : theme.pollOptionVoteCountTextStyle, + style: isOptionWinner ? theme.pollOptionWinnerVoteCountTextStyle : theme.pollOptionVoteCountTextStyle, ), ], ), diff --git a/packages/stream_chat_flutter/lib/src/poll/stream_poll_results_dialog.dart b/packages/stream_chat_flutter/lib/src/poll/stream_poll_results_dialog.dart index e1b5ee1064..ee1c053f9e 100644 --- a/packages/stream_chat_flutter/lib/src/poll/stream_poll_results_dialog.dart +++ b/packages/stream_chat_flutter/lib/src/poll/stream_poll_results_dialog.dart @@ -270,9 +270,7 @@ class PollVotesByOptionItem extends StatelessWidget { vertical: 12, horizontal: 16, ), - decoration: isOptionWinner - ? theme.pollOptionsWinnerDecoration - : theme.pollOptionsDecoration, + decoration: isOptionWinner ? theme.pollOptionsWinnerDecoration : theme.pollOptionsDecoration, child: Column( spacing: 16, mainAxisSize: MainAxisSize.min, @@ -283,9 +281,7 @@ class PollVotesByOptionItem extends StatelessWidget { Expanded( child: Text( option.text, - style: isOptionWinner - ? theme.pollOptionsWinnerTextStyle - : theme.pollOptionsTextStyle, + style: isOptionWinner ? theme.pollOptionsWinnerTextStyle : theme.pollOptionsTextStyle, ), ), const SizedBox(width: 8), @@ -298,9 +294,7 @@ class PollVotesByOptionItem extends StatelessWidget { ], Text( context.translations.voteCountLabel(count: pollVotesCount), - style: isOptionWinner - ? theme.pollOptionsWinnerVoteCountTextStyle - : theme.pollOptionsVoteCountTextStyle, + style: isOptionWinner ? theme.pollOptionsWinnerVoteCountTextStyle : theme.pollOptionsVoteCountTextStyle, ), ], ), diff --git a/packages/stream_chat_flutter/lib/src/poll/stream_poll_text_field.dart b/packages/stream_chat_flutter/lib/src/poll/stream_poll_text_field.dart index 3a4af9b9c4..9a28423978 100644 --- a/packages/stream_chat_flutter/lib/src/poll/stream_poll_text_field.dart +++ b/packages/stream_chat_flutter/lib/src/poll/stream_poll_text_field.dart @@ -90,9 +90,9 @@ class _StreamPollTextFieldState extends State { if (currValue != newValue) { _controller.value = switch (newValue) { final value? => TextEditingValue( - text: value, - selection: TextSelection.collapsed(offset: value.length), - ), + text: value, + selection: TextSelection.collapsed(offset: value.length), + ), _ => TextEditingValue.empty, }; } @@ -129,7 +129,8 @@ class _StreamPollTextFieldState extends State { right: horizontalPadding / 2, ), errorText: widget.errorText, - errorStyle: widget.errorStyle ?? + errorStyle: + widget.errorStyle ?? theme.textTheme.footnote.copyWith( color: theme.colorTheme.accentError, ), diff --git a/packages/stream_chat_flutter/lib/src/reactions/desktop_reactions_builder.dart b/packages/stream_chat_flutter/lib/src/reactions/desktop_reactions_builder.dart index f228833564..86a41a4583 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/desktop_reactions_builder.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/desktop_reactions_builder.dart @@ -41,8 +41,7 @@ class DesktopReactionsBuilder extends StatefulWidget { final bool reverse; @override - State createState() => - _DesktopReactionsBuilderState(); + State createState() => _DesktopReactionsBuilderState(); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { @@ -75,14 +74,12 @@ class _DesktopReactionsBuilderState extends State { final reactionsMap = {}; widget.message.latestReactions?.forEach((element) { - if (!reactionsMap.containsKey(element.type) || - element.user!.id == currentUser.id) { + if (!reactionsMap.containsKey(element.type) || element.user!.id == currentUser.id) { reactionsMap[element.type] = element; } }); - final reactionsList = reactionsMap.values.toList() - ..sort((a, b) => a.user!.id == currentUser.id ? 1 : -1); + final reactionsList = reactionsMap.values.toList()..sort((a, b) => a.user!.id == currentUser.id ? 1 : -1); return PortalTarget( visible: _showReactionsPopup, @@ -177,16 +174,15 @@ class _BottomReaction extends StatelessWidget { onTap: () { if (reaction.userId == userId) { StreamChannel.of(context).channel.deleteReaction( - message, - reaction, - ); + message, + reaction, + ); } else if (reactionIcon != null) { StreamChannel.of(context).channel.sendReaction( - message, - reactionIcon!.toReaction(), - enforceUnique: - StreamChatConfiguration.of(context).enforceUniqueReactions, - ); + message, + reactionIcon!.toReaction(), + enforceUnique: StreamChatConfiguration.of(context).enforceUniqueReactions, + ); } }, child: Card( @@ -195,7 +191,8 @@ class _BottomReaction extends StatelessWidget { // This is done to avoid shadow when background color is transparent. elevation: backgroundColor == Colors.transparent ? 0 : null, shape: RoundedRectangleBorder( - side: borderSide ?? + side: + borderSide ?? BorderSide( color: messageTheme?.reactionsBorderColor ?? Colors.transparent, ), @@ -211,7 +208,8 @@ class _BottomReaction extends StatelessWidget { constraints: BoxConstraints.tight( const Size.square(14), ), - child: reactionIcon?.builder( + child: + reactionIcon?.builder( context, reaction.user?.id == userId, 14, diff --git a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator.dart b/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator.dart index 3df7d1818c..975f548bbe 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator.dart @@ -14,11 +14,12 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// - [onTap]: An optional callback triggered when the reaction indicator /// is tapped. /// {@endtemplate} -typedef ReactionIndicatorBuilder = Widget Function( - BuildContext context, - Message message, - VoidCallback? onTap, -); +typedef ReactionIndicatorBuilder = + Widget Function( + BuildContext context, + Message message, + VoidCallback? onTap, + ); /// {@template streamReactionIndicator} /// A widget that displays a horizontal list of reaction icons that users have @@ -115,8 +116,7 @@ class StreamReactionIndicator extends StatelessWidget { final ownReactions = {...?message.ownReactions?.map((it) => it.type)}; final reactionIcons = {for (final it in this.reactionIcons) it.type: it}; - final sortedReactionGroups = message.reactionGroups?.entries - .sortedByCompare((it) => it.value, reactionSorting); + final sortedReactionGroups = message.reactionGroups?.entries.sortedByCompare((it) => it.value, reactionSorting); final indicatorIcons = sortedReactionGroups?.map( (group) { @@ -156,9 +156,9 @@ class StreamReactionIndicator extends StatelessWidget { child: switch (scrollable) { false => reactionIndicator, true => SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: reactionIndicator, - ), + scrollDirection: Axis.horizontal, + child: reactionIndicator, + ), }, ), ), diff --git a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_icon_list.dart b/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_icon_list.dart index eff65cf6f8..51884d3e21 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_icon_list.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_icon_list.dart @@ -11,10 +11,11 @@ import 'package:stream_chat_flutter/src/misc/reaction_icon.dart'; /// - [context]: The build context. /// - [icon]: The reaction icon data containing type and selection state. /// {@endtemplate} -typedef ReactionIndicatorIconBuilder = Widget Function( - BuildContext context, - ReactionIndicatorIcon icon, -); +typedef ReactionIndicatorIconBuilder = + Widget Function( + BuildContext context, + ReactionIndicatorIcon icon, + ); /// {@template reactionIndicatorIconList} /// A widget that displays a list of reactionIcons that users have reacted with diff --git a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart b/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart index 3b3ece7805..bbaa7f0cca 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart @@ -17,11 +17,12 @@ typedef OnReactionPicked = ValueSetter; /// - [message]: The message to show reactions for. /// - [onReactionPicked]: Callback when a reaction is picked. /// {@endtemplate} -typedef ReactionPickerBuilder = Widget Function( - BuildContext context, - Message message, - OnReactionPicked? onReactionPicked, -); +typedef ReactionPickerBuilder = + Widget Function( + BuildContext context, + Message message, + OnReactionPicked? onReactionPicked, + ); /// {@template streamReactionPicker} /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/reaction_picker.png) @@ -67,17 +68,17 @@ class StreamReactionPicker extends StatelessWidget { final platform = Theme.of(context).platform; return switch (platform) { TargetPlatform.iOS || TargetPlatform.android => StreamReactionPicker( - message: message, - reactionIcons: reactionIcons, - onReactionPicked: onReactionPicked, - ), + message: message, + reactionIcons: reactionIcons, + onReactionPicked: onReactionPicked, + ), _ => StreamReactionPicker( - message: message, - scrollable: false, - borderRadius: BorderRadius.zero, - reactionIcons: reactionIcons, - onReactionPicked: onReactionPicked, - ), + message: message, + scrollable: false, + borderRadius: BorderRadius.zero, + reactionIcons: reactionIcons, + onReactionPicked: onReactionPicked, + ), }; } @@ -162,9 +163,9 @@ class StreamReactionPicker extends StatelessWidget { child: switch (scrollable) { false => reactionPicker, true => SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: reactionPicker, - ), + scrollDirection: Axis.horizontal, + child: reactionPicker, + ), }, ), ); diff --git a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_icon_list.dart b/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_icon_list.dart index 9a7bfc7f6e..d692798c5c 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_icon_list.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_icon_list.dart @@ -24,11 +24,12 @@ typedef OnReactionPickerIconPressed = ValueSetter; /// - [icon]: The reaction icon data containing type and selection state. /// - [onPressed]: Callback when the reaction icon is pressed. /// {@endtemplate} -typedef ReactionPickerIconBuilder = Widget Function( - BuildContext context, - ReactionPickerIcon icon, - VoidCallback? onPressed, -); +typedef ReactionPickerIconBuilder = + Widget Function( + BuildContext context, + ReactionPickerIcon icon, + VoidCallback? onPressed, + ); /// {@template reactionPickerIconList} /// A widget that displays a list of reactionIcons that can be picked by a user. diff --git a/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble.dart index 88386c9d09..43cb68814c 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble.dart @@ -48,8 +48,7 @@ class StreamReactionBubble extends StatelessWidget { Widget build(BuildContext context) { final reactionIcons = StreamChatConfiguration.of(context).reactionIcons; final totalReactions = reactions.length; - final offset = - totalReactions > 1 ? 16.0.mirrorConditionally(flipTail) : 2.0; + final offset = totalReactions > 1 ? 16.0.mirrorConditionally(flipTail) : 2.0; return Stack( alignment: Alignment.center, children: [ @@ -84,7 +83,7 @@ class StreamReactionBubble extends StatelessWidget { reaction, reactionIcons, ), - ) + ), ], ), ), @@ -185,9 +184,9 @@ class ReactionBubblePainter extends CustomPainter { final path = Path() ..addOval( Rect.fromCircle( - center: const Offset(4, 3).mirrorConditionally(flipTail) + - Offset(tailCirclesSpace, tailCirclesSpace) - .mirrorConditionally(flipTail), + center: + const Offset(4, 3).mirrorConditionally(flipTail) + + Offset(tailCirclesSpace, tailCirclesSpace).mirrorConditionally(flipTail), radius: 4, ), ); @@ -203,9 +202,9 @@ class ReactionBubblePainter extends CustomPainter { final path = Path() ..addOval( Rect.fromCircle( - center: const Offset(4, 3).mirrorConditionally(flipTail) + - Offset(tailCirclesSpace, tailCirclesSpace) - .mirrorConditionally(flipTail), + center: + const Offset(4, 3).mirrorConditionally(flipTail) + + Offset(tailCirclesSpace, tailCirclesSpace).mirrorConditionally(flipTail), radius: 2, ), ); @@ -218,12 +217,14 @@ class ReactionBubblePainter extends CustomPainter { ..strokeWidth = 1; final path = Path() - ..addOval(Rect.fromCircle( - center: const Offset(4, 3).mirrorConditionally(flipTail) + - Offset(tailCirclesSpace, tailCirclesSpace) - .mirrorConditionally(flipTail), - radius: 2, - )); + ..addOval( + Rect.fromCircle( + center: + const Offset(4, 3).mirrorConditionally(flipTail) + + Offset(tailCirclesSpace, tailCirclesSpace).mirrorConditionally(flipTail), + radius: 2, + ), + ); canvas.drawPath(path, paint); } diff --git a/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble_overlay.dart b/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble_overlay.dart index bd1b50034e..94863fc78d 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble_overlay.dart @@ -7,11 +7,12 @@ import 'package:flutter_portal/flutter_portal.dart'; import 'package:stream_chat_flutter/src/misc/size_change_listener.dart'; /// Signature for building a custom ReactionBubble widget. -typedef ReactionBubbleBuilder = Widget Function( - BuildContext context, - ReactionBubbleConfig config, - Widget child, -); +typedef ReactionBubbleBuilder = + Widget Function( + BuildContext context, + ReactionBubbleConfig config, + Widget child, + ); /// Defines the anchor settings for positioning a ReactionBubble relative to a /// target widget. @@ -29,16 +30,16 @@ class ReactionBubbleAnchor { const ReactionBubbleAnchor.topEnd({ this.offset = Offset.zero, this.shiftToWithinBound = const AxisFlag(x: true), - }) : target = AlignmentDirectional.topEnd, - follower = AlignmentDirectional.bottomCenter; + }) : target = AlignmentDirectional.topEnd, + follower = AlignmentDirectional.bottomCenter; /// Creates an anchor that positions the bubble at the top-start of the /// target widget. const ReactionBubbleAnchor.topStart({ this.offset = Offset.zero, this.shiftToWithinBound = const AxisFlag(x: true), - }) : target = AlignmentDirectional.topStart, - follower = AlignmentDirectional.bottomCenter; + }) : target = AlignmentDirectional.topStart, + follower = AlignmentDirectional.bottomCenter; /// Additional offset applied to the bubble position. final Offset offset; @@ -118,7 +119,7 @@ class _ReactionBubbleOverlayState extends State { }) { final childEdgeX = switch (reverse) { true => availableSpace.width - childSize.width, - false => childSize.width + false => childSize.width, }; final idealBubbleLeft = childEdgeX - (bubbleRect.width / 2); @@ -243,8 +244,7 @@ class ReactionBubbleConfig { maskWidth: maskWidth ?? this.maskWidth, borderWidth: borderWidth ?? this.borderWidth, bigTailCircleRadius: bigTailCircleRadius ?? this.bigTailCircleRadius, - smallTailCircleRadius: - smallTailCircleRadius ?? this.smallTailCircleRadius, + smallTailCircleRadius: smallTailCircleRadius ?? this.smallTailCircleRadius, tailAlignment: tailAlignment ?? this.tailAlignment, ); } @@ -283,16 +283,16 @@ class ReactionBubblePainter extends CustomPainter { /// Creates a [ReactionBubblePainter] with the specified configuration. ReactionBubblePainter({ this.config = const ReactionBubbleConfig(), - }) : _fillPaint = Paint() - ..color = config.fillColor ?? Colors.white - ..style = PaintingStyle.fill, - _maskPaint = Paint() - ..color = config.maskColor ?? Colors.white - ..style = PaintingStyle.fill, - _borderPaint = Paint() - ..color = config.borderColor ?? Colors.black - ..style = PaintingStyle.stroke - ..strokeWidth = config.borderWidth; + }) : _fillPaint = Paint() + ..color = config.fillColor ?? Colors.white + ..style = PaintingStyle.fill, + _maskPaint = Paint() + ..color = config.maskColor ?? Colors.white + ..style = PaintingStyle.fill, + _borderPaint = Paint() + ..color = config.borderColor ?? Colors.black + ..style = PaintingStyle.stroke + ..strokeWidth = config.borderWidth; /// Configuration used to style the bubble. final ReactionBubbleConfig config; diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart index dc685d09ae..f5fe3a82c5 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart @@ -46,21 +46,21 @@ class StreamChannelGridTile extends StatelessWidget { Widget? footer, GestureTapCallback? onTap, GestureLongPressCallback? onLongPress, - }) => - StreamChannelGridTile( - key: key ?? this.key, - channel: channel ?? this.channel, - footer: footer ?? this.footer, - onTap: onTap ?? this.onTap, - onLongPress: onLongPress ?? this.onLongPress, - child: child ?? this.child, - ); + }) => StreamChannelGridTile( + key: key ?? this.key, + channel: channel ?? this.channel, + footer: footer ?? this.footer, + onTap: onTap ?? this.onTap, + onLongPress: onLongPress ?? this.onLongPress, + child: child ?? this.child, + ); @override Widget build(BuildContext context) { final channelPreviewTheme = StreamChannelPreviewTheme.of(context); - final child = this.child ?? + final child = + this.child ?? StreamChannelAvatar( channel: channel, borderRadius: BorderRadius.circular(32), @@ -70,7 +70,8 @@ class StreamChannelGridTile extends StatelessWidget { ), ); - final footer = this.footer ?? + final footer = + this.footer ?? StreamChannelName( channel: channel, textStyle: channelPreviewTheme.titleStyle, diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_view.dart index e391451647..febc711b5c 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_view.dart @@ -7,13 +7,12 @@ import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_loading_w import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Default grid delegate for [StreamChannelGridView]. -const defaultChannelGridViewDelegate = - SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4); +const defaultChannelGridViewDelegate = SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4); /// Signature for the item builder that creates the children of the /// [StreamChannelGridView]. -typedef StreamChannelGridViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamChannelGridViewIndexedWidgetBuilder = + StreamScrollViewIndexedWidgetBuilder; /// A [GridView] that shows a grid of [User]s, /// it uses [StreamChannelGridTile] as a default item. @@ -364,8 +363,7 @@ class StreamChannelGridView extends StatelessWidget { ), ); }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.grid( + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.grid( onTap: controller.retry, error: Text( context.translations.loadingChannelsError, diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart index 1d950ce44f..6c12bd0421 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart @@ -37,9 +37,9 @@ class StreamChannelListTile extends StatelessWidget { this.selected = false, this.selectedTileColor, }) : assert( - channel.state != null, - 'Channel ${channel.id} is not initialized', - ); + channel.state != null, + 'Channel ${channel.id} is not initialized', + ); /// The channel to display. final Channel channel; @@ -133,12 +133,10 @@ class StreamChannelListTile extends StatelessWidget { onLongPress: onLongPress ?? this.onLongPress, visualDensity: visualDensity ?? this.visualDensity, contentPadding: contentPadding ?? this.contentPadding, - sendingIndicatorBuilder: - sendingIndicatorBuilder ?? this.sendingIndicatorBuilder, + sendingIndicatorBuilder: sendingIndicatorBuilder ?? this.sendingIndicatorBuilder, tileColor: tileColor ?? this.tileColor, trailing: trailing ?? this.trailing, - unreadIndicatorBuilder: - unreadIndicatorBuilder ?? this.unreadIndicatorBuilder, + unreadIndicatorBuilder: unreadIndicatorBuilder ?? this.unreadIndicatorBuilder, selected: selected ?? this.selected, selectedTileColor: selectedTileColor ?? this.selectedTileColor, ); @@ -153,24 +151,28 @@ class StreamChannelListTile extends StatelessWidget { final streamChatTheme = StreamChatTheme.of(context); final streamChat = StreamChat.of(context); - final leading = this.leading ?? + final leading = + this.leading ?? StreamChannelAvatar( channel: channel, ); - final title = this.title ?? + final title = + this.title ?? StreamChannelName( channel: channel, textStyle: channelPreviewTheme.titleStyle, ); - final subtitle = this.subtitle ?? + final subtitle = + this.subtitle ?? ChannelListTileSubtitle( channel: channel, textStyle: channelPreviewTheme.subtitleStyle, ); - final trailing = this.trailing ?? + final trailing = + this.trailing ?? ChannelLastMessageDate( channel: channel, textStyle: channelPreviewTheme.lastMessageAtStyle, @@ -191,8 +193,7 @@ class StreamChannelListTile extends StatelessWidget { leading: leading, tileColor: tileColor, selected: selected, - selectedTileColor: selectedTileColor ?? - StreamChatTheme.of(context).colorTheme.borders, + selectedTileColor: selectedTileColor ?? StreamChatTheme.of(context).colorTheme.borders, title: Row( children: [ Expanded(child: title), @@ -204,8 +205,7 @@ class StreamChannelListTile extends StatelessWidget { if (members.isEmpty) { return const Empty(); } - return unreadIndicatorBuilder?.call(context) ?? - StreamUnreadIndicator.channels(cid: channel.cid); + return unreadIndicatorBuilder?.call(context) ?? StreamUnreadIndicator.channels(cid: channel.cid); }, ), ], @@ -227,26 +227,26 @@ class StreamChannelListTile extends StatelessWidget { (m) => !m.shadowed && !m.isDeleted, ); - if (lastMessage == null || - (lastMessage.user?.id != currentUser.id)) { + if (lastMessage == null || (lastMessage.user?.id != currentUser.id)) { return const Empty(); } - final hasNonUrlAttachments = lastMessage.attachments - .any((it) => it.type != AttachmentType.urlPreview); + final hasNonUrlAttachments = lastMessage.attachments.any( + (it) => it.type != AttachmentType.urlPreview, + ); return Padding( padding: const EdgeInsets.only(right: 4), child: sendingIndicatorBuilder?.call(context, lastMessage) ?? - SendingIndicatorBuilder( - messageTheme: streamChatTheme.ownMessageTheme, - message: lastMessage, - hasNonUrlAttachments: hasNonUrlAttachments, - streamChat: streamChat, - streamChatTheme: streamChatTheme, - channel: channel, - ), + SendingIndicatorBuilder( + messageTheme: streamChatTheme.ownMessageTheme, + message: lastMessage, + hasNonUrlAttachments: hasNonUrlAttachments, + streamChat: streamChat, + streamChatTheme: streamChatTheme, + channel: channel, + ), ); }, ), @@ -268,9 +268,9 @@ class ChannelLastMessageDate extends StatelessWidget { this.textStyle, this.formatter, }) : assert( - channel.state != null, - 'Channel ${channel.id} is not initialized', - ); + channel.state != null, + 'Channel ${channel.id} is not initialized', + ); /// The channel to display the last message date for. final Channel channel; @@ -303,9 +303,9 @@ class ChannelListTileSubtitle extends StatelessWidget { required this.channel, this.textStyle, }) : assert( - channel.state != null, - 'Channel ${channel.id} is not initialized', - ); + channel.state != null, + 'Channel ${channel.id} is not initialized', + ); /// The channel to create the subtitle from. final Channel channel; @@ -351,9 +351,9 @@ class ChannelLastMessageText extends StatefulWidget { this.textStyle, this.lastMessagePredicate = _defaultLastMessagePredicate, }) : assert( - channel.state != null, - 'Channel ${channel.id} is not initialized', - ); + channel.state != null, + 'Channel ${channel.id} is not initialized', + ); /// The channel to display the last message of. final Channel channel; diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart index 3541963b3f..ecd2d03523 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart @@ -11,13 +11,12 @@ Widget defaultChannelListViewSeparatorBuilder( BuildContext context, List items, int index, -) => - const StreamChannelListSeparator(); +) => const StreamChannelListSeparator(); /// Signature for the item builder that creates the children of the /// [StreamChannelListView]. -typedef StreamChannelListViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamChannelListViewIndexedWidgetBuilder = + StreamScrollViewIndexedWidgetBuilder; /// A [ListView] that shows a list of [Channel]s, /// it uses [StreamChannelListTile] as a default item. @@ -338,8 +337,7 @@ class StreamChannelListView extends StatelessWidget { ), ); }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.list( + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.list( onTap: controller.retry, error: Text(context.translations.loadingChannelsError), ), diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/draft_scroll_view/stream_draft_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/draft_scroll_view/stream_draft_list_view.dart index 1c59029017..a84fbc877c 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/draft_scroll_view/stream_draft_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/draft_scroll_view/stream_draft_list_view.dart @@ -11,13 +11,11 @@ Widget defaultDraftListViewSeparatorBuilder( BuildContext context, List drafts, int index, -) => - const StreamDraftListSeparator(); +) => const StreamDraftListSeparator(); /// Signature for the item builder that creates the children of the /// [StreamDraftListView]. -typedef StreamDraftListViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamDraftListViewIndexedWidgetBuilder = StreamScrollViewIndexedWidgetBuilder; /// {@template streamDraftListView} /// A [ListView] that shows a list of [Draft]'s. It uses a @@ -331,8 +329,7 @@ class StreamDraftListView extends StatelessWidget { ), ); }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.list( + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.list( onTap: controller.retry, error: Text(context.translations.loadingMessagesError), ), diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_grid_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_grid_view.dart index adf97a7860..bab71fd799 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_grid_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_grid_view.dart @@ -7,13 +7,11 @@ import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_loading_w import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Default grid delegate for [StreamMemberGridView]. -const defaultMemberGridViewDelegate = - SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4); +const defaultMemberGridViewDelegate = SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4); /// Signature for the item builder that creates the children of the /// [StreamMemberGridView]. -typedef StreamMemberGridViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamMemberGridViewIndexedWidgetBuilder = StreamScrollViewIndexedWidgetBuilder; /// Signature for the member grid tile, currently equal to [StreamUserGridTile]. typedef StreamMemberGridTile = StreamUserGridTile; @@ -365,8 +363,7 @@ class StreamMemberGridView extends StatelessWidget { ), ); }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.grid( + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.grid( onTap: controller.retry, error: Text( context.translations.loadingUsersError, diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_list_view.dart index fcdad5379e..994f431b3f 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_list_view.dart @@ -11,13 +11,11 @@ Widget defaultMemberListViewSeparatorBuilder( BuildContext context, List members, int index, -) => - const StreamUserListSeparator(); +) => const StreamUserListSeparator(); /// Signature for the item builder that creates the children of the /// [StreamMemberListView]. -typedef StreamMemberListViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamMemberListViewIndexedWidgetBuilder = StreamScrollViewIndexedWidgetBuilder; /// Signature for the member grid tile, currently equal to [StreamUserListTile]. typedef StreamMemberListTile = StreamUserListTile; @@ -278,86 +276,85 @@ class StreamMemberListView extends StatelessWidget { @override Widget build(BuildContext context) => PagedValueListView( - scrollDirection: scrollDirection, - padding: padding, - physics: physics, - reverse: reverse, - controller: controller, - scrollController: scrollController, - primary: primary, - shrinkWrap: shrinkWrap, - addAutomaticKeepAlives: addAutomaticKeepAlives, - addRepaintBoundaries: addRepaintBoundaries, - addSemanticIndexes: addSemanticIndexes, - keyboardDismissBehavior: keyboardDismissBehavior, - restorationId: restorationId, - dragStartBehavior: dragStartBehavior, - cacheExtent: cacheExtent, - clipBehavior: clipBehavior, - loadMoreTriggerIndex: loadMoreTriggerIndex, - separatorBuilder: separatorBuilder, - itemBuilder: (context, members, index) { - final member = members[index]; - final onTap = onMemberTap; - final onLongPress = onMemberLongPress; + scrollDirection: scrollDirection, + padding: padding, + physics: physics, + reverse: reverse, + controller: controller, + scrollController: scrollController, + primary: primary, + shrinkWrap: shrinkWrap, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + keyboardDismissBehavior: keyboardDismissBehavior, + restorationId: restorationId, + dragStartBehavior: dragStartBehavior, + cacheExtent: cacheExtent, + clipBehavior: clipBehavior, + loadMoreTriggerIndex: loadMoreTriggerIndex, + separatorBuilder: separatorBuilder, + itemBuilder: (context, members, index) { + final member = members[index]; + final onTap = onMemberTap; + final onLongPress = onMemberLongPress; - final streamUserListTile = StreamMemberListTile( - user: member.user!, - onTap: onTap == null ? null : () => onTap(member), - onLongPress: onLongPress == null ? null : () => onLongPress(member), - ); + final streamUserListTile = StreamMemberListTile( + user: member.user!, + onTap: onTap == null ? null : () => onTap(member), + onLongPress: onLongPress == null ? null : () => onLongPress(member), + ); - return itemBuilder?.call( - context, - members, - index, - streamUserListTile, - ) ?? - streamUserListTile; - }, - emptyBuilder: (context) { - final chatThemeData = StreamChatTheme.of(context); - return emptyBuilder?.call(context) ?? - Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( - size: 148, - icon: StreamSvgIcons.user, - color: chatThemeData.colorTheme.disabled, - ), - emptyTitle: Text( - context.translations.noUsersLabel, - style: chatThemeData.textTheme.headline, - ), - ), + return itemBuilder?.call( + context, + members, + index, + streamUserListTile, + ) ?? + streamUserListTile; + }, + emptyBuilder: (context) { + final chatThemeData = StreamChatTheme.of(context); + return emptyBuilder?.call(context) ?? + Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: StreamScrollViewEmptyWidget( + emptyIcon: StreamSvgIcon( + size: 148, + icon: StreamSvgIcons.user, + color: chatThemeData.colorTheme.disabled, + ), + emptyTitle: Text( + context.translations.noUsersLabel, + style: chatThemeData.textTheme.headline, ), - ); - }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.list( - onTap: controller.retry, - error: Text(context.translations.loadingUsersError), + ), + ), + ); + }, + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.list( + onTap: controller.retry, + error: Text(context.translations.loadingUsersError), + ), + loadMoreIndicatorBuilder: (context) => const Center( + child: Padding( + padding: EdgeInsets.all(16), + child: StreamScrollViewLoadMoreIndicator(), + ), + ), + loadingBuilder: (context) => + loadingBuilder?.call(context) ?? + const Center( + child: StreamScrollViewLoadingWidget(), ), - loadMoreIndicatorBuilder: (context) => const Center( - child: Padding( - padding: EdgeInsets.all(16), - child: StreamScrollViewLoadMoreIndicator(), + errorBuilder: (context, error) => + errorBuilder?.call(context, error) ?? + Center( + child: StreamScrollViewErrorWidget( + errorTitle: Text(context.translations.loadingUsersError), + onRetryPressed: controller.refresh, ), ), - loadingBuilder: (context) => - loadingBuilder?.call(context) ?? - const Center( - child: StreamScrollViewLoadingWidget(), - ), - errorBuilder: (context, error) => - errorBuilder?.call(context, error) ?? - Center( - child: StreamScrollViewErrorWidget( - errorTitle: Text(context.translations.loadingUsersError), - onRetryPressed: controller.refresh, - ), - ), - ); + ); } diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart index e7a939f942..87067d5f34 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart @@ -7,13 +7,11 @@ import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_loading_w import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Default grid delegate for [StreamMessageSearchGridView]. -const defaultMessageSearchGridViewDelegate = - SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4); +const defaultMessageSearchGridViewDelegate = SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4); /// Signature for the item builder that creates the children of the /// [StreamMessageSearchGridView]. -typedef StreamMessageSearchGridViewIndexedWidgetBuilder - = PagedValueScrollViewIndexedWidgetBuilder; +typedef StreamMessageSearchGridViewIndexedWidgetBuilder = PagedValueScrollViewIndexedWidgetBuilder; /// A [GridView] that shows a grid of [GetMessageResponse]s, /// it uses [StreamMessageSearchGridTile] as a default item. @@ -336,8 +334,7 @@ class StreamMessageSearchGridView extends StatelessWidget { ), ); }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.grid( + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.grid( onTap: controller.retry, error: Text(context.translations.loadingMessagesError), ), diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_tile.dart index 23575f05bc..ea31da2c12 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_tile.dart @@ -91,20 +91,19 @@ class StreamMessageSearchListTile extends StatelessWidget { Color? tileColor, VisualDensity? visualDensity, EdgeInsetsGeometry? contentPadding, - }) => - StreamMessageSearchListTile( - key: key ?? this.key, - messageResponse: messageResponse ?? this.messageResponse, - leading: leading ?? this.leading, - title: title ?? this.title, - subtitle: subtitle ?? this.subtitle, - trailing: trailing ?? this.trailing, - onTap: onTap ?? this.onTap, - onLongPress: onLongPress ?? this.onLongPress, - tileColor: tileColor ?? this.tileColor, - visualDensity: visualDensity ?? this.visualDensity, - contentPadding: contentPadding ?? this.contentPadding, - ); + }) => StreamMessageSearchListTile( + key: key ?? this.key, + messageResponse: messageResponse ?? this.messageResponse, + leading: leading ?? this.leading, + title: title ?? this.title, + subtitle: subtitle ?? this.subtitle, + trailing: trailing ?? this.trailing, + onTap: onTap ?? this.onTap, + onLongPress: onLongPress ?? this.onLongPress, + tileColor: tileColor ?? this.tileColor, + visualDensity: visualDensity ?? this.visualDensity, + contentPadding: contentPadding ?? this.contentPadding, + ); @override Widget build(BuildContext context) { @@ -112,7 +111,8 @@ class StreamMessageSearchListTile extends StatelessWidget { final user = message.user!; final channelPreviewTheme = StreamChannelPreviewTheme.of(context); - final leading = this.leading ?? + final leading = + this.leading ?? StreamUserAvatar( user: user, constraints: const BoxConstraints.tightFor( @@ -121,14 +121,15 @@ class StreamMessageSearchListTile extends StatelessWidget { ), ); - final title = this.title ?? + final title = + this.title ?? MessageSearchListTileTitle( messageResponse: messageResponse, - textStyle: channelPreviewTheme.titleStyle - ?.copyWith(overflow: TextOverflow.ellipsis), + textStyle: channelPreviewTheme.titleStyle?.copyWith(overflow: TextOverflow.ellipsis), ); - final subtitle = this.subtitle ?? + final subtitle = + this.subtitle ?? Row( children: [ Expanded( @@ -185,9 +186,7 @@ class MessageSearchListTileTitle extends StatelessWidget { TextSpan( children: [ TextSpan( - text: user.id == StreamChat.of(context).currentUser?.id - ? context.translations.youText - : user.name, + text: user.id == StreamChat.of(context).currentUser?.id ? context.translations.youText : user.name, ), if (channelName != null) ...[ TextSpan( diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart index 2f4c75468f..ee96feef28 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart @@ -11,14 +11,12 @@ Widget defaultMessageSearchListViewSeparatorBuilder( BuildContext context, List responses, int index, -) => - const StreamMessageSearchListSeparator(); +) => const StreamMessageSearchListSeparator(); /// Signature for the item builder that creates the children of the /// [StreamMessageSearchListView]. -typedef StreamMessageSearchListViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamMessageSearchListViewIndexedWidgetBuilder = + StreamScrollViewIndexedWidgetBuilder; /// A [ListView] that shows a list of [GetMessageResponse]s, /// it uses [StreamMessageSearchListTile] as a default item. @@ -81,8 +79,7 @@ class StreamMessageSearchListView extends StatelessWidget { final StreamMessageSearchListViewIndexedWidgetBuilder? itemBuilder; /// A builder that is called to build the list separator. - final PagedValueScrollViewIndexedWidgetBuilder - separatorBuilder; + final PagedValueScrollViewIndexedWidgetBuilder separatorBuilder; /// A builder that is called to build the empty state of the list. final WidgetBuilder? emptyBuilder; @@ -308,8 +305,7 @@ class StreamMessageSearchListView extends StatelessWidget { final streamMessageSearchListTile = StreamMessageSearchListTile( messageResponse: messageResponse, onTap: onTap == null ? null : () => onTap(messageResponse), - onLongPress: - onLongPress == null ? null : () => onLongPress(messageResponse), + onLongPress: onLongPress == null ? null : () => onLongPress(messageResponse), ); return itemBuilder?.call( @@ -340,8 +336,7 @@ class StreamMessageSearchListView extends StatelessWidget { ), ); }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.list( + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.list( onTap: controller.retry, error: Text(context.translations.loadingMessagesError), ), diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart index dcee2b76a4..d9c092a1d3 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart @@ -1,7 +1,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:photo_manager/photo_manager.dart' - show AssetEntity, ThumbnailFormat, ThumbnailSize; +import 'package:photo_manager/photo_manager.dart' show AssetEntity, ThumbnailFormat, ThumbnailSize; import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_error_widget.dart'; import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_load_more_error.dart'; @@ -10,8 +9,7 @@ import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_loading_w import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Default grid delegate for [StreamPhotoGallery]. -const defaultStreamPhotoGalleryDelegate = - SliverGridDelegateWithFixedCrossAxisCount( +const defaultStreamPhotoGalleryDelegate = SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, mainAxisSpacing: 2, crossAxisSpacing: 2, @@ -19,8 +17,8 @@ const defaultStreamPhotoGalleryDelegate = /// Signature for the item builder that creates the children of the /// [StreamPhotoGallery]. -typedef StreamPhotoGalleryIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamPhotoGalleryIndexedWidgetBuilder = + StreamScrollViewIndexedWidgetBuilder; /// Widget used to display a gallery of photos in the form of grid. class StreamPhotoGallery extends StatelessWidget { diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_controller.dart b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_controller.dart index eb9b21bbf9..8d25cb561e 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_controller.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_controller.dart @@ -3,8 +3,7 @@ import 'package:photo_manager/photo_manager.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// -class StreamPhotoGalleryController - extends PagedValueNotifier { +class StreamPhotoGalleryController extends PagedValueNotifier { /// StreamPhotoGalleryController({ this.limit = 50, diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart index 6b7ef7ebdc..4e124a6386 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart @@ -60,18 +60,17 @@ class StreamPhotoGalleryTile extends StatelessWidget { ThumbnailFormat? thumbnailFormat, int? thumbnailQuality, double? thumbnailScale, - }) => - StreamPhotoGalleryTile( - key: key ?? this.key, - media: media ?? this.media, - selected: selected ?? this.selected, - onTap: onTap ?? this.onTap, - onLongPress: onLongPress ?? this.onLongPress, - thumbnailSize: thumbnailSize ?? this.thumbnailSize, - thumbnailFormat: thumbnailFormat ?? this.thumbnailFormat, - thumbnailQuality: thumbnailQuality ?? this.thumbnailQuality, - thumbnailScale: thumbnailScale ?? this.thumbnailScale, - ); + }) => StreamPhotoGalleryTile( + key: key ?? this.key, + media: media ?? this.media, + selected: selected ?? this.selected, + onTap: onTap ?? this.onTap, + onLongPress: onLongPress ?? this.onLongPress, + thumbnailSize: thumbnailSize ?? this.thumbnailSize, + thumbnailFormat: thumbnailFormat ?? this.thumbnailFormat, + thumbnailQuality: thumbnailQuality ?? this.thumbnailQuality, + thumbnailScale: thumbnailScale ?? this.thumbnailScale, + ); @override Widget build(BuildContext context) { @@ -251,7 +250,8 @@ class MediaThumbnailProvider extends ImageProvider { int get hashCode => Object.hash(media, size, format, quality, scale); @override - String toString() => '$runtimeType(' + String toString() => + '$runtimeType(' 'media: $media, ' 'size: $size, ' 'format: $format, ' diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_tile.dart index 82ad280d74..6dd52422f3 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_tile.dart @@ -53,17 +53,16 @@ class StreamPollVoteListTile extends StatelessWidget { Color? tileColor, BorderRadiusGeometry? borderRadius, EdgeInsetsGeometry? contentPadding, - }) => - StreamPollVoteListTile( - key: key ?? this.key, - pollVote: pollVote ?? this.pollVote, - showAnswerText: showAnswerText ?? this.showAnswerText, - onTap: onTap ?? this.onTap, - onLongPress: onLongPress ?? this.onLongPress, - tileColor: tileColor ?? this.tileColor, - borderRadius: borderRadius ?? this.borderRadius, - contentPadding: contentPadding ?? this.contentPadding, - ); + }) => StreamPollVoteListTile( + key: key ?? this.key, + pollVote: pollVote ?? this.pollVote, + showAnswerText: showAnswerText ?? this.showAnswerText, + onTap: onTap ?? this.onTap, + onLongPress: onLongPress ?? this.onLongPress, + tileColor: tileColor ?? this.tileColor, + borderRadius: borderRadius ?? this.borderRadius, + contentPadding: contentPadding ?? this.contentPadding, + ); @override Widget build(BuildContext context) { @@ -81,8 +80,7 @@ class StreamPollVoteListTile extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (pollVote.answerText case final answerText? - when showAnswerText) ...[ + if (pollVote.answerText case final answerText? when showAnswerText) ...[ Text( answerText, style: theme.textTheme.headlineBold.copyWith( @@ -96,8 +94,7 @@ class StreamPollVoteListTile extends StatelessWidget { if (pollVote.user case final user?) ...[ StreamUserAvatar( user: user, - constraints: - BoxConstraints.tight(const Size.fromRadius(10)), + constraints: BoxConstraints.tight(const Size.fromRadius(10)), showOnlineStatus: false, ), Expanded( @@ -118,7 +115,7 @@ class StreamPollVoteListTile extends StatelessWidget { dateTime: pollVote.updatedAt.toLocal(), ), ], - ) + ), ], ), ), diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_view.dart index 13aecdb471..ff84097872 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_view.dart @@ -17,13 +17,12 @@ Widget defaultPollVoteListViewSeparatorBuilder( BuildContext context, List pollVotes, int index, -) => - const SizedBox(height: 8); +) => const SizedBox(height: 8); /// Signature for the item builder that creates the children of the /// [StreamPollVoteListView]. -typedef StreamPollVoteListViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamPollVoteListViewIndexedWidgetBuilder = + StreamScrollViewIndexedWidgetBuilder; /// {@template streamPollVoteListView} /// A [ListView] that shows a list of [PollVote] for a poll. It uses a @@ -283,87 +282,85 @@ class StreamPollVoteListView extends StatelessWidget { @override Widget build(BuildContext context) => PagedValueListView( - scrollDirection: scrollDirection, - padding: padding, - physics: physics, - reverse: reverse, - controller: controller, - scrollController: scrollController, - primary: primary, - shrinkWrap: shrinkWrap, - addAutomaticKeepAlives: addAutomaticKeepAlives, - addRepaintBoundaries: addRepaintBoundaries, - addSemanticIndexes: addSemanticIndexes, - keyboardDismissBehavior: keyboardDismissBehavior, - restorationId: restorationId, - dragStartBehavior: dragStartBehavior, - cacheExtent: cacheExtent, - clipBehavior: clipBehavior, - loadMoreTriggerIndex: loadMoreTriggerIndex, - separatorBuilder: separatorBuilder, - itemBuilder: (context, pollVotes, index) { - final pollVote = pollVotes[index]; - final onTap = onPollVoteTap; - final onLongPress = onPollVoteLongPress; + scrollDirection: scrollDirection, + padding: padding, + physics: physics, + reverse: reverse, + controller: controller, + scrollController: scrollController, + primary: primary, + shrinkWrap: shrinkWrap, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + keyboardDismissBehavior: keyboardDismissBehavior, + restorationId: restorationId, + dragStartBehavior: dragStartBehavior, + cacheExtent: cacheExtent, + clipBehavior: clipBehavior, + loadMoreTriggerIndex: loadMoreTriggerIndex, + separatorBuilder: separatorBuilder, + itemBuilder: (context, pollVotes, index) { + final pollVote = pollVotes[index]; + final onTap = onPollVoteTap; + final onLongPress = onPollVoteLongPress; - final streamPollVoteListTile = StreamPollVoteListTile( - pollVote: pollVote, - onTap: onTap == null ? null : () => onTap(pollVote), - onLongPress: - onLongPress == null ? null : () => onLongPress(pollVote), - ); + final streamPollVoteListTile = StreamPollVoteListTile( + pollVote: pollVote, + onTap: onTap == null ? null : () => onTap(pollVote), + onLongPress: onLongPress == null ? null : () => onLongPress(pollVote), + ); - return itemBuilder?.call( - context, - pollVotes, - index, - streamPollVoteListTile, - ) ?? - streamPollVoteListTile; - }, - emptyBuilder: (context) { - final chatThemeData = StreamChatTheme.of(context); - return emptyBuilder?.call(context) ?? - Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( - size: 148, - icon: StreamSvgIcons.polls, - color: chatThemeData.colorTheme.disabled, - ), - emptyTitle: Text( - context.translations.noPollVotesLabel, - style: chatThemeData.textTheme.headline, - ), - ), + return itemBuilder?.call( + context, + pollVotes, + index, + streamPollVoteListTile, + ) ?? + streamPollVoteListTile; + }, + emptyBuilder: (context) { + final chatThemeData = StreamChatTheme.of(context); + return emptyBuilder?.call(context) ?? + Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: StreamScrollViewEmptyWidget( + emptyIcon: StreamSvgIcon( + size: 148, + icon: StreamSvgIcons.polls, + color: chatThemeData.colorTheme.disabled, + ), + emptyTitle: Text( + context.translations.noPollVotesLabel, + style: chatThemeData.textTheme.headline, ), - ); - }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.list( - onTap: controller.retry, - error: Text(context.translations.loadingPollVotesError), + ), + ), + ); + }, + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.list( + onTap: controller.retry, + error: Text(context.translations.loadingPollVotesError), + ), + loadMoreIndicatorBuilder: (context) => const Center( + child: Padding( + padding: EdgeInsets.all(16), + child: StreamScrollViewLoadMoreIndicator(), + ), + ), + loadingBuilder: (context) => + loadingBuilder?.call(context) ?? + const Center( + child: StreamScrollViewLoadingWidget(), ), - loadMoreIndicatorBuilder: (context) => const Center( - child: Padding( - padding: EdgeInsets.all(16), - child: StreamScrollViewLoadMoreIndicator(), + errorBuilder: (context, error) => + errorBuilder?.call(context, error) ?? + Center( + child: StreamScrollViewErrorWidget( + errorTitle: Text(context.translations.loadingPollVotesError), + onRetryPressed: controller.refresh, ), ), - loadingBuilder: (context) => - loadingBuilder?.call(context) ?? - const Center( - child: StreamScrollViewLoadingWidget(), - ), - errorBuilder: (context, error) => - errorBuilder?.call(context, error) ?? - Center( - child: StreamScrollViewErrorWidget( - errorTitle: Text(context.translations.loadingPollVotesError), - onRetryPressed: controller.refresh, - ), - ), - ); + ); } diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_error_widget.dart b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_error_widget.dart index 3dafd08931..94801f87c0 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_error_widget.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_error_widget.dart @@ -52,7 +52,8 @@ class StreamScrollViewErrorWidget extends StatelessWidget { final errorIcon = AnimatedSwitcher( duration: kThemeChangeDuration, - child: this.errorIcon ?? + child: + this.errorIcon ?? Icon( Icons.error_outline_rounded, size: 148, @@ -67,7 +68,8 @@ class StreamScrollViewErrorWidget extends StatelessWidget { ); final retryButtonText = AnimatedDefaultTextStyle( - style: errorTitleStyle ?? + style: + errorTitleStyle ?? chatThemeData.textTheme.headline.copyWith( color: Colors.white, ), diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_indexed_widget_builder.dart b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_indexed_widget_builder.dart index 0305cd1f20..9d669f04d0 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_indexed_widget_builder.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_indexed_widget_builder.dart @@ -5,11 +5,10 @@ import 'package:flutter/material.dart'; /// /// Used by [StreamChannelListView], [StreamMessageSearchListView] /// and [StreamUserListView]. -typedef StreamScrollViewIndexedWidgetBuilder - = Widget Function( - BuildContext context, - List items, - int index, - WidgetType defaultWidget, -); +typedef StreamScrollViewIndexedWidgetBuilder = + Widget Function( + BuildContext context, + List items, + int index, + WidgetType defaultWidget, + ); diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_error.dart b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_error.dart index 0b32058559..fc97e8f5d0 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_error.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_error.dart @@ -74,14 +74,16 @@ class StreamScrollViewLoadMoreError extends StatelessWidget { final errorIcon = AnimatedSwitcher( duration: kThemeChangeDuration, - child: this.errorIcon ?? + child: + this.errorIcon ?? const StreamSvgIcon( color: Colors.white, icon: StreamSvgIcons.retry, ), ); - final backgroundColor = this.backgroundColor ?? + final backgroundColor = + this.backgroundColor ?? // ignore: deprecated_member_use theme.colorTheme.textLowEmphasis.withOpacity(0.9); diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_indicator.dart b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_indicator.dart index 7258522590..ab32318d69 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_indicator.dart @@ -18,8 +18,8 @@ class StreamScrollViewLoadMoreIndicator extends StatelessWidget { @override Widget build(BuildContext context) => SizedBox( - height: height, - width: width, - child: const CircularProgressIndicator.adaptive(), - ); + height: height, + width: width, + child: const CircularProgressIndicator.adaptive(), + ); } diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_loading_widget.dart b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_loading_widget.dart index 958eb80dc4..b18cab4d55 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_loading_widget.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_loading_widget.dart @@ -17,8 +17,8 @@ class StreamScrollViewLoadingWidget extends StatelessWidget { @override Widget build(BuildContext context) => SizedBox( - height: height, - width: width, - child: const CircularProgressIndicator.adaptive(), - ); + height: height, + width: width, + child: const CircularProgressIndicator.adaptive(), + ); } diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart index 0617a81305..2b089445f0 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart @@ -38,9 +38,7 @@ class StreamThreadListTile extends StatelessWidget { final theme = StreamThreadListTileTheme.of(context); final language = currentUser?.language; - final unreadMessageCount = thread.read - ?.firstWhereOrNull((read) => read.user.id == currentUser?.id) - ?.unreadMessages; + final unreadMessageCount = thread.read?.firstWhereOrNull((read) => read.user.id == currentUser?.id)?.unreadMessages; return Material( color: theme.backgroundColor, @@ -68,8 +66,7 @@ class StreamThreadListTile extends StatelessWidget { parentMessage: parentMessage, ), ), - if (unreadMessageCount case final count? when count > 0) - ThreadUnreadCount(unreadCount: count), + if (unreadMessageCount case final count? when count > 0) ThreadUnreadCount(unreadCount: count), ], ), if (thread.latestReplies.lastOrNull case final latestReply?) diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart index b8b2d293e3..c30204ea54 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart @@ -11,13 +11,11 @@ Widget defaultThreadListViewSeparatorBuilder( BuildContext context, List threads, int index, -) => - const StreamThreadListSeparator(); +) => const StreamThreadListSeparator(); /// Signature for the item builder that creates the children of the /// [StreamThreadListView]. -typedef StreamThreadListViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamThreadListViewIndexedWidgetBuilder = StreamScrollViewIndexedWidgetBuilder; /// {@template streamThreadListView} /// A [ListView] that shows a list of [Thread]'s. It uses a @@ -331,8 +329,7 @@ class StreamThreadListView extends StatelessWidget { ), ); }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.list( + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.list( onTap: controller.retry, error: Text(context.translations.loadingMessagesError), ), diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_tile.dart index 4d512f05ea..ce1e3a1afd 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_tile.dart @@ -45,19 +45,19 @@ class StreamUserGridTile extends StatelessWidget { Widget? footer, GestureTapCallback? onTap, GestureLongPressCallback? onLongPress, - }) => - StreamUserGridTile( - key: key ?? this.key, - user: user ?? this.user, - footer: footer ?? this.footer, - onTap: onTap ?? this.onTap, - onLongPress: onLongPress ?? this.onLongPress, - child: child ?? this.child, - ); + }) => StreamUserGridTile( + key: key ?? this.key, + user: user ?? this.user, + footer: footer ?? this.footer, + onTap: onTap ?? this.onTap, + onLongPress: onLongPress ?? this.onLongPress, + child: child ?? this.child, + ); @override Widget build(BuildContext context) { - final child = this.child ?? + final child = + this.child ?? StreamUserAvatar( user: user, borderRadius: BorderRadius.circular(32), @@ -71,7 +71,8 @@ class StreamUserGridTile extends StatelessWidget { ), ); - final footer = this.footer ?? + final footer = + this.footer ?? Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Text( diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_view.dart index 5aac833955..cb88b18703 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_view.dart @@ -7,13 +7,11 @@ import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_loading_w import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Default grid delegate for [StreamUserGridView]. -const defaultUserGridViewDelegate = - SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4); +const defaultUserGridViewDelegate = SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4); /// Signature for the item builder that creates the children of the /// [StreamUserGridView]. -typedef StreamUserGridViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamUserGridViewIndexedWidgetBuilder = StreamScrollViewIndexedWidgetBuilder; /// A [GridView] that shows a grid of [User]s, /// it uses [StreamUserGridTile] as a default item. @@ -362,8 +360,7 @@ class StreamUserGridView extends StatelessWidget { ), ); }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.grid( + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.grid( onTap: controller.retry, error: Text( context.translations.loadingUsersError, diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_tile.dart index 32129c0110..cf094ae354 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_tile.dart @@ -105,27 +105,27 @@ class StreamUserListTile extends StatelessWidget { Color? tileColor, VisualDensity? visualDensity, EdgeInsetsGeometry? contentPadding, - }) => - StreamUserListTile( - key: key ?? this.key, - user: user ?? this.user, - leading: leading ?? this.leading, - title: title ?? this.title, - subtitle: subtitle ?? this.subtitle, - selectedWidget: selectedWidget ?? this.selectedWidget, - selected: selected ?? this.selected, - onTap: onTap ?? this.onTap, - onLongPress: onLongPress ?? this.onLongPress, - tileColor: tileColor ?? this.tileColor, - visualDensity: visualDensity ?? this.visualDensity, - contentPadding: contentPadding ?? this.contentPadding, - ); + }) => StreamUserListTile( + key: key ?? this.key, + user: user ?? this.user, + leading: leading ?? this.leading, + title: title ?? this.title, + subtitle: subtitle ?? this.subtitle, + selectedWidget: selectedWidget ?? this.selectedWidget, + selected: selected ?? this.selected, + onTap: onTap ?? this.onTap, + onLongPress: onLongPress ?? this.onLongPress, + tileColor: tileColor ?? this.tileColor, + visualDensity: visualDensity ?? this.visualDensity, + contentPadding: contentPadding ?? this.contentPadding, + ); @override Widget build(BuildContext context) { final chatThemeData = StreamChatTheme.of(context); - final leading = this.leading ?? + final leading = + this.leading ?? StreamUserAvatar( user: user, constraints: const BoxConstraints.tightFor( @@ -134,18 +134,21 @@ class StreamUserListTile extends StatelessWidget { ), ); - final title = this.title ?? + final title = + this.title ?? Text( user.name, style: chatThemeData.textTheme.bodyBold, ); - final subtitle = this.subtitle ?? + final subtitle = + this.subtitle ?? UserLastActive( user: user, ); - final selectedWidget = this.selectedWidget ?? + final selectedWidget = + this.selectedWidget ?? StreamSvgIcon( icon: StreamSvgIcons.checkSend, color: chatThemeData.colorTheme.accentPrimary, @@ -184,7 +187,7 @@ class UserLastActive extends StatelessWidget { user.online ? context.translations.userOnlineText : '${context.translations.userLastOnlineText} ' - '${Jiffy.parseFromDateTime(lastActive).fromNow()}', + '${Jiffy.parseFromDateTime(lastActive).fromNow()}', style: chatTheme.textTheme.footnote.copyWith( // ignore: deprecated_member_use color: chatTheme.colorTheme.textHighEmphasis.withOpacity(0.5), diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_view.dart index df2530aad0..13a3a5203a 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_view.dart @@ -11,13 +11,11 @@ Widget defaultUserListViewSeparatorBuilder( BuildContext context, List users, int index, -) => - const StreamUserListSeparator(); +) => const StreamUserListSeparator(); /// Signature for the item builder that creates the children of the /// [StreamUserListView]. -typedef StreamUserListViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamUserListViewIndexedWidgetBuilder = StreamScrollViewIndexedWidgetBuilder; /// A [ListView] that shows a list of [User]s, /// it uses [StreamUserListTile] as a default item. @@ -278,88 +276,87 @@ class StreamUserListView extends StatelessWidget { @override Widget build(BuildContext context) => PagedValueListView( - scrollDirection: scrollDirection, - padding: padding, - physics: physics, - reverse: reverse, - controller: controller, - scrollController: scrollController, - primary: primary, - shrinkWrap: shrinkWrap, - addAutomaticKeepAlives: addAutomaticKeepAlives, - addRepaintBoundaries: addRepaintBoundaries, - addSemanticIndexes: addSemanticIndexes, - keyboardDismissBehavior: keyboardDismissBehavior, - restorationId: restorationId, - dragStartBehavior: dragStartBehavior, - cacheExtent: cacheExtent, - clipBehavior: clipBehavior, - loadMoreTriggerIndex: loadMoreTriggerIndex, - separatorBuilder: separatorBuilder, - itemBuilder: (context, users, index) { - final user = users[index]; - final onTap = onUserTap; - final onLongPress = onUserLongPress; - - final streamUserListTile = StreamUserListTile( - user: user, - onTap: onTap == null ? null : () => onTap(user), - onLongPress: onLongPress == null ? null : () => onLongPress(user), - ); + scrollDirection: scrollDirection, + padding: padding, + physics: physics, + reverse: reverse, + controller: controller, + scrollController: scrollController, + primary: primary, + shrinkWrap: shrinkWrap, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + keyboardDismissBehavior: keyboardDismissBehavior, + restorationId: restorationId, + dragStartBehavior: dragStartBehavior, + cacheExtent: cacheExtent, + clipBehavior: clipBehavior, + loadMoreTriggerIndex: loadMoreTriggerIndex, + separatorBuilder: separatorBuilder, + itemBuilder: (context, users, index) { + final user = users[index]; + final onTap = onUserTap; + final onLongPress = onUserLongPress; + + final streamUserListTile = StreamUserListTile( + user: user, + onTap: onTap == null ? null : () => onTap(user), + onLongPress: onLongPress == null ? null : () => onLongPress(user), + ); - return itemBuilder?.call( - context, - users, - index, - streamUserListTile, - ) ?? - streamUserListTile; - }, - emptyBuilder: (context) { - final chatThemeData = StreamChatTheme.of(context); - return emptyBuilder?.call(context) ?? - Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( - size: 148, - icon: StreamSvgIcons.user, - color: chatThemeData.colorTheme.disabled, - ), - emptyTitle: Text( - context.translations.noUsersLabel, - style: chatThemeData.textTheme.headline, - ), - ), + return itemBuilder?.call( + context, + users, + index, + streamUserListTile, + ) ?? + streamUserListTile; + }, + emptyBuilder: (context) { + final chatThemeData = StreamChatTheme.of(context); + return emptyBuilder?.call(context) ?? + Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: StreamScrollViewEmptyWidget( + emptyIcon: StreamSvgIcon( + size: 148, + icon: StreamSvgIcons.user, + color: chatThemeData.colorTheme.disabled, + ), + emptyTitle: Text( + context.translations.noUsersLabel, + style: chatThemeData.textTheme.headline, ), - ); - }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.list( - onTap: controller.retry, - error: Text(context.translations.loadingUsersError), + ), + ), + ); + }, + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.list( + onTap: controller.retry, + error: Text(context.translations.loadingUsersError), + ), + loadMoreIndicatorBuilder: (context) => const Center( + child: Padding( + padding: EdgeInsets.all(16), + child: StreamScrollViewLoadMoreIndicator(), + ), + ), + loadingBuilder: (context) => + loadingBuilder?.call(context) ?? + const Center( + child: StreamScrollViewLoadingWidget(), ), - loadMoreIndicatorBuilder: (context) => const Center( - child: Padding( - padding: EdgeInsets.all(16), - child: StreamScrollViewLoadMoreIndicator(), + errorBuilder: (context, error) => + errorBuilder?.call(context, error) ?? + Center( + child: StreamScrollViewErrorWidget( + errorTitle: Text(context.translations.loadingUsersError), + onRetryPressed: controller.refresh, ), ), - loadingBuilder: (context) => - loadingBuilder?.call(context) ?? - const Center( - child: StreamScrollViewLoadingWidget(), - ), - errorBuilder: (context, error) => - errorBuilder?.call(context, error) ?? - Center( - child: StreamScrollViewErrorWidget( - errorTitle: Text(context.translations.loadingUsersError), - onRetryPressed: controller.refresh, - ), - ), - ); + ); } /// A widget that is used to display a separator between diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index 33aef8d964..b24fdbdca2 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -141,8 +141,7 @@ class StreamChatState extends State { StreamChatClient get client => widget.client; /// Gets configuration options from widget - StreamChatConfigurationData get streamChatConfigData => - widget.streamChatConfigData ?? StreamChatConfigurationData(); + StreamChatConfigurationData get streamChatConfigData => widget.streamChatConfigData ?? StreamChatConfigurationData(); @override void initState() { @@ -208,8 +207,7 @@ class StreamChatState extends State { @override void didChangeDependencies() { - final currentLocale = - Localizations.localeOf(context).toString().toLowerCase(); + final currentLocale = Localizations.localeOf(context).toString().toLowerCase(); final availableLocales = Jiffy.getSupportedLocales(); if (availableLocales.contains(currentLocale)) { Jiffy.setLocale(currentLocale); diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart b/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart index 946f41f70e..f25fbaede7 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart @@ -18,8 +18,7 @@ class StreamChatConfiguration extends InheritedWidget { final StreamChatConfigurationData data; @override - bool updateShouldNotify(StreamChatConfiguration oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamChatConfiguration oldWidget) => data != oldWidget.data; /// Finds the [StreamChatConfigurationData] from the closest /// [StreamChatConfiguration] ancestor that encloses the given context. @@ -81,8 +80,7 @@ class StreamChatConfiguration extends InheritedWidget { /// See also: /// * [of], which throws if no [StreamChatConfiguration] is found. static StreamChatConfigurationData? maybeOf(BuildContext context) { - final streamChatConfiguration = - context.dependOnInheritedWidgetOfExactType(); + final streamChatConfiguration = context.dependOnInheritedWidgetOfExactType(); return streamChatConfiguration?.data; } } @@ -175,8 +173,7 @@ class StreamChatConfigurationData { reactionIcons: reactionIcons ?? StreamReactionIcon.defaultReactions, enforceUniqueReactions: enforceUniqueReactions ?? true, draftMessagesEnabled: draftMessagesEnabled, - messagePreviewFormatter: - messagePreviewFormatter ?? MessagePreviewFormatter(), + messagePreviewFormatter: messagePreviewFormatter ?? MessagePreviewFormatter(), ); } @@ -206,11 +203,9 @@ class StreamChatConfigurationData { defaultUserImage: defaultUserImage ?? this.defaultUserImage, placeholderUserImage: placeholderUserImage ?? this.placeholderUserImage, loadingIndicator: loadingIndicator ?? this.loadingIndicator, - enforceUniqueReactions: - enforceUniqueReactions ?? this.enforceUniqueReactions, + enforceUniqueReactions: enforceUniqueReactions ?? this.enforceUniqueReactions, draftMessagesEnabled: draftMessagesEnabled ?? this.draftMessagesEnabled, - messagePreviewFormatter: - messagePreviewFormatter ?? this.messagePreviewFormatter, + messagePreviewFormatter: messagePreviewFormatter ?? this.messagePreviewFormatter, ); } diff --git a/packages/stream_chat_flutter/lib/src/theme/audio_waveform_slider_theme.dart b/packages/stream_chat_flutter/lib/src/theme/audio_waveform_slider_theme.dart index 0b2b9b5424..3e2b1062cc 100644 --- a/packages/stream_chat_flutter/lib/src/theme/audio_waveform_slider_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/audio_waveform_slider_theme.dart @@ -36,19 +36,15 @@ class StreamAudioWaveformSliderTheme extends InheritedTheme { /// StreamAudioWaveformSliderTheme.of(context); /// ``` static StreamAudioWaveformSliderThemeData of(BuildContext context) { - final audioWaveformSliderTheme = context - .dependOnInheritedWidgetOfExactType(); - return audioWaveformSliderTheme?.data ?? - StreamChatTheme.of(context).audioWaveformSliderTheme; + final audioWaveformSliderTheme = context.dependOnInheritedWidgetOfExactType(); + return audioWaveformSliderTheme?.data ?? StreamChatTheme.of(context).audioWaveformSliderTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamAudioWaveformSliderTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamAudioWaveformSliderTheme(data: data, child: child); @override - bool updateShouldNotify(StreamAudioWaveformSliderTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamAudioWaveformSliderTheme oldWidget) => data != oldWidget.data; } /// {@template streamAudioWaveformSliderThemeData} @@ -105,13 +101,11 @@ class StreamAudioWaveformSliderThemeData with Diagnosticable { StreamAudioWaveformSliderThemeData a, StreamAudioWaveformSliderThemeData b, double t, - ) => - StreamAudioWaveformSliderThemeData( - audioWaveformTheme: StreamAudioWaveformThemeData.lerp( - a.audioWaveformTheme!, b.audioWaveformTheme!, t), - thumbColor: Color.lerp(a.thumbColor, b.thumbColor, t), - thumbBorderColor: Color.lerp(a.thumbBorderColor, b.thumbBorderColor, t), - ); + ) => StreamAudioWaveformSliderThemeData( + audioWaveformTheme: StreamAudioWaveformThemeData.lerp(a.audioWaveformTheme!, b.audioWaveformTheme!, t), + thumbColor: Color.lerp(a.thumbColor, b.thumbColor, t), + thumbBorderColor: Color.lerp(a.thumbBorderColor, b.thumbBorderColor, t), + ); @override bool operator ==(Object other) => @@ -122,17 +116,13 @@ class StreamAudioWaveformSliderThemeData with Diagnosticable { other.thumbBorderColor == thumbBorderColor; @override - int get hashCode => - audioWaveformTheme.hashCode ^ - thumbColor.hashCode ^ - thumbBorderColor.hashCode; + int get hashCode => audioWaveformTheme.hashCode ^ thumbColor.hashCode ^ thumbBorderColor.hashCode; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties - ..add(DiagnosticsProperty( - 'audioWaveformTheme', audioWaveformTheme)) + ..add(DiagnosticsProperty('audioWaveformTheme', audioWaveformTheme)) ..add(ColorProperty('thumbColor', thumbColor)) ..add(ColorProperty('thumbBorderColor', thumbBorderColor)); } diff --git a/packages/stream_chat_flutter/lib/src/theme/audio_waveform_theme.dart b/packages/stream_chat_flutter/lib/src/theme/audio_waveform_theme.dart index dde8fc78df..d7f81148df 100644 --- a/packages/stream_chat_flutter/lib/src/theme/audio_waveform_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/audio_waveform_theme.dart @@ -36,19 +36,15 @@ class StreamAudioWaveformTheme extends InheritedTheme { /// StreamAudioWaveformTheme theme = StreamAudioWaveformTheme.of(context); /// ``` static StreamAudioWaveformThemeData of(BuildContext context) { - final audioWaveformTheme = - context.dependOnInheritedWidgetOfExactType(); - return audioWaveformTheme?.data ?? - StreamChatTheme.of(context).audioWaveformTheme; + final audioWaveformTheme = context.dependOnInheritedWidgetOfExactType(); + return audioWaveformTheme?.data ?? StreamChatTheme.of(context).audioWaveformTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamAudioWaveformTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamAudioWaveformTheme(data: data, child: child); @override - bool updateShouldNotify(StreamAudioWaveformTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamAudioWaveformTheme oldWidget) => data != oldWidget.data; } /// {@template streamVoiceRecordingAttachmentThemeData} @@ -119,14 +115,13 @@ class StreamAudioWaveformThemeData with Diagnosticable { StreamAudioWaveformThemeData a, StreamAudioWaveformThemeData b, double t, - ) => - StreamAudioWaveformThemeData( - color: Color.lerp(a.color, b.color, t), - progressColor: Color.lerp(a.progressColor, b.progressColor, t), - minBarHeight: lerpDouble(a.minBarHeight, b.minBarHeight, t), - spacingRatio: lerpDouble(a.spacingRatio, b.spacingRatio, t), - heightScale: lerpDouble(a.heightScale, b.heightScale, t), - ); + ) => StreamAudioWaveformThemeData( + color: Color.lerp(a.color, b.color, t), + progressColor: Color.lerp(a.progressColor, b.progressColor, t), + minBarHeight: lerpDouble(a.minBarHeight, b.minBarHeight, t), + spacingRatio: lerpDouble(a.spacingRatio, b.spacingRatio, t), + heightScale: lerpDouble(a.heightScale, b.heightScale, t), + ); @override bool operator ==(Object other) => @@ -140,11 +135,7 @@ class StreamAudioWaveformThemeData with Diagnosticable { @override int get hashCode => - color.hashCode ^ - progressColor.hashCode ^ - minBarHeight.hashCode ^ - spacingRatio.hashCode ^ - heightScale.hashCode; + color.hashCode ^ progressColor.hashCode ^ minBarHeight.hashCode ^ spacingRatio.hashCode ^ heightScale.hashCode; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { diff --git a/packages/stream_chat_flutter/lib/src/theme/avatar_theme.dart b/packages/stream_chat_flutter/lib/src/theme/avatar_theme.dart index dbc36da931..e0e6b24e6a 100644 --- a/packages/stream_chat_flutter/lib/src/theme/avatar_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/avatar_theme.dart @@ -10,8 +10,8 @@ class StreamAvatarThemeData with Diagnosticable { const StreamAvatarThemeData({ BoxConstraints? constraints, BorderRadius? borderRadius, - }) : _constraints = constraints, - _borderRadius = borderRadius; + }) : _constraints = constraints, + _borderRadius = borderRadius; final BoxConstraints? _constraints; final BorderRadius? _borderRadius; diff --git a/packages/stream_chat_flutter/lib/src/theme/channel_header_theme.dart b/packages/stream_chat_flutter/lib/src/theme/channel_header_theme.dart index 2df24f685e..d9ea4b009e 100644 --- a/packages/stream_chat_flutter/lib/src/theme/channel_header_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/channel_header_theme.dart @@ -35,19 +35,15 @@ class StreamChannelHeaderTheme extends InheritedTheme { /// final theme = ChannelHeaderTheme.of(context); /// ``` static StreamChannelHeaderThemeData of(BuildContext context) { - final channelHeaderTheme = - context.dependOnInheritedWidgetOfExactType(); - return channelHeaderTheme?.data ?? - StreamChatTheme.of(context).channelHeaderTheme; + final channelHeaderTheme = context.dependOnInheritedWidgetOfExactType(); + return channelHeaderTheme?.data ?? StreamChatTheme.of(context).channelHeaderTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamChannelHeaderTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamChannelHeaderTheme(data: data, child: child); @override - bool updateShouldNotify(StreamChannelHeaderTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamChannelHeaderTheme oldWidget) => data != oldWidget.data; } /// {@template channel_header_theme_data} @@ -108,8 +104,7 @@ class StreamChannelHeaderThemeData with Diagnosticable { return StreamChannelHeaderThemeData( titleStyle: TextStyle.lerp(a.titleStyle, b.titleStyle, t), subtitleStyle: TextStyle.lerp(a.subtitleStyle, b.subtitleStyle, t), - avatarTheme: - const StreamAvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t), + avatarTheme: const StreamAvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t), color: Color.lerp(a.color, b.color, t), ); } @@ -119,8 +114,7 @@ class StreamChannelHeaderThemeData with Diagnosticable { if (other == null) return this; return copyWith( titleStyle: titleStyle?.merge(other.titleStyle) ?? other.titleStyle, - subtitleStyle: - subtitleStyle?.merge(other.subtitleStyle) ?? other.subtitleStyle, + subtitleStyle: subtitleStyle?.merge(other.subtitleStyle) ?? other.subtitleStyle, avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme, color: other.color, ); @@ -137,11 +131,7 @@ class StreamChannelHeaderThemeData with Diagnosticable { color == other.color; @override - int get hashCode => - titleStyle.hashCode ^ - subtitleStyle.hashCode ^ - avatarTheme.hashCode ^ - color.hashCode; + int get hashCode => titleStyle.hashCode ^ subtitleStyle.hashCode ^ avatarTheme.hashCode ^ color.hashCode; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { diff --git a/packages/stream_chat_flutter/lib/src/theme/channel_list_header_theme.dart b/packages/stream_chat_flutter/lib/src/theme/channel_list_header_theme.dart index 7452065df7..6335400790 100644 --- a/packages/stream_chat_flutter/lib/src/theme/channel_list_header_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/channel_list_header_theme.dart @@ -35,19 +35,15 @@ class StreamChannelListHeaderTheme extends InheritedTheme { /// final theme = ChannelListHeaderTheme.of(context); /// ``` static StreamChannelListHeaderThemeData of(BuildContext context) { - final channelListHeaderTheme = context - .dependOnInheritedWidgetOfExactType(); - return channelListHeaderTheme?.data ?? - StreamChatTheme.of(context).channelListHeaderTheme; + final channelListHeaderTheme = context.dependOnInheritedWidgetOfExactType(); + return channelListHeaderTheme?.data ?? StreamChatTheme.of(context).channelListHeaderTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamChannelListHeaderTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamChannelListHeaderTheme(data: data, child: child); @override - bool updateShouldNotify(StreamChannelListHeaderTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamChannelListHeaderTheme oldWidget) => data != oldWidget.data; } /// {@template channel_list_header_theme_data} @@ -92,8 +88,7 @@ class StreamChannelListHeaderThemeData with Diagnosticable { double t, ) { return StreamChannelListHeaderThemeData( - avatarTheme: - const StreamAvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t), + avatarTheme: const StreamAvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t), color: Color.lerp(a.color, b.color, t), titleStyle: TextStyle.lerp(a.titleStyle, b.titleStyle, t), ); @@ -121,8 +116,7 @@ class StreamChannelListHeaderThemeData with Diagnosticable { color == other.color; @override - int get hashCode => - titleStyle.hashCode ^ avatarTheme.hashCode ^ color.hashCode; + int get hashCode => titleStyle.hashCode ^ avatarTheme.hashCode ^ color.hashCode; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { diff --git a/packages/stream_chat_flutter/lib/src/theme/channel_preview_theme.dart b/packages/stream_chat_flutter/lib/src/theme/channel_preview_theme.dart index 3b9a39f658..89019f322a 100644 --- a/packages/stream_chat_flutter/lib/src/theme/channel_preview_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/channel_preview_theme.dart @@ -35,19 +35,15 @@ class StreamChannelPreviewTheme extends InheritedTheme { /// final theme = ChannelPreviewTheme.of(context); /// ``` static StreamChannelPreviewThemeData of(BuildContext context) { - final channelPreviewTheme = - context.dependOnInheritedWidgetOfExactType(); - return channelPreviewTheme?.data ?? - StreamChatTheme.of(context).channelPreviewTheme; + final channelPreviewTheme = context.dependOnInheritedWidgetOfExactType(); + return channelPreviewTheme?.data ?? StreamChatTheme.of(context).channelPreviewTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamChannelPreviewTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamChannelPreviewTheme(data: data, child: child); @override - bool updateShouldNotify(StreamChannelPreviewTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamChannelPreviewTheme oldWidget) => data != oldWidget.data; } /// {@template channelPreviewThemeData} @@ -124,8 +120,7 @@ class StreamChannelPreviewThemeData with Diagnosticable { avatarTheme: avatarTheme ?? this.avatarTheme, unreadCounterColor: unreadCounterColor ?? this.unreadCounterColor, indicatorIconSize: indicatorIconSize ?? this.indicatorIconSize, - lastMessageAtFormatter: - lastMessageAtFormatter ?? this.lastMessageAtFormatter, + lastMessageAtFormatter: lastMessageAtFormatter ?? this.lastMessageAtFormatter, ); } @@ -136,17 +131,13 @@ class StreamChannelPreviewThemeData with Diagnosticable { double t, ) { return StreamChannelPreviewThemeData( - avatarTheme: - const StreamAvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t), + avatarTheme: const StreamAvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t), indicatorIconSize: a.indicatorIconSize, - lastMessageAtStyle: - TextStyle.lerp(a.lastMessageAtStyle, b.lastMessageAtStyle, t), + lastMessageAtStyle: TextStyle.lerp(a.lastMessageAtStyle, b.lastMessageAtStyle, t), subtitleStyle: TextStyle.lerp(a.subtitleStyle, b.subtitleStyle, t), titleStyle: TextStyle.lerp(a.titleStyle, b.titleStyle, t), - unreadCounterColor: - Color.lerp(a.unreadCounterColor, b.unreadCounterColor, t), - lastMessageAtFormatter: - t < 0.5 ? a.lastMessageAtFormatter : b.lastMessageAtFormatter, + unreadCounterColor: Color.lerp(a.unreadCounterColor, b.unreadCounterColor, t), + lastMessageAtFormatter: t < 0.5 ? a.lastMessageAtFormatter : b.lastMessageAtFormatter, ); } @@ -155,14 +146,11 @@ class StreamChannelPreviewThemeData with Diagnosticable { if (other == null) return this; return copyWith( titleStyle: titleStyle?.merge(other.titleStyle) ?? other.titleStyle, - subtitleStyle: - subtitleStyle?.merge(other.subtitleStyle) ?? other.subtitleStyle, - lastMessageAtStyle: lastMessageAtStyle?.merge(other.lastMessageAtStyle) ?? - other.lastMessageAtStyle, + subtitleStyle: subtitleStyle?.merge(other.subtitleStyle) ?? other.subtitleStyle, + lastMessageAtStyle: lastMessageAtStyle?.merge(other.lastMessageAtStyle) ?? other.lastMessageAtStyle, avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme, unreadCounterColor: other.unreadCounterColor, - lastMessageAtFormatter: - other.lastMessageAtFormatter ?? lastMessageAtFormatter, + lastMessageAtFormatter: other.lastMessageAtFormatter ?? lastMessageAtFormatter, ); } @@ -198,7 +186,6 @@ class StreamChannelPreviewThemeData with Diagnosticable { ..add(DiagnosticsProperty('lastMessageAtStyle', lastMessageAtStyle)) ..add(DiagnosticsProperty('avatarTheme', avatarTheme)) ..add(ColorProperty('unreadCounterColor', unreadCounterColor)) - ..add(DiagnosticsProperty( - 'lastMessageAtFormatter', lastMessageAtFormatter)); + ..add(DiagnosticsProperty('lastMessageAtFormatter', lastMessageAtFormatter)); } } diff --git a/packages/stream_chat_flutter/lib/src/theme/draft_list_tile_theme.dart b/packages/stream_chat_flutter/lib/src/theme/draft_list_tile_theme.dart index 20b8973657..dce3292e6e 100644 --- a/packages/stream_chat_flutter/lib/src/theme/draft_list_tile_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/draft_list_tile_theme.dart @@ -29,19 +29,15 @@ class StreamDraftListTileTheme extends InheritedTheme { /// If there is no enclosing [StreamDraftListTileTheme] widget, then /// [StreamChatThemeData.draftListTileTheme] is used. static StreamDraftListTileThemeData of(BuildContext context) { - final draftListTileTheme = - context.dependOnInheritedWidgetOfExactType(); - return draftListTileTheme?.data ?? - StreamChatTheme.of(context).draftListTileTheme; + final draftListTileTheme = context.dependOnInheritedWidgetOfExactType(); + return draftListTileTheme?.data ?? StreamChatTheme.of(context).draftListTileTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamDraftListTileTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamDraftListTileTheme(data: data, child: child); @override - bool updateShouldNotify(StreamDraftListTileTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamDraftListTileTheme oldWidget) => data != oldWidget.data; } /// {@template streamDraftListTileThemeData} @@ -101,17 +97,14 @@ class StreamDraftListTileThemeData with Diagnosticable { TextStyle? draftTimestampStyle, DateFormatter? draftTimestampFormatter, Color? draftIconColor, - }) => - StreamDraftListTileThemeData( - padding: padding ?? this.padding, - backgroundColor: backgroundColor ?? this.backgroundColor, - draftChannelNameStyle: - draftChannelNameStyle ?? this.draftChannelNameStyle, - draftMessageStyle: draftMessageStyle ?? this.draftMessageStyle, - draftTimestampStyle: draftTimestampStyle ?? this.draftTimestampStyle, - draftTimestampFormatter: - draftTimestampFormatter ?? this.draftTimestampFormatter, - ); + }) => StreamDraftListTileThemeData( + padding: padding ?? this.padding, + backgroundColor: backgroundColor ?? this.backgroundColor, + draftChannelNameStyle: draftChannelNameStyle ?? this.draftChannelNameStyle, + draftMessageStyle: draftMessageStyle ?? this.draftMessageStyle, + draftTimestampStyle: draftTimestampStyle ?? this.draftTimestampStyle, + draftTimestampFormatter: draftTimestampFormatter ?? this.draftTimestampFormatter, + ); /// Merges this [StreamDraftListTileThemeData] with the [other]. StreamDraftListTileThemeData merge( @@ -133,28 +126,26 @@ class StreamDraftListTileThemeData with Diagnosticable { StreamDraftListTileThemeData? a, StreamDraftListTileThemeData? b, double t, - ) => - StreamDraftListTileThemeData( - padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t), - backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t), - draftChannelNameStyle: TextStyle.lerp( - a?.draftChannelNameStyle, - b?.draftChannelNameStyle, - t, - ), - draftMessageStyle: TextStyle.lerp( - a?.draftMessageStyle, - b?.draftMessageStyle, - t, - ), - draftTimestampStyle: TextStyle.lerp( - a?.draftTimestampStyle, - b?.draftTimestampStyle, - t, - ), - draftTimestampFormatter: - t < 0.5 ? a?.draftTimestampFormatter : b?.draftTimestampFormatter, - ); + ) => StreamDraftListTileThemeData( + padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t), + backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t), + draftChannelNameStyle: TextStyle.lerp( + a?.draftChannelNameStyle, + b?.draftChannelNameStyle, + t, + ), + draftMessageStyle: TextStyle.lerp( + a?.draftMessageStyle, + b?.draftMessageStyle, + t, + ), + draftTimestampStyle: TextStyle.lerp( + a?.draftTimestampStyle, + b?.draftTimestampStyle, + t, + ), + draftTimestampFormatter: t < 0.5 ? a?.draftTimestampFormatter : b?.draftTimestampFormatter, + ); @override bool operator ==(Object other) => diff --git a/packages/stream_chat_flutter/lib/src/theme/gallery_footer_theme.dart b/packages/stream_chat_flutter/lib/src/theme/gallery_footer_theme.dart index 84e1688a60..48733b8632 100644 --- a/packages/stream_chat_flutter/lib/src/theme/gallery_footer_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/gallery_footer_theme.dart @@ -33,19 +33,15 @@ class StreamGalleryFooterTheme extends InheritedTheme { /// ImageFooterTheme theme = ImageFooterTheme.of(context); /// ``` static StreamGalleryFooterThemeData of(BuildContext context) { - final imageFooterTheme = - context.dependOnInheritedWidgetOfExactType(); - return imageFooterTheme?.data ?? - StreamChatTheme.of(context).galleryFooterTheme; + final imageFooterTheme = context.dependOnInheritedWidgetOfExactType(); + return imageFooterTheme?.data ?? StreamChatTheme.of(context).galleryFooterTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamGalleryFooterTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamGalleryFooterTheme(data: data, child: child); @override - bool updateShouldNotify(StreamGalleryFooterTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamGalleryFooterTheme oldWidget) => data != oldWidget.data; } /// {@template galleryFooterThemeData} @@ -128,14 +124,10 @@ class StreamGalleryFooterThemeData with Diagnosticable { shareIconColor: shareIconColor ?? this.shareIconColor, titleTextStyle: titleTextStyle ?? this.titleTextStyle, gridIconButtonColor: gridIconButtonColor ?? this.gridIconButtonColor, - bottomSheetBarrierColor: - bottomSheetBarrierColor ?? this.bottomSheetBarrierColor, - bottomSheetBackgroundColor: - bottomSheetBackgroundColor ?? this.bottomSheetBackgroundColor, - bottomSheetPhotosTextStyle: - bottomSheetPhotosTextStyle ?? this.bottomSheetPhotosTextStyle, - bottomSheetCloseIconColor: - bottomSheetCloseIconColor ?? this.bottomSheetCloseIconColor, + bottomSheetBarrierColor: bottomSheetBarrierColor ?? this.bottomSheetBarrierColor, + bottomSheetBackgroundColor: bottomSheetBackgroundColor ?? this.bottomSheetBackgroundColor, + bottomSheetPhotosTextStyle: bottomSheetPhotosTextStyle ?? this.bottomSheetPhotosTextStyle, + bottomSheetCloseIconColor: bottomSheetCloseIconColor ?? this.bottomSheetCloseIconColor, ); } @@ -151,10 +143,8 @@ class StreamGalleryFooterThemeData with Diagnosticable { backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), shareIconColor: Color.lerp(a.shareIconColor, b.shareIconColor, t), titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t), - gridIconButtonColor: - Color.lerp(a.gridIconButtonColor, b.gridIconButtonColor, t), - bottomSheetBarrierColor: - Color.lerp(a.bottomSheetBarrierColor, b.bottomSheetBarrierColor, t), + gridIconButtonColor: Color.lerp(a.gridIconButtonColor, b.gridIconButtonColor, t), + bottomSheetBarrierColor: Color.lerp(a.bottomSheetBarrierColor, b.bottomSheetBarrierColor, t), bottomSheetBackgroundColor: Color.lerp( a.bottomSheetBackgroundColor, b.bottomSheetBackgroundColor, @@ -222,17 +212,23 @@ class StreamGalleryFooterThemeData with Diagnosticable { ..add(DiagnosticsProperty('titleTextStyle', titleTextStyle)) ..add(ColorProperty('gridIconButtonColor', gridIconButtonColor)) ..add(ColorProperty('bottomSheetBarrierColor', bottomSheetBarrierColor)) - ..add(ColorProperty( - 'bottomSheetBackgroundColor', - bottomSheetBackgroundColor, - )) - ..add(DiagnosticsProperty( - 'bottomSheetPhotosTextStyle', - bottomSheetPhotosTextStyle, - )) - ..add(ColorProperty( - 'bottomSheetCloseIconColor', - bottomSheetCloseIconColor, - )); + ..add( + ColorProperty( + 'bottomSheetBackgroundColor', + bottomSheetBackgroundColor, + ), + ) + ..add( + DiagnosticsProperty( + 'bottomSheetPhotosTextStyle', + bottomSheetPhotosTextStyle, + ), + ) + ..add( + ColorProperty( + 'bottomSheetCloseIconColor', + bottomSheetCloseIconColor, + ), + ); } } diff --git a/packages/stream_chat_flutter/lib/src/theme/gallery_header_theme.dart b/packages/stream_chat_flutter/lib/src/theme/gallery_header_theme.dart index 90977d4c9f..8add8e4775 100644 --- a/packages/stream_chat_flutter/lib/src/theme/gallery_header_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/gallery_header_theme.dart @@ -33,19 +33,15 @@ class StreamGalleryHeaderTheme extends InheritedTheme { /// ImageHeaderTheme theme = ImageHeaderTheme.of(context); /// ``` static StreamGalleryHeaderThemeData of(BuildContext context) { - final galleryHeaderTheme = - context.dependOnInheritedWidgetOfExactType(); - return galleryHeaderTheme?.data ?? - StreamChatTheme.of(context).galleryHeaderTheme; + final galleryHeaderTheme = context.dependOnInheritedWidgetOfExactType(); + return galleryHeaderTheme?.data ?? StreamChatTheme.of(context).galleryHeaderTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamGalleryHeaderTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamGalleryHeaderTheme(data: data, child: child); @override - bool updateShouldNotify(StreamGalleryHeaderTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamGalleryHeaderTheme oldWidget) => data != oldWidget.data; } /// {@template galleryHeaderThemeData} @@ -111,8 +107,7 @@ class StreamGalleryHeaderThemeData with Diagnosticable { iconMenuPointColor: iconMenuPointColor ?? this.iconMenuPointColor, titleTextStyle: titleTextStyle ?? this.titleTextStyle, subtitleTextStyle: subtitleTextStyle ?? this.subtitleTextStyle, - bottomSheetBarrierColor: - bottomSheetBarrierColor ?? this.bottomSheetBarrierColor, + bottomSheetBarrierColor: bottomSheetBarrierColor ?? this.bottomSheetBarrierColor, ); } @@ -127,13 +122,10 @@ class StreamGalleryHeaderThemeData with Diagnosticable { return StreamGalleryHeaderThemeData( closeButtonColor: Color.lerp(a.closeButtonColor, b.closeButtonColor, t), backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), - iconMenuPointColor: - Color.lerp(a.iconMenuPointColor, b.iconMenuPointColor, t), + iconMenuPointColor: Color.lerp(a.iconMenuPointColor, b.iconMenuPointColor, t), titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t), - subtitleTextStyle: - TextStyle.lerp(a.subtitleTextStyle, b.subtitleTextStyle, t), - bottomSheetBarrierColor: - Color.lerp(a.bottomSheetBarrierColor, b.bottomSheetBarrierColor, t), + subtitleTextStyle: TextStyle.lerp(a.subtitleTextStyle, b.subtitleTextStyle, t), + bottomSheetBarrierColor: Color.lerp(a.bottomSheetBarrierColor, b.bottomSheetBarrierColor, t), ); } diff --git a/packages/stream_chat_flutter/lib/src/theme/message_input_theme.dart b/packages/stream_chat_flutter/lib/src/theme/message_input_theme.dart index a97df00b1e..728ebd2cfb 100644 --- a/packages/stream_chat_flutter/lib/src/theme/message_input_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/message_input_theme.dart @@ -35,19 +35,15 @@ class StreamMessageInputTheme extends InheritedTheme { /// final theme = MessageInputTheme.of(context); /// ``` static StreamMessageInputThemeData of(BuildContext context) { - final messageInputTheme = - context.dependOnInheritedWidgetOfExactType(); - return messageInputTheme?.data ?? - StreamChatTheme.of(context).messageInputTheme; + final messageInputTheme = context.dependOnInheritedWidgetOfExactType(); + return messageInputTheme?.data ?? StreamChatTheme.of(context).messageInputTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamMessageInputTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamMessageInputTheme(data: data, child: child); @override - bool updateShouldNotify(StreamMessageInputTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamMessageInputTheme oldWidget) => data != oldWidget.data; } /// {@template messageInputThemeData} @@ -160,13 +156,11 @@ class StreamMessageInputThemeData with Diagnosticable { bool? useSystemAttachmentPicker, }) { return StreamMessageInputThemeData( - sendAnimationDuration: - sendAnimationDuration ?? this.sendAnimationDuration, + sendAnimationDuration: sendAnimationDuration ?? this.sendAnimationDuration, inputBackgroundColor: inputBackgroundColor ?? this.inputBackgroundColor, actionButtonColor: actionButtonColor ?? this.actionButtonColor, sendButtonColor: sendButtonColor ?? this.sendButtonColor, - actionButtonIdleColor: - actionButtonIdleColor ?? this.actionButtonIdleColor, + actionButtonIdleColor: actionButtonIdleColor ?? this.actionButtonIdleColor, linkHighlightColor: linkHighlightColor ?? this.linkHighlightColor, expandButtonColor: expandButtonColor ?? this.expandButtonColor, inputTextStyle: inputTextStyle ?? this.inputTextStyle, @@ -178,8 +172,7 @@ class StreamMessageInputThemeData with Diagnosticable { enableSafeArea: enableSafeArea ?? this.enableSafeArea, elevation: elevation ?? this.elevation, shadow: shadow ?? this.shadow, - useSystemAttachmentPicker: - useSystemAttachmentPicker ?? this.useSystemAttachmentPicker, + useSystemAttachmentPicker: useSystemAttachmentPicker ?? this.useSystemAttachmentPicker, ); } @@ -190,25 +183,17 @@ class StreamMessageInputThemeData with Diagnosticable { double t, ) { return StreamMessageInputThemeData( - actionButtonColor: - Color.lerp(a.actionButtonColor, b.actionButtonColor, t), - actionButtonIdleColor: - Color.lerp(a.actionButtonIdleColor, b.actionButtonIdleColor, t), - activeBorderGradient: - Gradient.lerp(a.activeBorderGradient, b.activeBorderGradient, t), + actionButtonColor: Color.lerp(a.actionButtonColor, b.actionButtonColor, t), + actionButtonIdleColor: Color.lerp(a.actionButtonIdleColor, b.actionButtonIdleColor, t), + activeBorderGradient: Gradient.lerp(a.activeBorderGradient, b.activeBorderGradient, t), borderRadius: BorderRadius.lerp(a.borderRadius, b.borderRadius, t), - expandButtonColor: - Color.lerp(a.expandButtonColor, b.expandButtonColor, t), - linkHighlightColor: - Color.lerp(a.linkHighlightColor, b.linkHighlightColor, t), - idleBorderGradient: - Gradient.lerp(a.idleBorderGradient, b.idleBorderGradient, t), - inputBackgroundColor: - Color.lerp(a.inputBackgroundColor, b.inputBackgroundColor, t), + expandButtonColor: Color.lerp(a.expandButtonColor, b.expandButtonColor, t), + linkHighlightColor: Color.lerp(a.linkHighlightColor, b.linkHighlightColor, t), + idleBorderGradient: Gradient.lerp(a.idleBorderGradient, b.idleBorderGradient, t), + inputBackgroundColor: Color.lerp(a.inputBackgroundColor, b.inputBackgroundColor, t), inputTextStyle: TextStyle.lerp(a.inputTextStyle, b.inputTextStyle, t), sendButtonColor: Color.lerp(a.sendButtonColor, b.sendButtonColor, t), - sendButtonIdleColor: - Color.lerp(a.sendButtonIdleColor, b.sendButtonIdleColor, t), + sendButtonIdleColor: Color.lerp(a.sendButtonIdleColor, b.sendButtonIdleColor, t), sendAnimationDuration: a.sendAnimationDuration, inputDecoration: a.inputDecoration, enableSafeArea: a.enableSafeArea, @@ -228,10 +213,8 @@ class StreamMessageInputThemeData with Diagnosticable { actionButtonIdleColor: other.actionButtonIdleColor, sendButtonColor: other.sendButtonColor, sendButtonIdleColor: other.sendButtonIdleColor, - inputTextStyle: - inputTextStyle?.merge(other.inputTextStyle) ?? other.inputTextStyle, - inputDecoration: inputDecoration?.merge(other.inputDecoration) ?? - other.inputDecoration, + inputTextStyle: inputTextStyle?.merge(other.inputTextStyle) ?? other.inputTextStyle, + inputDecoration: inputDecoration?.merge(other.inputDecoration) ?? other.inputDecoration, activeBorderGradient: other.activeBorderGradient, idleBorderGradient: other.idleBorderGradient, borderRadius: other.borderRadius, @@ -307,7 +290,6 @@ class StreamMessageInputThemeData with Diagnosticable { ..add(DiagnosticsProperty('elevation', elevation)) ..add(DiagnosticsProperty('shadow', shadow)) ..add(DiagnosticsProperty('enableSafeArea', enableSafeArea)) - ..add(DiagnosticsProperty( - 'useSystemAttachmentPicker', useSystemAttachmentPicker)); + ..add(DiagnosticsProperty('useSystemAttachmentPicker', useSystemAttachmentPicker)); } } diff --git a/packages/stream_chat_flutter/lib/src/theme/message_list_view_theme.dart b/packages/stream_chat_flutter/lib/src/theme/message_list_view_theme.dart index d21b3725e9..66eb1b9644 100644 --- a/packages/stream_chat_flutter/lib/src/theme/message_list_view_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/message_list_view_theme.dart @@ -33,19 +33,15 @@ class StreamMessageListViewTheme extends InheritedTheme { /// MessageListViewTheme theme = MessageListViewTheme.of(context); /// ``` static StreamMessageListViewThemeData of(BuildContext context) { - final messageListViewTheme = context - .dependOnInheritedWidgetOfExactType(); - return messageListViewTheme?.data ?? - StreamChatTheme.of(context).messageListViewTheme; + final messageListViewTheme = context.dependOnInheritedWidgetOfExactType(); + return messageListViewTheme?.data ?? StreamChatTheme.of(context).messageListViewTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamMessageListViewTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamMessageListViewTheme(data: data, child: child); @override - bool updateShouldNotify(StreamMessageListViewTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamMessageListViewTheme oldWidget) => data != oldWidget.data; } /// {@template messageListViewThemeData} diff --git a/packages/stream_chat_flutter/lib/src/theme/message_theme.dart b/packages/stream_chat_flutter/lib/src/theme/message_theme.dart index 62b38d4edd..1ad66765e0 100644 --- a/packages/stream_chat_flutter/lib/src/theme/message_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/message_theme.dart @@ -140,29 +140,20 @@ class StreamMessageThemeData with Diagnosticable { createdAtStyle: createdAtStyle ?? this.createdAtStyle, createdAtFormatter: createdAtFormatter ?? this.createdAtFormatter, messageDeletedStyle: messageDeletedStyle ?? this.messageDeletedStyle, - messageBackgroundColor: - messageBackgroundColor ?? this.messageBackgroundColor, - messageBackgroundGradient: - messageBackgroundGradient ?? this.messageBackgroundGradient, + messageBackgroundColor: messageBackgroundColor ?? this.messageBackgroundColor, + messageBackgroundGradient: messageBackgroundGradient ?? this.messageBackgroundGradient, messageBorderColor: messageBorderColor ?? this.messageBorderColor, avatarTheme: avatarTheme ?? this.avatarTheme, repliesStyle: repliesStyle ?? this.repliesStyle, - reactionsBackgroundColor: - reactionsBackgroundColor ?? this.reactionsBackgroundColor, + reactionsBackgroundColor: reactionsBackgroundColor ?? this.reactionsBackgroundColor, reactionsBorderColor: reactionsBorderColor ?? this.reactionsBorderColor, reactionsMaskColor: reactionsMaskColor ?? this.reactionsMaskColor, - urlAttachmentBackgroundColor: - urlAttachmentBackgroundColor ?? this.urlAttachmentBackgroundColor, - urlAttachmentHostStyle: - urlAttachmentHostStyle ?? this.urlAttachmentHostStyle, - urlAttachmentTitleStyle: - urlAttachmentTitleStyle ?? this.urlAttachmentTitleStyle, - urlAttachmentTextStyle: - urlAttachmentTextStyle ?? this.urlAttachmentTextStyle, - urlAttachmentTitleMaxLine: - urlAttachmentTitleMaxLine ?? this.urlAttachmentTitleMaxLine, - urlAttachmentTextMaxLine: - urlAttachmentTextMaxLine ?? this.urlAttachmentTextMaxLine, + urlAttachmentBackgroundColor: urlAttachmentBackgroundColor ?? this.urlAttachmentBackgroundColor, + urlAttachmentHostStyle: urlAttachmentHostStyle ?? this.urlAttachmentHostStyle, + urlAttachmentTitleStyle: urlAttachmentTitleStyle ?? this.urlAttachmentTitleStyle, + urlAttachmentTextStyle: urlAttachmentTextStyle ?? this.urlAttachmentTextStyle, + urlAttachmentTitleMaxLine: urlAttachmentTitleMaxLine ?? this.urlAttachmentTitleMaxLine, + urlAttachmentTextMaxLine: urlAttachmentTextMaxLine ?? this.urlAttachmentTextMaxLine, ); } @@ -173,41 +164,30 @@ class StreamMessageThemeData with Diagnosticable { double t, ) { return StreamMessageThemeData( - avatarTheme: - const StreamAvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t), - messageAuthorStyle: - TextStyle.lerp(a.messageAuthorStyle, b.messageAuthorStyle, t), + avatarTheme: const StreamAvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t), + messageAuthorStyle: TextStyle.lerp(a.messageAuthorStyle, b.messageAuthorStyle, t), createdAtStyle: TextStyle.lerp(a.createdAtStyle, b.createdAtStyle, t), createdAtFormatter: t < 0.5 ? a.createdAtFormatter : b.createdAtFormatter, - messageDeletedStyle: - TextStyle.lerp(a.messageDeletedStyle, b.messageDeletedStyle, t), - messageBackgroundColor: - Color.lerp(a.messageBackgroundColor, b.messageBackgroundColor, t), - messageBackgroundGradient: - t < 0.5 ? a.messageBackgroundGradient : b.messageBackgroundGradient, - messageBorderColor: - Color.lerp(a.messageBorderColor, b.messageBorderColor, t), - messageLinksStyle: - TextStyle.lerp(a.messageLinksStyle, b.messageLinksStyle, t), - messageTextStyle: - TextStyle.lerp(a.messageTextStyle, b.messageTextStyle, t), + messageDeletedStyle: TextStyle.lerp(a.messageDeletedStyle, b.messageDeletedStyle, t), + messageBackgroundColor: Color.lerp(a.messageBackgroundColor, b.messageBackgroundColor, t), + messageBackgroundGradient: t < 0.5 ? a.messageBackgroundGradient : b.messageBackgroundGradient, + messageBorderColor: Color.lerp(a.messageBorderColor, b.messageBorderColor, t), + messageLinksStyle: TextStyle.lerp(a.messageLinksStyle, b.messageLinksStyle, t), + messageTextStyle: TextStyle.lerp(a.messageTextStyle, b.messageTextStyle, t), reactionsBackgroundColor: Color.lerp( a.reactionsBackgroundColor, b.reactionsBackgroundColor, t, ), - reactionsBorderColor: - Color.lerp(a.messageBorderColor, b.reactionsBorderColor, t), - reactionsMaskColor: - Color.lerp(a.reactionsMaskColor, b.reactionsMaskColor, t), + reactionsBorderColor: Color.lerp(a.messageBorderColor, b.reactionsBorderColor, t), + reactionsMaskColor: Color.lerp(a.reactionsMaskColor, b.reactionsMaskColor, t), repliesStyle: TextStyle.lerp(a.repliesStyle, b.repliesStyle, t), urlAttachmentBackgroundColor: Color.lerp( a.urlAttachmentBackgroundColor, b.urlAttachmentBackgroundColor, t, ), - urlAttachmentHostStyle: - TextStyle.lerp(a.urlAttachmentHostStyle, b.urlAttachmentHostStyle, t), + urlAttachmentHostStyle: TextStyle.lerp(a.urlAttachmentHostStyle, b.urlAttachmentHostStyle, t), urlAttachmentTextStyle: TextStyle.lerp( a.urlAttachmentTextStyle, b.urlAttachmentTextStyle, @@ -235,20 +215,13 @@ class StreamMessageThemeData with Diagnosticable { StreamMessageThemeData merge(StreamMessageThemeData? other) { if (other == null) return this; return copyWith( - messageTextStyle: messageTextStyle?.merge(other.messageTextStyle) ?? - other.messageTextStyle, - messageAuthorStyle: messageAuthorStyle?.merge(other.messageAuthorStyle) ?? - other.messageAuthorStyle, - messageLinksStyle: messageLinksStyle?.merge(other.messageLinksStyle) ?? - other.messageLinksStyle, - createdAtStyle: - createdAtStyle?.merge(other.createdAtStyle) ?? other.createdAtStyle, + messageTextStyle: messageTextStyle?.merge(other.messageTextStyle) ?? other.messageTextStyle, + messageAuthorStyle: messageAuthorStyle?.merge(other.messageAuthorStyle) ?? other.messageAuthorStyle, + messageLinksStyle: messageLinksStyle?.merge(other.messageLinksStyle) ?? other.messageLinksStyle, + createdAtStyle: createdAtStyle?.merge(other.createdAtStyle) ?? other.createdAtStyle, createdAtFormatter: other.createdAtFormatter ?? createdAtFormatter, - messageDeletedStyle: - messageDeletedStyle?.merge(other.messageDeletedStyle) ?? - other.messageDeletedStyle, - repliesStyle: - repliesStyle?.merge(other.repliesStyle) ?? other.repliesStyle, + messageDeletedStyle: messageDeletedStyle?.merge(other.messageDeletedStyle) ?? other.messageDeletedStyle, + repliesStyle: repliesStyle?.merge(other.repliesStyle) ?? other.repliesStyle, messageBackgroundColor: other.messageBackgroundColor, messageBackgroundGradient: other.messageBackgroundGradient, messageBorderColor: other.messageBorderColor, @@ -326,36 +299,47 @@ class StreamMessageThemeData with Diagnosticable { ..add(DiagnosticsProperty('messageDeletedStyle', messageDeletedStyle)) ..add(DiagnosticsProperty('repliesStyle', repliesStyle)) ..add(ColorProperty('messageBackgroundColor', messageBackgroundColor)) - ..add(DiagnosticsProperty( - 'messageBackgroundGradient', messageBackgroundGradient)) + ..add(DiagnosticsProperty('messageBackgroundGradient', messageBackgroundGradient)) ..add(ColorProperty('messageBorderColor', messageBorderColor)) ..add(DiagnosticsProperty('avatarTheme', avatarTheme)) ..add(ColorProperty('reactionsBackgroundColor', reactionsBackgroundColor)) ..add(ColorProperty('reactionsBorderColor', reactionsBorderColor)) ..add(ColorProperty('reactionsMaskColor', reactionsMaskColor)) - ..add(ColorProperty( - 'urlAttachmentBackgroundColor', - urlAttachmentBackgroundColor, - )) - ..add(DiagnosticsProperty( - 'urlAttachmentHostStyle', - urlAttachmentHostStyle, - )) - ..add(DiagnosticsProperty( - 'urlAttachmentTitleStyle', - urlAttachmentTitleStyle, - )) - ..add(DiagnosticsProperty( - 'urlAttachmentTextStyle', - urlAttachmentTextStyle, - )) - ..add(DiagnosticsProperty( - 'urlAttachmentTitleMaxLine', - urlAttachmentTitleMaxLine, - )) - ..add(DiagnosticsProperty( - 'urlAttachmentTextMaxLine', - urlAttachmentTextMaxLine, - )); + ..add( + ColorProperty( + 'urlAttachmentBackgroundColor', + urlAttachmentBackgroundColor, + ), + ) + ..add( + DiagnosticsProperty( + 'urlAttachmentHostStyle', + urlAttachmentHostStyle, + ), + ) + ..add( + DiagnosticsProperty( + 'urlAttachmentTitleStyle', + urlAttachmentTitleStyle, + ), + ) + ..add( + DiagnosticsProperty( + 'urlAttachmentTextStyle', + urlAttachmentTextStyle, + ), + ) + ..add( + DiagnosticsProperty( + 'urlAttachmentTitleMaxLine', + urlAttachmentTitleMaxLine, + ), + ) + ..add( + DiagnosticsProperty( + 'urlAttachmentTextMaxLine', + urlAttachmentTextMaxLine, + ), + ); } } diff --git a/packages/stream_chat_flutter/lib/src/theme/poll_comments_dialog_theme.dart b/packages/stream_chat_flutter/lib/src/theme/poll_comments_dialog_theme.dart index 9ad433546e..f404fc6c36 100644 --- a/packages/stream_chat_flutter/lib/src/theme/poll_comments_dialog_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/poll_comments_dialog_theme.dart @@ -30,19 +30,15 @@ class StreamPollCommentsDialogTheme extends InheritedTheme { /// If there is no enclosing [StreamPollCommentsDialogTheme] widget, then /// [StreamChatThemeData.pollCommentsDialogTheme] is used. static StreamPollCommentsDialogThemeData of(BuildContext context) { - final pollCommentsDialogTheme = context - .dependOnInheritedWidgetOfExactType(); - return pollCommentsDialogTheme?.data ?? - StreamChatTheme.of(context).pollCommentsDialogTheme; + final pollCommentsDialogTheme = context.dependOnInheritedWidgetOfExactType(); + return pollCommentsDialogTheme?.data ?? StreamChatTheme.of(context).pollCommentsDialogTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamPollCommentsDialogTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamPollCommentsDialogTheme(data: data, child: child); @override - bool updateShouldNotify(StreamPollCommentsDialogTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamPollCommentsDialogTheme oldWidget) => data != oldWidget.data; } /// {@template streamPollCommentsDialogThemeData} @@ -97,22 +93,16 @@ class StreamPollCommentsDialogThemeData with Diagnosticable { Color? pollCommentItemBackgroundColor, BorderRadius? pollCommentItemBorderRadius, ButtonStyle? updateYourCommentButtonStyle, - }) => - StreamPollCommentsDialogThemeData( - backgroundColor: backgroundColor ?? this.backgroundColor, - appBarElevation: appBarElevation ?? this.appBarElevation, - appBarBackgroundColor: - appBarBackgroundColor ?? this.appBarBackgroundColor, - appBarForegroundColor: - appBarForegroundColor ?? this.appBarForegroundColor, - appBarTitleTextStyle: appBarTitleTextStyle ?? this.appBarTitleTextStyle, - pollCommentItemBackgroundColor: pollCommentItemBackgroundColor ?? - this.pollCommentItemBackgroundColor, - pollCommentItemBorderRadius: - pollCommentItemBorderRadius ?? this.pollCommentItemBorderRadius, - updateYourCommentButtonStyle: - updateYourCommentButtonStyle ?? this.updateYourCommentButtonStyle, - ); + }) => StreamPollCommentsDialogThemeData( + backgroundColor: backgroundColor ?? this.backgroundColor, + appBarElevation: appBarElevation ?? this.appBarElevation, + appBarBackgroundColor: appBarBackgroundColor ?? this.appBarBackgroundColor, + appBarForegroundColor: appBarForegroundColor ?? this.appBarForegroundColor, + appBarTitleTextStyle: appBarTitleTextStyle ?? this.appBarTitleTextStyle, + pollCommentItemBackgroundColor: pollCommentItemBackgroundColor ?? this.pollCommentItemBackgroundColor, + pollCommentItemBorderRadius: pollCommentItemBorderRadius ?? this.pollCommentItemBorderRadius, + updateYourCommentButtonStyle: updateYourCommentButtonStyle ?? this.updateYourCommentButtonStyle, + ); /// Merges this [StreamPollCommentsDialogThemeData] with the [other]. StreamPollCommentsDialogThemeData merge( @@ -140,12 +130,9 @@ class StreamPollCommentsDialogThemeData with Diagnosticable { return StreamPollCommentsDialogThemeData( backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t), appBarElevation: lerpDouble(a?.appBarElevation, b?.appBarElevation, t), - appBarBackgroundColor: - Color.lerp(a?.appBarBackgroundColor, b?.appBarBackgroundColor, t), - appBarForegroundColor: - Color.lerp(a?.appBarForegroundColor, b?.appBarForegroundColor, t), - appBarTitleTextStyle: - TextStyle.lerp(a?.appBarTitleTextStyle, b?.appBarTitleTextStyle, t), + appBarBackgroundColor: Color.lerp(a?.appBarBackgroundColor, b?.appBarBackgroundColor, t), + appBarForegroundColor: Color.lerp(a?.appBarForegroundColor, b?.appBarForegroundColor, t), + appBarTitleTextStyle: TextStyle.lerp(a?.appBarTitleTextStyle, b?.appBarTitleTextStyle, t), pollCommentItemBackgroundColor: Color.lerp( a?.pollCommentItemBackgroundColor, b?.pollCommentItemBackgroundColor, @@ -173,8 +160,7 @@ class StreamPollCommentsDialogThemeData with Diagnosticable { other.appBarBackgroundColor == appBarBackgroundColor && other.appBarForegroundColor == appBarForegroundColor && other.appBarTitleTextStyle == appBarTitleTextStyle && - other.pollCommentItemBackgroundColor == - pollCommentItemBackgroundColor && + other.pollCommentItemBackgroundColor == pollCommentItemBackgroundColor && other.pollCommentItemBorderRadius == pollCommentItemBorderRadius && other.updateYourCommentButtonStyle == updateYourCommentButtonStyle; diff --git a/packages/stream_chat_flutter/lib/src/theme/poll_creator_theme.dart b/packages/stream_chat_flutter/lib/src/theme/poll_creator_theme.dart index 5287547603..940fdbb4e2 100644 --- a/packages/stream_chat_flutter/lib/src/theme/poll_creator_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/poll_creator_theme.dart @@ -35,19 +35,15 @@ class StreamPollCreatorTheme extends InheritedTheme { /// StreamPollCreatorTheme theme = StreamPollCreatorTheme.of(context); /// ``` static StreamPollCreatorThemeData of(BuildContext context) { - final pollCreatorTheme = - context.dependOnInheritedWidgetOfExactType(); - return pollCreatorTheme?.data ?? - StreamChatTheme.of(context).pollCreatorTheme; + final pollCreatorTheme = context.dependOnInheritedWidgetOfExactType(); + return pollCreatorTheme?.data ?? StreamChatTheme.of(context).pollCreatorTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamPollCreatorTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamPollCreatorTheme(data: data, child: child); @override - bool updateShouldNotify(StreamPollCreatorTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamPollCreatorTheme oldWidget) => data != oldWidget.data; } /// {@template streamPollCreatorThemeData} @@ -173,40 +169,24 @@ class StreamPollCreatorThemeData with Diagnosticable { backgroundColor: backgroundColor ?? this.backgroundColor, appBarTitleStyle: appBarTitleStyle ?? this.appBarTitleStyle, appBarElevation: appBarElevation ?? this.appBarElevation, - appBarBackgroundColor: - appBarBackgroundColor ?? this.appBarBackgroundColor, - appBarForegroundColor: - appBarForegroundColor ?? this.appBarForegroundColor, - questionTextFieldFillColor: - questionTextFieldFillColor ?? this.questionTextFieldFillColor, + appBarBackgroundColor: appBarBackgroundColor ?? this.appBarBackgroundColor, + appBarForegroundColor: appBarForegroundColor ?? this.appBarForegroundColor, + questionTextFieldFillColor: questionTextFieldFillColor ?? this.questionTextFieldFillColor, questionHeaderStyle: questionHeaderStyle ?? this.questionHeaderStyle, - questionTextFieldStyle: - questionTextFieldStyle ?? this.questionTextFieldStyle, - questionTextFieldErrorStyle: - questionTextFieldErrorStyle ?? this.questionTextFieldErrorStyle, - questionTextFieldBorderRadius: - questionTextFieldBorderRadius ?? this.questionTextFieldBorderRadius, - optionsTextFieldFillColor: - optionsTextFieldFillColor ?? this.optionsTextFieldFillColor, + questionTextFieldStyle: questionTextFieldStyle ?? this.questionTextFieldStyle, + questionTextFieldErrorStyle: questionTextFieldErrorStyle ?? this.questionTextFieldErrorStyle, + questionTextFieldBorderRadius: questionTextFieldBorderRadius ?? this.questionTextFieldBorderRadius, + optionsTextFieldFillColor: optionsTextFieldFillColor ?? this.optionsTextFieldFillColor, optionsHeaderStyle: optionsHeaderStyle ?? this.optionsHeaderStyle, - optionsTextFieldStyle: - optionsTextFieldStyle ?? this.optionsTextFieldStyle, - optionsTextFieldErrorStyle: - optionsTextFieldErrorStyle ?? this.optionsTextFieldErrorStyle, - optionsTextFieldBorderRadius: - optionsTextFieldBorderRadius ?? this.optionsTextFieldBorderRadius, - switchListTileFillColor: - switchListTileFillColor ?? this.switchListTileFillColor, - switchListTileTitleStyle: - switchListTileTitleStyle ?? this.switchListTileTitleStyle, - switchListTileErrorStyle: - switchListTileErrorStyle ?? this.switchListTileErrorStyle, - switchListTileBorderRadius: - switchListTileBorderRadius ?? this.switchListTileBorderRadius, - actionDialogTitleStyle: - actionDialogTitleStyle ?? this.actionDialogTitleStyle, - actionDialogContentStyle: - actionDialogContentStyle ?? this.actionDialogContentStyle, + optionsTextFieldStyle: optionsTextFieldStyle ?? this.optionsTextFieldStyle, + optionsTextFieldErrorStyle: optionsTextFieldErrorStyle ?? this.optionsTextFieldErrorStyle, + optionsTextFieldBorderRadius: optionsTextFieldBorderRadius ?? this.optionsTextFieldBorderRadius, + switchListTileFillColor: switchListTileFillColor ?? this.switchListTileFillColor, + switchListTileTitleStyle: switchListTileTitleStyle ?? this.switchListTileTitleStyle, + switchListTileErrorStyle: switchListTileErrorStyle ?? this.switchListTileErrorStyle, + switchListTileBorderRadius: switchListTileBorderRadius ?? this.switchListTileBorderRadius, + actionDialogTitleStyle: actionDialogTitleStyle ?? this.actionDialogTitleStyle, + actionDialogContentStyle: actionDialogContentStyle ?? this.actionDialogContentStyle, ); } @@ -217,40 +197,24 @@ class StreamPollCreatorThemeData with Diagnosticable { backgroundColor: other.backgroundColor ?? backgroundColor, appBarTitleStyle: other.appBarTitleStyle ?? appBarTitleStyle, appBarElevation: other.appBarElevation ?? appBarElevation, - appBarBackgroundColor: - other.appBarBackgroundColor ?? appBarBackgroundColor, - appBarForegroundColor: - other.appBarForegroundColor ?? appBarForegroundColor, - questionTextFieldFillColor: - other.questionTextFieldFillColor ?? questionTextFieldFillColor, + appBarBackgroundColor: other.appBarBackgroundColor ?? appBarBackgroundColor, + appBarForegroundColor: other.appBarForegroundColor ?? appBarForegroundColor, + questionTextFieldFillColor: other.questionTextFieldFillColor ?? questionTextFieldFillColor, questionHeaderStyle: other.questionHeaderStyle ?? questionHeaderStyle, - questionTextFieldStyle: - other.questionTextFieldStyle ?? questionTextFieldStyle, - questionTextFieldErrorStyle: - other.questionTextFieldErrorStyle ?? questionTextFieldErrorStyle, - questionTextFieldBorderRadius: - other.questionTextFieldBorderRadius ?? questionTextFieldBorderRadius, - optionsTextFieldFillColor: - other.optionsTextFieldFillColor ?? optionsTextFieldFillColor, + questionTextFieldStyle: other.questionTextFieldStyle ?? questionTextFieldStyle, + questionTextFieldErrorStyle: other.questionTextFieldErrorStyle ?? questionTextFieldErrorStyle, + questionTextFieldBorderRadius: other.questionTextFieldBorderRadius ?? questionTextFieldBorderRadius, + optionsTextFieldFillColor: other.optionsTextFieldFillColor ?? optionsTextFieldFillColor, optionsHeaderStyle: other.optionsHeaderStyle ?? optionsHeaderStyle, - optionsTextFieldStyle: - other.optionsTextFieldStyle ?? optionsTextFieldStyle, - optionsTextFieldErrorStyle: - other.optionsTextFieldErrorStyle ?? optionsTextFieldErrorStyle, - optionsTextFieldBorderRadius: - other.optionsTextFieldBorderRadius ?? optionsTextFieldBorderRadius, - switchListTileFillColor: - other.switchListTileFillColor ?? switchListTileFillColor, - switchListTileTitleStyle: - other.switchListTileTitleStyle ?? switchListTileTitleStyle, - switchListTileErrorStyle: - other.switchListTileErrorStyle ?? switchListTileErrorStyle, - switchListTileBorderRadius: - other.switchListTileBorderRadius ?? switchListTileBorderRadius, - actionDialogTitleStyle: - other.actionDialogTitleStyle ?? actionDialogTitleStyle, - actionDialogContentStyle: - other.actionDialogContentStyle ?? actionDialogContentStyle, + optionsTextFieldStyle: other.optionsTextFieldStyle ?? optionsTextFieldStyle, + optionsTextFieldErrorStyle: other.optionsTextFieldErrorStyle ?? optionsTextFieldErrorStyle, + optionsTextFieldBorderRadius: other.optionsTextFieldBorderRadius ?? optionsTextFieldBorderRadius, + switchListTileFillColor: other.switchListTileFillColor ?? switchListTileFillColor, + switchListTileTitleStyle: other.switchListTileTitleStyle ?? switchListTileTitleStyle, + switchListTileErrorStyle: other.switchListTileErrorStyle ?? switchListTileErrorStyle, + switchListTileBorderRadius: other.switchListTileBorderRadius ?? switchListTileBorderRadius, + actionDialogTitleStyle: other.actionDialogTitleStyle ?? actionDialogTitleStyle, + actionDialogContentStyle: other.actionDialogContentStyle ?? actionDialogContentStyle, ); } @@ -262,45 +226,34 @@ class StreamPollCreatorThemeData with Diagnosticable { ) { return StreamPollCreatorThemeData( backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), - appBarTitleStyle: - TextStyle.lerp(a.appBarTitleStyle, b.appBarTitleStyle, t), + appBarTitleStyle: TextStyle.lerp(a.appBarTitleStyle, b.appBarTitleStyle, t), appBarElevation: lerpDouble(a.appBarElevation, b.appBarElevation, t), - appBarBackgroundColor: - Color.lerp(a.appBarBackgroundColor, b.appBarBackgroundColor, t), - appBarForegroundColor: - Color.lerp(a.appBarForegroundColor, b.appBarForegroundColor, t), - questionTextFieldFillColor: Color.lerp( - a.questionTextFieldFillColor, b.questionTextFieldFillColor, t), - questionHeaderStyle: - TextStyle.lerp(a.questionHeaderStyle, b.questionHeaderStyle, t), - questionTextFieldStyle: - TextStyle.lerp(a.questionTextFieldStyle, b.questionTextFieldStyle, t), - questionTextFieldErrorStyle: TextStyle.lerp( - a.questionTextFieldErrorStyle, b.questionTextFieldErrorStyle, t), + appBarBackgroundColor: Color.lerp(a.appBarBackgroundColor, b.appBarBackgroundColor, t), + appBarForegroundColor: Color.lerp(a.appBarForegroundColor, b.appBarForegroundColor, t), + questionTextFieldFillColor: Color.lerp(a.questionTextFieldFillColor, b.questionTextFieldFillColor, t), + questionHeaderStyle: TextStyle.lerp(a.questionHeaderStyle, b.questionHeaderStyle, t), + questionTextFieldStyle: TextStyle.lerp(a.questionTextFieldStyle, b.questionTextFieldStyle, t), + questionTextFieldErrorStyle: TextStyle.lerp(a.questionTextFieldErrorStyle, b.questionTextFieldErrorStyle, t), questionTextFieldBorderRadius: BorderRadius.lerp( - a.questionTextFieldBorderRadius, b.questionTextFieldBorderRadius, t), - optionsTextFieldFillColor: Color.lerp( - a.optionsTextFieldFillColor, b.optionsTextFieldFillColor, t), - optionsHeaderStyle: - TextStyle.lerp(a.optionsHeaderStyle, b.optionsHeaderStyle, t), - optionsTextFieldStyle: - TextStyle.lerp(a.optionsTextFieldStyle, b.optionsTextFieldStyle, t), - optionsTextFieldErrorStyle: TextStyle.lerp( - a.optionsTextFieldErrorStyle, b.optionsTextFieldErrorStyle, t), + a.questionTextFieldBorderRadius, + b.questionTextFieldBorderRadius, + t, + ), + optionsTextFieldFillColor: Color.lerp(a.optionsTextFieldFillColor, b.optionsTextFieldFillColor, t), + optionsHeaderStyle: TextStyle.lerp(a.optionsHeaderStyle, b.optionsHeaderStyle, t), + optionsTextFieldStyle: TextStyle.lerp(a.optionsTextFieldStyle, b.optionsTextFieldStyle, t), + optionsTextFieldErrorStyle: TextStyle.lerp(a.optionsTextFieldErrorStyle, b.optionsTextFieldErrorStyle, t), optionsTextFieldBorderRadius: BorderRadius.lerp( - a.optionsTextFieldBorderRadius, b.optionsTextFieldBorderRadius, t), - switchListTileFillColor: - Color.lerp(a.switchListTileFillColor, b.switchListTileFillColor, t), - switchListTileTitleStyle: TextStyle.lerp( - a.switchListTileTitleStyle, b.switchListTileTitleStyle, t), - switchListTileErrorStyle: TextStyle.lerp( - a.switchListTileErrorStyle, b.switchListTileErrorStyle, t), - switchListTileBorderRadius: BorderRadius.lerp( - a.switchListTileBorderRadius, b.switchListTileBorderRadius, t), - actionDialogTitleStyle: - TextStyle.lerp(a.actionDialogTitleStyle, b.actionDialogTitleStyle, t), - actionDialogContentStyle: TextStyle.lerp( - a.actionDialogContentStyle, b.actionDialogContentStyle, t), + a.optionsTextFieldBorderRadius, + b.optionsTextFieldBorderRadius, + t, + ), + switchListTileFillColor: Color.lerp(a.switchListTileFillColor, b.switchListTileFillColor, t), + switchListTileTitleStyle: TextStyle.lerp(a.switchListTileTitleStyle, b.switchListTileTitleStyle, t), + switchListTileErrorStyle: TextStyle.lerp(a.switchListTileErrorStyle, b.switchListTileErrorStyle, t), + switchListTileBorderRadius: BorderRadius.lerp(a.switchListTileBorderRadius, b.switchListTileBorderRadius, t), + actionDialogTitleStyle: TextStyle.lerp(a.actionDialogTitleStyle, b.actionDialogTitleStyle, t), + actionDialogContentStyle: TextStyle.lerp(a.actionDialogContentStyle, b.actionDialogContentStyle, t), ); } @@ -317,8 +270,7 @@ class StreamPollCreatorThemeData with Diagnosticable { other.questionHeaderStyle == questionHeaderStyle && other.questionTextFieldStyle == questionTextFieldStyle && other.questionTextFieldErrorStyle == questionTextFieldErrorStyle && - other.questionTextFieldBorderRadius == - questionTextFieldBorderRadius && + other.questionTextFieldBorderRadius == questionTextFieldBorderRadius && other.optionsTextFieldFillColor == optionsTextFieldFillColor && other.optionsHeaderStyle == optionsHeaderStyle && other.optionsTextFieldStyle == optionsTextFieldStyle && diff --git a/packages/stream_chat_flutter/lib/src/theme/poll_interactor_theme.dart b/packages/stream_chat_flutter/lib/src/theme/poll_interactor_theme.dart index 1880ad5ce7..5eba4be0ac 100644 --- a/packages/stream_chat_flutter/lib/src/theme/poll_interactor_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/poll_interactor_theme.dart @@ -37,19 +37,15 @@ class StreamPollInteractorTheme extends InheritedTheme { /// StreamPollInteractorTheme theme = StreamPollInteractorTheme.of(context); /// ``` static StreamPollInteractorThemeData of(BuildContext context) { - final pollInteractorTheme = - context.dependOnInheritedWidgetOfExactType(); - return pollInteractorTheme?.data ?? - StreamChatTheme.of(context).pollInteractorTheme; + final pollInteractorTheme = context.dependOnInheritedWidgetOfExactType(); + return pollInteractorTheme?.data ?? StreamChatTheme.of(context).pollInteractorTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamPollInteractorTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamPollInteractorTheme(data: data, child: child); @override - bool updateShouldNotify(StreamPollInteractorTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamPollInteractorTheme oldWidget) => data != oldWidget.data; } /// {@template streamPollInteractorThemeData} @@ -160,42 +156,27 @@ class StreamPollInteractorThemeData with Diagnosticable { pollTitleStyle: pollTitleStyle ?? this.pollTitleStyle, pollSubtitleStyle: pollSubtitleStyle ?? this.pollSubtitleStyle, pollOptionTextStyle: pollOptionTextStyle ?? this.pollOptionTextStyle, - pollOptionVoteCountTextStyle: - pollOptionVoteCountTextStyle ?? this.pollOptionVoteCountTextStyle, - pollOptionCheckboxShape: - pollOptionCheckboxShape ?? this.pollOptionCheckboxShape, - pollOptionCheckboxCheckColor: - pollOptionCheckboxCheckColor ?? this.pollOptionCheckboxCheckColor, - pollOptionCheckboxActiveColor: - pollOptionCheckboxActiveColor ?? this.pollOptionCheckboxActiveColor, - pollOptionCheckboxBorderSide: - pollOptionCheckboxBorderSide ?? this.pollOptionCheckboxBorderSide, + pollOptionVoteCountTextStyle: pollOptionVoteCountTextStyle ?? this.pollOptionVoteCountTextStyle, + pollOptionCheckboxShape: pollOptionCheckboxShape ?? this.pollOptionCheckboxShape, + pollOptionCheckboxCheckColor: pollOptionCheckboxCheckColor ?? this.pollOptionCheckboxCheckColor, + pollOptionCheckboxActiveColor: pollOptionCheckboxActiveColor ?? this.pollOptionCheckboxActiveColor, + pollOptionCheckboxBorderSide: pollOptionCheckboxBorderSide ?? this.pollOptionCheckboxBorderSide, pollOptionVotesProgressBarMinHeight: - pollOptionVotesProgressBarMinHeight ?? - this.pollOptionVotesProgressBarMinHeight, + pollOptionVotesProgressBarMinHeight ?? this.pollOptionVotesProgressBarMinHeight, pollOptionVotesProgressBarTrackColor: - pollOptionVotesProgressBarTrackColor ?? - this.pollOptionVotesProgressBarTrackColor, + pollOptionVotesProgressBarTrackColor ?? this.pollOptionVotesProgressBarTrackColor, pollOptionVotesProgressBarValueColor: - pollOptionVotesProgressBarValueColor ?? - this.pollOptionVotesProgressBarValueColor, + pollOptionVotesProgressBarValueColor ?? this.pollOptionVotesProgressBarValueColor, pollOptionVotesProgressBarWinnerColor: - pollOptionVotesProgressBarWinnerColor ?? - this.pollOptionVotesProgressBarWinnerColor, + pollOptionVotesProgressBarWinnerColor ?? this.pollOptionVotesProgressBarWinnerColor, pollOptionVotesProgressBarBorderRadius: - pollOptionVotesProgressBarBorderRadius ?? - this.pollOptionVotesProgressBarBorderRadius, - pollActionButtonStyle: - pollActionButtonStyle ?? this.pollActionButtonStyle, - pollActionDialogTitleStyle: - pollActionDialogTitleStyle ?? this.pollActionDialogTitleStyle, - pollActionDialogTextFieldStyle: - pollActionDialogTextFieldStyle ?? this.pollActionDialogTextFieldStyle, - pollActionDialogTextFieldFillColor: pollActionDialogTextFieldFillColor ?? - this.pollActionDialogTextFieldFillColor, + pollOptionVotesProgressBarBorderRadius ?? this.pollOptionVotesProgressBarBorderRadius, + pollActionButtonStyle: pollActionButtonStyle ?? this.pollActionButtonStyle, + pollActionDialogTitleStyle: pollActionDialogTitleStyle ?? this.pollActionDialogTitleStyle, + pollActionDialogTextFieldStyle: pollActionDialogTextFieldStyle ?? this.pollActionDialogTextFieldStyle, + pollActionDialogTextFieldFillColor: pollActionDialogTextFieldFillColor ?? this.pollActionDialogTextFieldFillColor, pollActionDialogTextFieldBorderRadius: - pollActionDialogTextFieldBorderRadius ?? - this.pollActionDialogTextFieldBorderRadius, + pollActionDialogTextFieldBorderRadius ?? this.pollActionDialogTextFieldBorderRadius, ); } @@ -206,43 +187,28 @@ class StreamPollInteractorThemeData with Diagnosticable { pollTitleStyle: other.pollTitleStyle ?? pollTitleStyle, pollSubtitleStyle: other.pollSubtitleStyle ?? pollSubtitleStyle, pollOptionTextStyle: other.pollOptionTextStyle ?? pollOptionTextStyle, - pollOptionVoteCountTextStyle: - other.pollOptionVoteCountTextStyle ?? pollOptionVoteCountTextStyle, - pollOptionCheckboxShape: - other.pollOptionCheckboxShape ?? pollOptionCheckboxShape, - pollOptionCheckboxCheckColor: - other.pollOptionCheckboxCheckColor ?? pollOptionCheckboxCheckColor, - pollOptionCheckboxActiveColor: - other.pollOptionCheckboxActiveColor ?? pollOptionCheckboxActiveColor, - pollOptionCheckboxBorderSide: - other.pollOptionCheckboxBorderSide ?? pollOptionCheckboxBorderSide, + pollOptionVoteCountTextStyle: other.pollOptionVoteCountTextStyle ?? pollOptionVoteCountTextStyle, + pollOptionCheckboxShape: other.pollOptionCheckboxShape ?? pollOptionCheckboxShape, + pollOptionCheckboxCheckColor: other.pollOptionCheckboxCheckColor ?? pollOptionCheckboxCheckColor, + pollOptionCheckboxActiveColor: other.pollOptionCheckboxActiveColor ?? pollOptionCheckboxActiveColor, + pollOptionCheckboxBorderSide: other.pollOptionCheckboxBorderSide ?? pollOptionCheckboxBorderSide, pollOptionVotesProgressBarMinHeight: - other.pollOptionVotesProgressBarMinHeight ?? - pollOptionVotesProgressBarMinHeight, + other.pollOptionVotesProgressBarMinHeight ?? pollOptionVotesProgressBarMinHeight, pollOptionVotesProgressBarTrackColor: - other.pollOptionVotesProgressBarTrackColor ?? - pollOptionVotesProgressBarTrackColor, + other.pollOptionVotesProgressBarTrackColor ?? pollOptionVotesProgressBarTrackColor, pollOptionVotesProgressBarValueColor: - other.pollOptionVotesProgressBarValueColor ?? - pollOptionVotesProgressBarValueColor, + other.pollOptionVotesProgressBarValueColor ?? pollOptionVotesProgressBarValueColor, pollOptionVotesProgressBarWinnerColor: - other.pollOptionVotesProgressBarWinnerColor ?? - pollOptionVotesProgressBarWinnerColor, + other.pollOptionVotesProgressBarWinnerColor ?? pollOptionVotesProgressBarWinnerColor, pollOptionVotesProgressBarBorderRadius: - other.pollOptionVotesProgressBarBorderRadius ?? - pollOptionVotesProgressBarBorderRadius, - pollActionButtonStyle: - other.pollActionButtonStyle ?? pollActionButtonStyle, - pollActionDialogTitleStyle: - other.pollActionDialogTitleStyle ?? pollActionDialogTitleStyle, - pollActionDialogTextFieldStyle: other.pollActionDialogTextFieldStyle ?? - pollActionDialogTextFieldStyle, + other.pollOptionVotesProgressBarBorderRadius ?? pollOptionVotesProgressBarBorderRadius, + pollActionButtonStyle: other.pollActionButtonStyle ?? pollActionButtonStyle, + pollActionDialogTitleStyle: other.pollActionDialogTitleStyle ?? pollActionDialogTitleStyle, + pollActionDialogTextFieldStyle: other.pollActionDialogTextFieldStyle ?? pollActionDialogTextFieldStyle, pollActionDialogTextFieldFillColor: - other.pollActionDialogTextFieldFillColor ?? - pollActionDialogTextFieldFillColor, + other.pollActionDialogTextFieldFillColor ?? pollActionDialogTextFieldFillColor, pollActionDialogTextFieldBorderRadius: - other.pollActionDialogTextFieldBorderRadius ?? - pollActionDialogTextFieldBorderRadius, + other.pollActionDialogTextFieldBorderRadius ?? pollActionDialogTextFieldBorderRadius, ); } @@ -254,56 +220,55 @@ class StreamPollInteractorThemeData with Diagnosticable { ) { return StreamPollInteractorThemeData( pollTitleStyle: TextStyle.lerp(a.pollTitleStyle, b.pollTitleStyle, t), - pollSubtitleStyle: - TextStyle.lerp(a.pollSubtitleStyle, b.pollSubtitleStyle, t), - pollOptionTextStyle: - TextStyle.lerp(a.pollOptionTextStyle, b.pollOptionTextStyle, t), - pollOptionVoteCountTextStyle: TextStyle.lerp( - a.pollOptionVoteCountTextStyle, b.pollOptionVoteCountTextStyle, t), - pollOptionCheckboxShape: OutlinedBorder.lerp( - a.pollOptionCheckboxShape, b.pollOptionCheckboxShape, t), - pollOptionCheckboxCheckColor: Color.lerp( - a.pollOptionCheckboxCheckColor, b.pollOptionCheckboxCheckColor, t), - pollOptionCheckboxActiveColor: Color.lerp( - a.pollOptionCheckboxActiveColor, b.pollOptionCheckboxActiveColor, t), - pollOptionCheckboxBorderSide: _lerpSides( - a.pollOptionCheckboxBorderSide, b.pollOptionCheckboxBorderSide, t), + pollSubtitleStyle: TextStyle.lerp(a.pollSubtitleStyle, b.pollSubtitleStyle, t), + pollOptionTextStyle: TextStyle.lerp(a.pollOptionTextStyle, b.pollOptionTextStyle, t), + pollOptionVoteCountTextStyle: TextStyle.lerp(a.pollOptionVoteCountTextStyle, b.pollOptionVoteCountTextStyle, t), + pollOptionCheckboxShape: OutlinedBorder.lerp(a.pollOptionCheckboxShape, b.pollOptionCheckboxShape, t), + pollOptionCheckboxCheckColor: Color.lerp(a.pollOptionCheckboxCheckColor, b.pollOptionCheckboxCheckColor, t), + pollOptionCheckboxActiveColor: Color.lerp(a.pollOptionCheckboxActiveColor, b.pollOptionCheckboxActiveColor, t), + pollOptionCheckboxBorderSide: _lerpSides(a.pollOptionCheckboxBorderSide, b.pollOptionCheckboxBorderSide, t), pollOptionVotesProgressBarMinHeight: lerpDouble( - a.pollOptionVotesProgressBarMinHeight, - b.pollOptionVotesProgressBarMinHeight, - t), + a.pollOptionVotesProgressBarMinHeight, + b.pollOptionVotesProgressBarMinHeight, + t, + ), pollOptionVotesProgressBarTrackColor: Color.lerp( - a.pollOptionVotesProgressBarTrackColor, - b.pollOptionVotesProgressBarTrackColor, - t), + a.pollOptionVotesProgressBarTrackColor, + b.pollOptionVotesProgressBarTrackColor, + t, + ), pollOptionVotesProgressBarValueColor: Color.lerp( - a.pollOptionVotesProgressBarValueColor, - b.pollOptionVotesProgressBarValueColor, - t), + a.pollOptionVotesProgressBarValueColor, + b.pollOptionVotesProgressBarValueColor, + t, + ), pollOptionVotesProgressBarWinnerColor: Color.lerp( - a.pollOptionVotesProgressBarWinnerColor, - b.pollOptionVotesProgressBarWinnerColor, - t), + a.pollOptionVotesProgressBarWinnerColor, + b.pollOptionVotesProgressBarWinnerColor, + t, + ), pollOptionVotesProgressBarBorderRadius: BorderRadius.lerp( - a.pollOptionVotesProgressBarBorderRadius, - b.pollOptionVotesProgressBarBorderRadius, - t), - pollActionButtonStyle: - ButtonStyle.lerp(a.pollActionButtonStyle, b.pollActionButtonStyle, t), - pollActionDialogTitleStyle: TextStyle.lerp( - a.pollActionDialogTitleStyle, b.pollActionDialogTitleStyle, t), + a.pollOptionVotesProgressBarBorderRadius, + b.pollOptionVotesProgressBarBorderRadius, + t, + ), + pollActionButtonStyle: ButtonStyle.lerp(a.pollActionButtonStyle, b.pollActionButtonStyle, t), + pollActionDialogTitleStyle: TextStyle.lerp(a.pollActionDialogTitleStyle, b.pollActionDialogTitleStyle, t), pollActionDialogTextFieldStyle: TextStyle.lerp( - a.pollActionDialogTextFieldStyle, - b.pollActionDialogTextFieldStyle, - t), + a.pollActionDialogTextFieldStyle, + b.pollActionDialogTextFieldStyle, + t, + ), pollActionDialogTextFieldFillColor: Color.lerp( - a.pollActionDialogTextFieldFillColor, - b.pollActionDialogTextFieldFillColor, - t), + a.pollActionDialogTextFieldFillColor, + b.pollActionDialogTextFieldFillColor, + t, + ), pollActionDialogTextFieldBorderRadius: BorderRadius.lerp( - a.pollActionDialogTextFieldBorderRadius, - b.pollActionDialogTextFieldBorderRadius, - t), + a.pollActionDialogTextFieldBorderRadius, + b.pollActionDialogTextFieldBorderRadius, + t, + ), ); } @@ -332,27 +297,18 @@ class StreamPollInteractorThemeData with Diagnosticable { other.pollOptionVoteCountTextStyle == pollOptionVoteCountTextStyle && other.pollOptionCheckboxShape == pollOptionCheckboxShape && other.pollOptionCheckboxCheckColor == pollOptionCheckboxCheckColor && - other.pollOptionCheckboxActiveColor == - pollOptionCheckboxActiveColor && + other.pollOptionCheckboxActiveColor == pollOptionCheckboxActiveColor && other.pollOptionCheckboxBorderSide == pollOptionCheckboxBorderSide && - other.pollOptionVotesProgressBarMinHeight == - pollOptionVotesProgressBarMinHeight && - other.pollOptionVotesProgressBarTrackColor == - pollOptionVotesProgressBarTrackColor && - other.pollOptionVotesProgressBarValueColor == - pollOptionVotesProgressBarValueColor && - other.pollOptionVotesProgressBarWinnerColor == - pollOptionVotesProgressBarWinnerColor && - other.pollOptionVotesProgressBarBorderRadius == - pollOptionVotesProgressBarBorderRadius && + other.pollOptionVotesProgressBarMinHeight == pollOptionVotesProgressBarMinHeight && + other.pollOptionVotesProgressBarTrackColor == pollOptionVotesProgressBarTrackColor && + other.pollOptionVotesProgressBarValueColor == pollOptionVotesProgressBarValueColor && + other.pollOptionVotesProgressBarWinnerColor == pollOptionVotesProgressBarWinnerColor && + other.pollOptionVotesProgressBarBorderRadius == pollOptionVotesProgressBarBorderRadius && other.pollActionButtonStyle == pollActionButtonStyle && other.pollActionDialogTitleStyle == pollActionDialogTitleStyle && - other.pollActionDialogTextFieldStyle == - pollActionDialogTextFieldStyle && - other.pollActionDialogTextFieldFillColor == - pollActionDialogTextFieldFillColor && - other.pollActionDialogTextFieldBorderRadius == - pollActionDialogTextFieldBorderRadius; + other.pollActionDialogTextFieldStyle == pollActionDialogTextFieldStyle && + other.pollActionDialogTextFieldFillColor == pollActionDialogTextFieldFillColor && + other.pollActionDialogTextFieldBorderRadius == pollActionDialogTextFieldBorderRadius; @override int get hashCode => diff --git a/packages/stream_chat_flutter/lib/src/theme/poll_option_votes_dialog_theme.dart b/packages/stream_chat_flutter/lib/src/theme/poll_option_votes_dialog_theme.dart index 72ee25dc4d..e3b5307426 100644 --- a/packages/stream_chat_flutter/lib/src/theme/poll_option_votes_dialog_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/poll_option_votes_dialog_theme.dart @@ -30,19 +30,15 @@ class StreamPollOptionVotesDialogTheme extends InheritedTheme { /// If there is no enclosing [StreamPollOptionVotesDialogTheme] widget, then /// [StreamChatThemeData.pollOptionVotesDialogTheme] is used. static StreamPollOptionVotesDialogThemeData of(BuildContext context) { - final pollCommentsDialogTheme = context - .dependOnInheritedWidgetOfExactType(); - return pollCommentsDialogTheme?.data ?? - StreamChatTheme.of(context).pollOptionVotesDialogTheme; + final pollCommentsDialogTheme = context.dependOnInheritedWidgetOfExactType(); + return pollCommentsDialogTheme?.data ?? StreamChatTheme.of(context).pollOptionVotesDialogTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamPollOptionVotesDialogTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamPollOptionVotesDialogTheme(data: data, child: child); @override - bool updateShouldNotify(StreamPollOptionVotesDialogTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamPollOptionVotesDialogTheme oldWidget) => data != oldWidget.data; } /// {@template streamPollOptionVotesDialogThemeData} @@ -103,25 +99,17 @@ class StreamPollOptionVotesDialogThemeData with Diagnosticable { TextStyle? pollOptionWinnerVoteCountTextStyle, Color? pollOptionVoteItemBackgroundColor, BorderRadius? pollOptionVoteItemBorderRadius, - }) => - StreamPollOptionVotesDialogThemeData( - backgroundColor: backgroundColor ?? this.backgroundColor, - appBarElevation: appBarElevation ?? this.appBarElevation, - appBarBackgroundColor: - appBarBackgroundColor ?? this.appBarBackgroundColor, - appBarForegroundColor: - appBarForegroundColor ?? this.appBarForegroundColor, - appBarTitleTextStyle: appBarTitleTextStyle ?? this.appBarTitleTextStyle, - pollOptionVoteCountTextStyle: - pollOptionVoteCountTextStyle ?? this.pollOptionVoteCountTextStyle, - pollOptionWinnerVoteCountTextStyle: - pollOptionWinnerVoteCountTextStyle ?? - this.pollOptionWinnerVoteCountTextStyle, - pollOptionVoteItemBackgroundColor: pollOptionVoteItemBackgroundColor ?? - this.pollOptionVoteItemBackgroundColor, - pollOptionVoteItemBorderRadius: pollOptionVoteItemBorderRadius ?? - this.pollOptionVoteItemBorderRadius, - ); + }) => StreamPollOptionVotesDialogThemeData( + backgroundColor: backgroundColor ?? this.backgroundColor, + appBarElevation: appBarElevation ?? this.appBarElevation, + appBarBackgroundColor: appBarBackgroundColor ?? this.appBarBackgroundColor, + appBarForegroundColor: appBarForegroundColor ?? this.appBarForegroundColor, + appBarTitleTextStyle: appBarTitleTextStyle ?? this.appBarTitleTextStyle, + pollOptionVoteCountTextStyle: pollOptionVoteCountTextStyle ?? this.pollOptionVoteCountTextStyle, + pollOptionWinnerVoteCountTextStyle: pollOptionWinnerVoteCountTextStyle ?? this.pollOptionWinnerVoteCountTextStyle, + pollOptionVoteItemBackgroundColor: pollOptionVoteItemBackgroundColor ?? this.pollOptionVoteItemBackgroundColor, + pollOptionVoteItemBorderRadius: pollOptionVoteItemBorderRadius ?? this.pollOptionVoteItemBorderRadius, + ); /// Merges this [StreamPollOptionVotesDialogThemeData] with the [other]. StreamPollOptionVotesDialogThemeData merge( @@ -135,10 +123,8 @@ class StreamPollOptionVotesDialogThemeData with Diagnosticable { appBarForegroundColor: other.appBarForegroundColor, appBarTitleTextStyle: other.appBarTitleTextStyle, pollOptionVoteCountTextStyle: other.pollOptionVoteCountTextStyle, - pollOptionWinnerVoteCountTextStyle: - other.pollOptionWinnerVoteCountTextStyle, - pollOptionVoteItemBackgroundColor: - other.pollOptionVoteItemBackgroundColor, + pollOptionWinnerVoteCountTextStyle: other.pollOptionWinnerVoteCountTextStyle, + pollOptionVoteItemBackgroundColor: other.pollOptionVoteItemBackgroundColor, pollOptionVoteItemBorderRadius: other.pollOptionVoteItemBorderRadius, ); } @@ -152,12 +138,9 @@ class StreamPollOptionVotesDialogThemeData with Diagnosticable { return StreamPollOptionVotesDialogThemeData( backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t), appBarElevation: lerpDouble(a?.appBarElevation, b?.appBarElevation, t), - appBarBackgroundColor: - Color.lerp(a?.appBarBackgroundColor, b?.appBarBackgroundColor, t), - appBarForegroundColor: - Color.lerp(a?.appBarForegroundColor, b?.appBarForegroundColor, t), - appBarTitleTextStyle: - TextStyle.lerp(a?.appBarTitleTextStyle, b?.appBarTitleTextStyle, t), + appBarBackgroundColor: Color.lerp(a?.appBarBackgroundColor, b?.appBarBackgroundColor, t), + appBarForegroundColor: Color.lerp(a?.appBarForegroundColor, b?.appBarForegroundColor, t), + appBarTitleTextStyle: TextStyle.lerp(a?.appBarTitleTextStyle, b?.appBarTitleTextStyle, t), pollOptionVoteCountTextStyle: TextStyle.lerp( a?.pollOptionVoteCountTextStyle, b?.pollOptionVoteCountTextStyle, @@ -173,11 +156,13 @@ class StreamPollOptionVotesDialogThemeData with Diagnosticable { b?.pollOptionVoteItemBackgroundColor, t, ), - pollOptionVoteItemBorderRadius: BorderRadiusGeometry.lerp( - a?.pollOptionVoteItemBorderRadius, - b?.pollOptionVoteItemBorderRadius, - t, - ) as BorderRadius?, + pollOptionVoteItemBorderRadius: + BorderRadiusGeometry.lerp( + a?.pollOptionVoteItemBorderRadius, + b?.pollOptionVoteItemBorderRadius, + t, + ) + as BorderRadius?, ); } @@ -191,12 +176,9 @@ class StreamPollOptionVotesDialogThemeData with Diagnosticable { other.appBarForegroundColor == appBarForegroundColor && other.appBarTitleTextStyle == appBarTitleTextStyle && other.pollOptionVoteCountTextStyle == pollOptionVoteCountTextStyle && - other.pollOptionWinnerVoteCountTextStyle == - pollOptionWinnerVoteCountTextStyle && - other.pollOptionVoteItemBackgroundColor == - pollOptionVoteItemBackgroundColor && - other.pollOptionVoteItemBorderRadius == - pollOptionVoteItemBorderRadius; + other.pollOptionWinnerVoteCountTextStyle == pollOptionWinnerVoteCountTextStyle && + other.pollOptionVoteItemBackgroundColor == pollOptionVoteItemBackgroundColor && + other.pollOptionVoteItemBorderRadius == pollOptionVoteItemBorderRadius; @override int get hashCode => diff --git a/packages/stream_chat_flutter/lib/src/theme/poll_options_dialog_theme.dart b/packages/stream_chat_flutter/lib/src/theme/poll_options_dialog_theme.dart index 8d2e07c8a8..cdf780e512 100644 --- a/packages/stream_chat_flutter/lib/src/theme/poll_options_dialog_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/poll_options_dialog_theme.dart @@ -30,19 +30,15 @@ class StreamPollOptionsDialogTheme extends InheritedTheme { /// If there is no enclosing [StreamPollOptionsDialogTheme] widget, then /// [StreamChatThemeData.pollOptionsDialogTheme] is used. static StreamPollOptionsDialogThemeData of(BuildContext context) { - final pollOptionsDialogTheme = context - .dependOnInheritedWidgetOfExactType(); - return pollOptionsDialogTheme?.data ?? - StreamChatTheme.of(context).pollOptionsDialogTheme; + final pollOptionsDialogTheme = context.dependOnInheritedWidgetOfExactType(); + return pollOptionsDialogTheme?.data ?? StreamChatTheme.of(context).pollOptionsDialogTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamPollOptionsDialogTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamPollOptionsDialogTheme(data: data, child: child); @override - bool updateShouldNotify(StreamPollOptionsDialogTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamPollOptionsDialogTheme oldWidget) => data != oldWidget.data; } /// {@template streamPollOptionsDialogThemeData} @@ -98,20 +94,16 @@ class StreamPollOptionsDialogThemeData with Diagnosticable { TextStyle? pollTitleTextStyle, Decoration? pollTitleDecoration, Decoration? pollOptionsListViewDecoration, - }) => - StreamPollOptionsDialogThemeData( - backgroundColor: backgroundColor ?? this.backgroundColor, - appBarElevation: appBarElevation ?? this.appBarElevation, - appBarBackgroundColor: - appBarBackgroundColor ?? this.appBarBackgroundColor, - appBarForegroundColor: - appBarForegroundColor ?? this.appBarForegroundColor, - appBarTitleTextStyle: appBarTitleTextStyle ?? this.appBarTitleTextStyle, - pollTitleTextStyle: pollTitleTextStyle ?? this.pollTitleTextStyle, - pollTitleDecoration: pollTitleDecoration ?? this.pollTitleDecoration, - pollOptionsListViewDecoration: - pollOptionsListViewDecoration ?? this.pollOptionsListViewDecoration, - ); + }) => StreamPollOptionsDialogThemeData( + backgroundColor: backgroundColor ?? this.backgroundColor, + appBarElevation: appBarElevation ?? this.appBarElevation, + appBarBackgroundColor: appBarBackgroundColor ?? this.appBarBackgroundColor, + appBarForegroundColor: appBarForegroundColor ?? this.appBarForegroundColor, + appBarTitleTextStyle: appBarTitleTextStyle ?? this.appBarTitleTextStyle, + pollTitleTextStyle: pollTitleTextStyle ?? this.pollTitleTextStyle, + pollTitleDecoration: pollTitleDecoration ?? this.pollTitleDecoration, + pollOptionsListViewDecoration: pollOptionsListViewDecoration ?? this.pollOptionsListViewDecoration, + ); /// Merges this [StreamPollOptionsDialogThemeData] with the [other]. StreamPollOptionsDialogThemeData merge( @@ -135,26 +127,20 @@ class StreamPollOptionsDialogThemeData with Diagnosticable { StreamPollOptionsDialogThemeData a, StreamPollOptionsDialogThemeData b, double t, - ) => - StreamPollOptionsDialogThemeData( - backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), - appBarElevation: lerpDouble(a.appBarElevation, b.appBarElevation, t), - appBarBackgroundColor: - Color.lerp(a.appBarBackgroundColor, b.appBarBackgroundColor, t), - appBarForegroundColor: - Color.lerp(a.appBarForegroundColor, b.appBarForegroundColor, t), - appBarTitleTextStyle: - TextStyle.lerp(a.appBarTitleTextStyle, b.appBarTitleTextStyle, t), - pollTitleTextStyle: - TextStyle.lerp(a.pollTitleTextStyle, b.pollTitleTextStyle, t), - pollTitleDecoration: - Decoration.lerp(a.pollTitleDecoration, b.pollTitleDecoration, t), - pollOptionsListViewDecoration: Decoration.lerp( - a.pollOptionsListViewDecoration, - b.pollOptionsListViewDecoration, - t, - ), - ); + ) => StreamPollOptionsDialogThemeData( + backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + appBarElevation: lerpDouble(a.appBarElevation, b.appBarElevation, t), + appBarBackgroundColor: Color.lerp(a.appBarBackgroundColor, b.appBarBackgroundColor, t), + appBarForegroundColor: Color.lerp(a.appBarForegroundColor, b.appBarForegroundColor, t), + appBarTitleTextStyle: TextStyle.lerp(a.appBarTitleTextStyle, b.appBarTitleTextStyle, t), + pollTitleTextStyle: TextStyle.lerp(a.pollTitleTextStyle, b.pollTitleTextStyle, t), + pollTitleDecoration: Decoration.lerp(a.pollTitleDecoration, b.pollTitleDecoration, t), + pollOptionsListViewDecoration: Decoration.lerp( + a.pollOptionsListViewDecoration, + b.pollOptionsListViewDecoration, + t, + ), + ); @override bool operator ==(Object other) => diff --git a/packages/stream_chat_flutter/lib/src/theme/poll_results_dialog_theme.dart b/packages/stream_chat_flutter/lib/src/theme/poll_results_dialog_theme.dart index bb7d1550a5..7b2dd580dd 100644 --- a/packages/stream_chat_flutter/lib/src/theme/poll_results_dialog_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/poll_results_dialog_theme.dart @@ -36,19 +36,15 @@ class StreamPollResultsDialogTheme extends InheritedTheme { /// StreamPollCreatorTheme theme = StreamPollCreatorTheme.of(context); /// ``` static StreamPollResultsDialogThemeData of(BuildContext context) { - final pollResultsDialogTheme = context - .dependOnInheritedWidgetOfExactType(); - return pollResultsDialogTheme?.data ?? - StreamChatTheme.of(context).pollResultsDialogTheme; + final pollResultsDialogTheme = context.dependOnInheritedWidgetOfExactType(); + return pollResultsDialogTheme?.data ?? StreamChatTheme.of(context).pollResultsDialogTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamPollResultsDialogTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamPollResultsDialogTheme(data: data, child: child); @override - bool updateShouldNotify(StreamPollResultsDialogTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamPollResultsDialogTheme oldWidget) => data != oldWidget.data; } /// {@template streamPollCreatorThemeData} @@ -138,27 +134,19 @@ class StreamPollResultsDialogThemeData with Diagnosticable { return StreamPollResultsDialogThemeData( backgroundColor: backgroundColor ?? this.backgroundColor, appBarElevation: appBarElevation ?? this.appBarElevation, - appBarBackgroundColor: - appBarBackgroundColor ?? this.appBarBackgroundColor, - appBarForegroundColor: - appBarForegroundColor ?? this.appBarForegroundColor, + appBarBackgroundColor: appBarBackgroundColor ?? this.appBarBackgroundColor, + appBarForegroundColor: appBarForegroundColor ?? this.appBarForegroundColor, appBarTitleTextStyle: appBarTitleTextStyle ?? this.appBarTitleTextStyle, pollTitleTextStyle: pollTitleTextStyle ?? this.pollTitleTextStyle, pollTitleDecoration: pollTitleDecoration ?? this.pollTitleDecoration, - pollOptionsDecoration: - pollOptionsDecoration ?? this.pollOptionsDecoration, - pollOptionsWinnerDecoration: - pollOptionsWinnerDecoration ?? this.pollOptionsWinnerDecoration, + pollOptionsDecoration: pollOptionsDecoration ?? this.pollOptionsDecoration, + pollOptionsWinnerDecoration: pollOptionsWinnerDecoration ?? this.pollOptionsWinnerDecoration, pollOptionsTextStyle: pollOptionsTextStyle ?? this.pollOptionsTextStyle, - pollOptionsWinnerTextStyle: - pollOptionsWinnerTextStyle ?? this.pollOptionsWinnerTextStyle, - pollOptionsVoteCountTextStyle: - pollOptionsVoteCountTextStyle ?? this.pollOptionsVoteCountTextStyle, + pollOptionsWinnerTextStyle: pollOptionsWinnerTextStyle ?? this.pollOptionsWinnerTextStyle, + pollOptionsVoteCountTextStyle: pollOptionsVoteCountTextStyle ?? this.pollOptionsVoteCountTextStyle, pollOptionsWinnerVoteCountTextStyle: - pollOptionsWinnerVoteCountTextStyle ?? - this.pollOptionsWinnerVoteCountTextStyle, - pollOptionsShowAllVotesButtonStyle: pollOptionsShowAllVotesButtonStyle ?? - this.pollOptionsShowAllVotesButtonStyle, + pollOptionsWinnerVoteCountTextStyle ?? this.pollOptionsWinnerVoteCountTextStyle, + pollOptionsShowAllVotesButtonStyle: pollOptionsShowAllVotesButtonStyle ?? this.pollOptionsShowAllVotesButtonStyle, ); } @@ -180,10 +168,8 @@ class StreamPollResultsDialogThemeData with Diagnosticable { pollOptionsTextStyle: other.pollOptionsTextStyle, pollOptionsWinnerTextStyle: other.pollOptionsWinnerTextStyle, pollOptionsVoteCountTextStyle: other.pollOptionsVoteCountTextStyle, - pollOptionsWinnerVoteCountTextStyle: - other.pollOptionsWinnerVoteCountTextStyle, - pollOptionsShowAllVotesButtonStyle: - other.pollOptionsShowAllVotesButtonStyle, + pollOptionsWinnerVoteCountTextStyle: other.pollOptionsWinnerVoteCountTextStyle, + pollOptionsShowAllVotesButtonStyle: other.pollOptionsShowAllVotesButtonStyle, ); } @@ -196,25 +182,18 @@ class StreamPollResultsDialogThemeData with Diagnosticable { return StreamPollResultsDialogThemeData( backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), appBarElevation: lerpDouble(a.appBarElevation, b.appBarElevation, t), - appBarBackgroundColor: - Color.lerp(a.appBarBackgroundColor, b.appBarBackgroundColor, t), - appBarForegroundColor: - Color.lerp(a.appBarForegroundColor, b.appBarForegroundColor, t), - appBarTitleTextStyle: - TextStyle.lerp(a.appBarTitleTextStyle, b.appBarTitleTextStyle, t), - pollTitleTextStyle: - TextStyle.lerp(a.pollTitleTextStyle, b.pollTitleTextStyle, t), - pollTitleDecoration: - Decoration.lerp(a.pollTitleDecoration, b.pollTitleDecoration, t), - pollOptionsDecoration: - Decoration.lerp(a.pollOptionsDecoration, b.pollOptionsDecoration, t), + appBarBackgroundColor: Color.lerp(a.appBarBackgroundColor, b.appBarBackgroundColor, t), + appBarForegroundColor: Color.lerp(a.appBarForegroundColor, b.appBarForegroundColor, t), + appBarTitleTextStyle: TextStyle.lerp(a.appBarTitleTextStyle, b.appBarTitleTextStyle, t), + pollTitleTextStyle: TextStyle.lerp(a.pollTitleTextStyle, b.pollTitleTextStyle, t), + pollTitleDecoration: Decoration.lerp(a.pollTitleDecoration, b.pollTitleDecoration, t), + pollOptionsDecoration: Decoration.lerp(a.pollOptionsDecoration, b.pollOptionsDecoration, t), pollOptionsWinnerDecoration: Decoration.lerp( a.pollOptionsWinnerDecoration, b.pollOptionsWinnerDecoration, t, ), - pollOptionsTextStyle: - TextStyle.lerp(a.pollOptionsTextStyle, b.pollOptionsTextStyle, t), + pollOptionsTextStyle: TextStyle.lerp(a.pollOptionsTextStyle, b.pollOptionsTextStyle, t), pollOptionsWinnerTextStyle: TextStyle.lerp( a.pollOptionsWinnerTextStyle, b.pollOptionsWinnerTextStyle, @@ -253,12 +232,9 @@ class StreamPollResultsDialogThemeData with Diagnosticable { other.pollOptionsWinnerDecoration == pollOptionsWinnerDecoration && other.pollOptionsTextStyle == pollOptionsTextStyle && other.pollOptionsWinnerTextStyle == pollOptionsWinnerTextStyle && - other.pollOptionsVoteCountTextStyle == - pollOptionsVoteCountTextStyle && - other.pollOptionsWinnerVoteCountTextStyle == - pollOptionsWinnerVoteCountTextStyle && - other.pollOptionsShowAllVotesButtonStyle == - pollOptionsShowAllVotesButtonStyle; + other.pollOptionsVoteCountTextStyle == pollOptionsVoteCountTextStyle && + other.pollOptionsWinnerVoteCountTextStyle == pollOptionsWinnerVoteCountTextStyle && + other.pollOptionsShowAllVotesButtonStyle == pollOptionsShowAllVotesButtonStyle; @override int get hashCode => diff --git a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart index 489b4a03c5..4cb81111f6 100644 --- a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart @@ -23,8 +23,7 @@ class StreamChatTheme extends InheritedWidget { /// Use this method to get the current [StreamChatThemeData] instance static StreamChatThemeData of(BuildContext context) { - final streamChatTheme = - context.dependOnInheritedWidgetOfExactType(); + final streamChatTheme = context.dependOnInheritedWidgetOfExactType(); assert( streamChatTheme != null, @@ -107,12 +106,10 @@ class StreamChatThemeData { } /// Theme initialized with light - factory StreamChatThemeData.light() => - StreamChatThemeData(brightness: Brightness.light); + factory StreamChatThemeData.light() => StreamChatThemeData(brightness: Brightness.light); /// Theme initialized with dark - factory StreamChatThemeData.dark() => - StreamChatThemeData(brightness: Brightness.dark); + factory StreamChatThemeData.dark() => StreamChatThemeData(brightness: Brightness.dark); /// Raw theme initialization const StreamChatThemeData.raw({ @@ -226,15 +223,13 @@ class StreamChatThemeData { ), channelHeaderTheme: channelHeaderTheme, ownMessageTheme: StreamMessageThemeData( - messageAuthorStyle: - textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), + messageAuthorStyle: textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), messageTextStyle: textTheme.body, messageDeletedStyle: textTheme.body.copyWith( color: colorTheme.textLowEmphasis, fontStyle: FontStyle.italic, ), - createdAtStyle: - textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), + createdAtStyle: textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), repliesStyle: textTheme.footnoteBold.copyWith(color: accentColor), messageBackgroundColor: colorTheme.inputBg, messageBorderColor: colorTheme.borders, @@ -265,10 +260,8 @@ class StreamChatThemeData { color: colorTheme.textLowEmphasis, fontStyle: FontStyle.italic, ), - createdAtStyle: - textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), - messageAuthorStyle: - textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), + createdAtStyle: textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), + messageAuthorStyle: textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), repliesStyle: textTheme.footnoteBold.copyWith(color: accentColor), messageLinksStyle: TextStyle(color: accentColor), messageBackgroundColor: colorTheme.barsBg, @@ -522,8 +515,7 @@ class StreamChatThemeData { threadUnreadMessageCountStyle: textTheme.footnoteBold.copyWith( color: Colors.white, ), - threadUnreadMessageCountBackgroundColor: - channelPreviewTheme.unreadCounterColor, + threadUnreadMessageCountBackgroundColor: channelPreviewTheme.unreadCounterColor, threadChannelNameStyle: textTheme.bodyBold.copyWith( color: colorTheme.textHighEmphasis, ), @@ -705,47 +697,37 @@ class StreamChatThemeData { StreamAudioWaveformThemeData? audioWaveformTheme, StreamAudioWaveformSliderThemeData? audioWaveformSliderTheme, StreamVoiceRecordingAttachmentThemeData? voiceRecordingAttachmentTheme, - }) => - StreamChatThemeData.raw( - channelListHeaderTheme: - this.channelListHeaderTheme.merge(channelListHeaderTheme), - textTheme: this.textTheme.merge(textTheme), - colorTheme: this.colorTheme.merge(colorTheme), - primaryIconTheme: this.primaryIconTheme.merge(primaryIconTheme), - channelPreviewTheme: - this.channelPreviewTheme.merge(channelPreviewTheme), - channelHeaderTheme: this.channelHeaderTheme.merge(channelHeaderTheme), - ownMessageTheme: this.ownMessageTheme.merge(ownMessageTheme), - otherMessageTheme: this.otherMessageTheme.merge(otherMessageTheme), - messageInputTheme: this.messageInputTheme.merge(messageInputTheme), - galleryHeaderTheme: galleryHeaderTheme ?? this.galleryHeaderTheme, - galleryFooterTheme: galleryFooterTheme ?? this.galleryFooterTheme, - messageListViewTheme: messageListViewTheme ?? this.messageListViewTheme, - pollCreatorTheme: pollCreatorTheme ?? this.pollCreatorTheme, - pollInteractorTheme: pollInteractorTheme ?? this.pollInteractorTheme, - pollResultsDialogTheme: - pollResultsDialogTheme ?? this.pollResultsDialogTheme, - pollOptionsDialogTheme: - pollOptionsDialogTheme ?? this.pollOptionsDialogTheme, - pollCommentsDialogTheme: - pollCommentsDialogTheme ?? this.pollCommentsDialogTheme, - pollOptionVotesDialogTheme: - pollOptionVotesDialogTheme ?? this.pollOptionVotesDialogTheme, - threadListTileTheme: threadListTileTheme ?? this.threadListTileTheme, - draftListTileTheme: draftListTileTheme ?? this.draftListTileTheme, - audioWaveformTheme: audioWaveformTheme ?? this.audioWaveformTheme, - audioWaveformSliderTheme: - audioWaveformSliderTheme ?? this.audioWaveformSliderTheme, - voiceRecordingAttachmentTheme: - voiceRecordingAttachmentTheme ?? this.voiceRecordingAttachmentTheme, - ); + }) => StreamChatThemeData.raw( + channelListHeaderTheme: this.channelListHeaderTheme.merge(channelListHeaderTheme), + textTheme: this.textTheme.merge(textTheme), + colorTheme: this.colorTheme.merge(colorTheme), + primaryIconTheme: this.primaryIconTheme.merge(primaryIconTheme), + channelPreviewTheme: this.channelPreviewTheme.merge(channelPreviewTheme), + channelHeaderTheme: this.channelHeaderTheme.merge(channelHeaderTheme), + ownMessageTheme: this.ownMessageTheme.merge(ownMessageTheme), + otherMessageTheme: this.otherMessageTheme.merge(otherMessageTheme), + messageInputTheme: this.messageInputTheme.merge(messageInputTheme), + galleryHeaderTheme: galleryHeaderTheme ?? this.galleryHeaderTheme, + galleryFooterTheme: galleryFooterTheme ?? this.galleryFooterTheme, + messageListViewTheme: messageListViewTheme ?? this.messageListViewTheme, + pollCreatorTheme: pollCreatorTheme ?? this.pollCreatorTheme, + pollInteractorTheme: pollInteractorTheme ?? this.pollInteractorTheme, + pollResultsDialogTheme: pollResultsDialogTheme ?? this.pollResultsDialogTheme, + pollOptionsDialogTheme: pollOptionsDialogTheme ?? this.pollOptionsDialogTheme, + pollCommentsDialogTheme: pollCommentsDialogTheme ?? this.pollCommentsDialogTheme, + pollOptionVotesDialogTheme: pollOptionVotesDialogTheme ?? this.pollOptionVotesDialogTheme, + threadListTileTheme: threadListTileTheme ?? this.threadListTileTheme, + draftListTileTheme: draftListTileTheme ?? this.draftListTileTheme, + audioWaveformTheme: audioWaveformTheme ?? this.audioWaveformTheme, + audioWaveformSliderTheme: audioWaveformSliderTheme ?? this.audioWaveformSliderTheme, + voiceRecordingAttachmentTheme: voiceRecordingAttachmentTheme ?? this.voiceRecordingAttachmentTheme, + ); /// Merge themes StreamChatThemeData merge(StreamChatThemeData? other) { if (other == null) return this; return copyWith( - channelListHeaderTheme: - channelListHeaderTheme.merge(other.channelListHeaderTheme), + channelListHeaderTheme: channelListHeaderTheme.merge(other.channelListHeaderTheme), textTheme: textTheme.merge(other.textTheme), colorTheme: colorTheme.merge(other.colorTheme), primaryIconTheme: other.primaryIconTheme, @@ -756,25 +738,18 @@ class StreamChatThemeData { messageInputTheme: messageInputTheme.merge(other.messageInputTheme), galleryHeaderTheme: galleryHeaderTheme.merge(other.galleryHeaderTheme), galleryFooterTheme: galleryFooterTheme.merge(other.galleryFooterTheme), - messageListViewTheme: - messageListViewTheme.merge(other.messageListViewTheme), + messageListViewTheme: messageListViewTheme.merge(other.messageListViewTheme), pollCreatorTheme: pollCreatorTheme.merge(other.pollCreatorTheme), pollInteractorTheme: pollInteractorTheme.merge(other.pollInteractorTheme), - pollResultsDialogTheme: - pollResultsDialogTheme.merge(other.pollResultsDialogTheme), - pollOptionsDialogTheme: - pollOptionsDialogTheme.merge(other.pollOptionsDialogTheme), - pollCommentsDialogTheme: - pollCommentsDialogTheme.merge(other.pollCommentsDialogTheme), - pollOptionVotesDialogTheme: - pollOptionVotesDialogTheme.merge(other.pollOptionVotesDialogTheme), + pollResultsDialogTheme: pollResultsDialogTheme.merge(other.pollResultsDialogTheme), + pollOptionsDialogTheme: pollOptionsDialogTheme.merge(other.pollOptionsDialogTheme), + pollCommentsDialogTheme: pollCommentsDialogTheme.merge(other.pollCommentsDialogTheme), + pollOptionVotesDialogTheme: pollOptionVotesDialogTheme.merge(other.pollOptionVotesDialogTheme), threadListTileTheme: threadListTileTheme.merge(other.threadListTileTheme), draftListTileTheme: draftListTileTheme.merge(other.draftListTileTheme), audioWaveformTheme: audioWaveformTheme.merge(other.audioWaveformTheme), - audioWaveformSliderTheme: - audioWaveformSliderTheme.merge(other.audioWaveformSliderTheme), - voiceRecordingAttachmentTheme: voiceRecordingAttachmentTheme - .merge(other.voiceRecordingAttachmentTheme), + audioWaveformSliderTheme: audioWaveformSliderTheme.merge(other.audioWaveformSliderTheme), + voiceRecordingAttachmentTheme: voiceRecordingAttachmentTheme.merge(other.voiceRecordingAttachmentTheme), ); } } diff --git a/packages/stream_chat_flutter/lib/src/theme/text_theme.dart b/packages/stream_chat_flutter/lib/src/theme/text_theme.dart index 62a30d1245..113e67f8f6 100644 --- a/packages/stream_chat_flutter/lib/src/theme/text_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/text_theme.dart @@ -140,28 +140,27 @@ class StreamTextTheme { TextStyle? footnoteBold, TextStyle? footnote, TextStyle? captionBold, - }) => - brightness == Brightness.light - ? StreamTextTheme.light( - body: body ?? this.body, - title: title ?? this.title, - headlineBold: headlineBold ?? this.headlineBold, - headline: headline ?? this.headline, - bodyBold: bodyBold ?? this.bodyBold, - footnoteBold: footnoteBold ?? this.footnoteBold, - footnote: footnote ?? this.footnote, - captionBold: captionBold ?? this.captionBold, - ) - : StreamTextTheme.dark( - body: body ?? this.body, - title: title ?? this.title, - headlineBold: headlineBold ?? this.headlineBold, - headline: headline ?? this.headline, - bodyBold: bodyBold ?? this.bodyBold, - footnoteBold: footnoteBold ?? this.footnoteBold, - footnote: footnote ?? this.footnote, - captionBold: captionBold ?? this.captionBold, - ); + }) => brightness == Brightness.light + ? StreamTextTheme.light( + body: body ?? this.body, + title: title ?? this.title, + headlineBold: headlineBold ?? this.headlineBold, + headline: headline ?? this.headline, + bodyBold: bodyBold ?? this.bodyBold, + footnoteBold: footnoteBold ?? this.footnoteBold, + footnote: footnote ?? this.footnote, + captionBold: captionBold ?? this.captionBold, + ) + : StreamTextTheme.dark( + body: body ?? this.body, + title: title ?? this.title, + headlineBold: headlineBold ?? this.headlineBold, + headline: headline ?? this.headline, + bodyBold: bodyBold ?? this.bodyBold, + footnoteBold: footnoteBold ?? this.footnoteBold, + footnote: footnote ?? this.footnote, + captionBold: captionBold ?? this.captionBold, + ); /// Merge text theme StreamTextTheme merge(StreamTextTheme? other) { diff --git a/packages/stream_chat_flutter/lib/src/theme/thread_list_tile_theme.dart b/packages/stream_chat_flutter/lib/src/theme/thread_list_tile_theme.dart index 6becfc2a27..aada8337d5 100644 --- a/packages/stream_chat_flutter/lib/src/theme/thread_list_tile_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/thread_list_tile_theme.dart @@ -29,19 +29,15 @@ class StreamThreadListTileTheme extends InheritedTheme { /// If there is no enclosing [StreamThreadListTileTheme] widget, then /// [StreamChatThemeData.pollOptionVotesDialogTheme] is used. static StreamThreadListTileThemeData of(BuildContext context) { - final threadListTileTheme = - context.dependOnInheritedWidgetOfExactType(); - return threadListTileTheme?.data ?? - StreamChatTheme.of(context).threadListTileTheme; + final threadListTileTheme = context.dependOnInheritedWidgetOfExactType(); + return threadListTileTheme?.data ?? StreamChatTheme.of(context).threadListTileTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamThreadListTileTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamThreadListTileTheme(data: data, child: child); @override - bool updateShouldNotify(StreamThreadListTileTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamThreadListTileTheme oldWidget) => data != oldWidget.data; } /// {@template streamThreadListTileThemeData} @@ -124,29 +120,20 @@ class StreamThreadListTileThemeData with Diagnosticable { DateFormatter? threadLatestReplyTimestampFormatter, TextStyle? threadUnreadMessageCountStyle, Color? threadUnreadMessageCountBackgroundColor, - }) => - StreamThreadListTileThemeData( - padding: padding ?? this.padding, - backgroundColor: backgroundColor ?? this.backgroundColor, - threadChannelNameStyle: - threadChannelNameStyle ?? this.threadChannelNameStyle, - threadReplyToMessageStyle: - threadReplyToMessageStyle ?? this.threadReplyToMessageStyle, - threadLatestReplyUsernameStyle: threadLatestReplyUsernameStyle ?? - this.threadLatestReplyUsernameStyle, - threadLatestReplyMessageStyle: - threadLatestReplyMessageStyle ?? this.threadLatestReplyMessageStyle, - threadLatestReplyTimestampStyle: threadLatestReplyTimestampStyle ?? - this.threadLatestReplyTimestampStyle, - threadLatestReplyTimestampFormatter: - threadLatestReplyTimestampFormatter ?? - this.threadLatestReplyTimestampFormatter, - threadUnreadMessageCountStyle: - threadUnreadMessageCountStyle ?? this.threadUnreadMessageCountStyle, - threadUnreadMessageCountBackgroundColor: - threadUnreadMessageCountBackgroundColor ?? - this.threadUnreadMessageCountBackgroundColor, - ); + }) => StreamThreadListTileThemeData( + padding: padding ?? this.padding, + backgroundColor: backgroundColor ?? this.backgroundColor, + threadChannelNameStyle: threadChannelNameStyle ?? this.threadChannelNameStyle, + threadReplyToMessageStyle: threadReplyToMessageStyle ?? this.threadReplyToMessageStyle, + threadLatestReplyUsernameStyle: threadLatestReplyUsernameStyle ?? this.threadLatestReplyUsernameStyle, + threadLatestReplyMessageStyle: threadLatestReplyMessageStyle ?? this.threadLatestReplyMessageStyle, + threadLatestReplyTimestampStyle: threadLatestReplyTimestampStyle ?? this.threadLatestReplyTimestampStyle, + threadLatestReplyTimestampFormatter: + threadLatestReplyTimestampFormatter ?? this.threadLatestReplyTimestampFormatter, + threadUnreadMessageCountStyle: threadUnreadMessageCountStyle ?? this.threadUnreadMessageCountStyle, + threadUnreadMessageCountBackgroundColor: + threadUnreadMessageCountBackgroundColor ?? this.threadUnreadMessageCountBackgroundColor, + ); /// Merges this [StreamThreadListTileThemeData] with the [other]. StreamThreadListTileThemeData merge( @@ -161,11 +148,9 @@ class StreamThreadListTileThemeData with Diagnosticable { threadLatestReplyUsernameStyle: other.threadLatestReplyUsernameStyle, threadLatestReplyMessageStyle: other.threadLatestReplyMessageStyle, threadLatestReplyTimestampStyle: other.threadLatestReplyTimestampStyle, - threadLatestReplyTimestampFormatter: - other.threadLatestReplyTimestampFormatter, + threadLatestReplyTimestampFormatter: other.threadLatestReplyTimestampFormatter, threadUnreadMessageCountStyle: other.threadUnreadMessageCountStyle, - threadUnreadMessageCountBackgroundColor: - other.threadUnreadMessageCountBackgroundColor, + threadUnreadMessageCountBackgroundColor: other.threadUnreadMessageCountBackgroundColor, ); } @@ -174,49 +159,48 @@ class StreamThreadListTileThemeData with Diagnosticable { StreamThreadListTileThemeData? a, StreamThreadListTileThemeData? b, double t, - ) => - StreamThreadListTileThemeData( - padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t), - backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t), - threadChannelNameStyle: TextStyle.lerp( - a?.threadChannelNameStyle, - b?.threadChannelNameStyle, - t, - ), - threadReplyToMessageStyle: TextStyle.lerp( - a?.threadReplyToMessageStyle, - b?.threadReplyToMessageStyle, - t, - ), - threadLatestReplyUsernameStyle: TextStyle.lerp( - a?.threadLatestReplyUsernameStyle, - b?.threadLatestReplyUsernameStyle, - t, - ), - threadLatestReplyMessageStyle: TextStyle.lerp( - a?.threadLatestReplyMessageStyle, - b?.threadLatestReplyMessageStyle, - t, - ), - threadLatestReplyTimestampStyle: TextStyle.lerp( - a?.threadLatestReplyTimestampStyle, - b?.threadLatestReplyTimestampStyle, - t, - ), - threadLatestReplyTimestampFormatter: t < 0.5 - ? a?.threadLatestReplyTimestampFormatter - : b?.threadLatestReplyTimestampFormatter, - threadUnreadMessageCountStyle: TextStyle.lerp( - a?.threadUnreadMessageCountStyle, - b?.threadUnreadMessageCountStyle, - t, - ), - threadUnreadMessageCountBackgroundColor: Color.lerp( - a?.threadUnreadMessageCountBackgroundColor, - b?.threadUnreadMessageCountBackgroundColor, - t, - ), - ); + ) => StreamThreadListTileThemeData( + padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t), + backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t), + threadChannelNameStyle: TextStyle.lerp( + a?.threadChannelNameStyle, + b?.threadChannelNameStyle, + t, + ), + threadReplyToMessageStyle: TextStyle.lerp( + a?.threadReplyToMessageStyle, + b?.threadReplyToMessageStyle, + t, + ), + threadLatestReplyUsernameStyle: TextStyle.lerp( + a?.threadLatestReplyUsernameStyle, + b?.threadLatestReplyUsernameStyle, + t, + ), + threadLatestReplyMessageStyle: TextStyle.lerp( + a?.threadLatestReplyMessageStyle, + b?.threadLatestReplyMessageStyle, + t, + ), + threadLatestReplyTimestampStyle: TextStyle.lerp( + a?.threadLatestReplyTimestampStyle, + b?.threadLatestReplyTimestampStyle, + t, + ), + threadLatestReplyTimestampFormatter: t < 0.5 + ? a?.threadLatestReplyTimestampFormatter + : b?.threadLatestReplyTimestampFormatter, + threadUnreadMessageCountStyle: TextStyle.lerp( + a?.threadUnreadMessageCountStyle, + b?.threadUnreadMessageCountStyle, + t, + ), + threadUnreadMessageCountBackgroundColor: Color.lerp( + a?.threadUnreadMessageCountBackgroundColor, + b?.threadUnreadMessageCountBackgroundColor, + t, + ), + ); @override bool operator ==(Object other) => @@ -226,18 +210,12 @@ class StreamThreadListTileThemeData with Diagnosticable { other.backgroundColor == backgroundColor && other.threadChannelNameStyle == threadChannelNameStyle && other.threadReplyToMessageStyle == threadReplyToMessageStyle && - other.threadLatestReplyUsernameStyle == - threadLatestReplyUsernameStyle && - other.threadLatestReplyMessageStyle == - threadLatestReplyMessageStyle && - other.threadLatestReplyTimestampStyle == - threadLatestReplyTimestampStyle && - other.threadLatestReplyTimestampFormatter == - threadLatestReplyTimestampFormatter && - other.threadUnreadMessageCountStyle == - threadUnreadMessageCountStyle && - other.threadUnreadMessageCountBackgroundColor == - threadUnreadMessageCountBackgroundColor; + other.threadLatestReplyUsernameStyle == threadLatestReplyUsernameStyle && + other.threadLatestReplyMessageStyle == threadLatestReplyMessageStyle && + other.threadLatestReplyTimestampStyle == threadLatestReplyTimestampStyle && + other.threadLatestReplyTimestampFormatter == threadLatestReplyTimestampFormatter && + other.threadUnreadMessageCountStyle == threadUnreadMessageCountStyle && + other.threadUnreadMessageCountBackgroundColor == threadUnreadMessageCountBackgroundColor; @override int get hashCode => diff --git a/packages/stream_chat_flutter/lib/src/theme/voice_recording_attachment_theme.dart b/packages/stream_chat_flutter/lib/src/theme/voice_recording_attachment_theme.dart index 6103156c04..d7cfbf2949 100644 --- a/packages/stream_chat_flutter/lib/src/theme/voice_recording_attachment_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/voice_recording_attachment_theme.dart @@ -36,19 +36,15 @@ class StreamVoiceRecordingAttachmentTheme extends InheritedTheme { /// StreamVoiceRecordingAttachmentTheme.of(context); /// ``` static StreamVoiceRecordingAttachmentThemeData of(BuildContext context) { - final voiceRecordingTheme = context.dependOnInheritedWidgetOfExactType< - StreamVoiceRecordingAttachmentTheme>(); - return voiceRecordingTheme?.data ?? - StreamChatTheme.of(context).voiceRecordingAttachmentTheme; + final voiceRecordingTheme = context.dependOnInheritedWidgetOfExactType(); + return voiceRecordingTheme?.data ?? StreamChatTheme.of(context).voiceRecordingAttachmentTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamVoiceRecordingAttachmentTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamVoiceRecordingAttachmentTheme(data: data, child: child); @override - bool updateShouldNotify(StreamVoiceRecordingAttachmentTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamVoiceRecordingAttachmentTheme oldWidget) => data != oldWidget.data; } /// {@template streamVoiceRecordingAttachmentThemeData} @@ -110,21 +106,17 @@ class StreamVoiceRecordingAttachmentThemeData with Diagnosticable { TextStyle? durationTextStyle, ButtonStyle? speedControlButtonStyle, StreamAudioWaveformSliderThemeData? audioWaveformSliderTheme, - }) => - StreamVoiceRecordingAttachmentThemeData( - backgroundColor: backgroundColor ?? this.backgroundColor, - playIcon: playIcon ?? this.playIcon, - pauseIcon: pauseIcon ?? this.pauseIcon, - loadingIndicator: loadingIndicator ?? this.loadingIndicator, - audioControlButtonStyle: - audioControlButtonStyle ?? this.audioControlButtonStyle, - titleTextStyle: titleTextStyle ?? this.titleTextStyle, - durationTextStyle: durationTextStyle ?? this.durationTextStyle, - speedControlButtonStyle: - speedControlButtonStyle ?? this.speedControlButtonStyle, - audioWaveformSliderTheme: - audioWaveformSliderTheme ?? this.audioWaveformSliderTheme, - ); + }) => StreamVoiceRecordingAttachmentThemeData( + backgroundColor: backgroundColor ?? this.backgroundColor, + playIcon: playIcon ?? this.playIcon, + pauseIcon: pauseIcon ?? this.pauseIcon, + loadingIndicator: loadingIndicator ?? this.loadingIndicator, + audioControlButtonStyle: audioControlButtonStyle ?? this.audioControlButtonStyle, + titleTextStyle: titleTextStyle ?? this.titleTextStyle, + durationTextStyle: durationTextStyle ?? this.durationTextStyle, + speedControlButtonStyle: speedControlButtonStyle ?? this.speedControlButtonStyle, + audioWaveformSliderTheme: audioWaveformSliderTheme ?? this.audioWaveformSliderTheme, + ); /// Merges this [StreamVoiceRecordingAttachmentThemeData] with the [other]. StreamVoiceRecordingAttachmentThemeData merge( @@ -158,15 +150,15 @@ class StreamVoiceRecordingAttachmentThemeData with Diagnosticable { playIcon: t < 0.5 ? a.playIcon : b.playIcon, pauseIcon: t < 0.5 ? a.pauseIcon : b.pauseIcon, loadingIndicator: t < 0.5 ? a.loadingIndicator : b.loadingIndicator, - audioControlButtonStyle: ButtonStyle.lerp( - a.audioControlButtonStyle, b.audioControlButtonStyle, t), + audioControlButtonStyle: ButtonStyle.lerp(a.audioControlButtonStyle, b.audioControlButtonStyle, t), titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t), - durationTextStyle: - TextStyle.lerp(a.durationTextStyle, b.durationTextStyle, t), - speedControlButtonStyle: ButtonStyle.lerp( - a.speedControlButtonStyle, b.speedControlButtonStyle, t), + durationTextStyle: TextStyle.lerp(a.durationTextStyle, b.durationTextStyle, t), + speedControlButtonStyle: ButtonStyle.lerp(a.speedControlButtonStyle, b.speedControlButtonStyle, t), audioWaveformSliderTheme: StreamAudioWaveformSliderThemeData.lerp( - a.audioWaveformSliderTheme!, b.audioWaveformSliderTheme!, t), + a.audioWaveformSliderTheme!, + b.audioWaveformSliderTheme!, + t, + ), ); } diff --git a/packages/stream_chat_flutter/lib/src/utils/date_formatter.dart b/packages/stream_chat_flutter/lib/src/utils/date_formatter.dart index 1b41cf6d3f..7cad9fb564 100644 --- a/packages/stream_chat_flutter/lib/src/utils/date_formatter.dart +++ b/packages/stream_chat_flutter/lib/src/utils/date_formatter.dart @@ -3,10 +3,11 @@ import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; /// Represents a function type that formats a date. -typedef DateFormatter = String Function( - BuildContext context, - DateTime date, -); +typedef DateFormatter = + String Function( + BuildContext context, + DateTime date, + ); /// Formats the given [date] as a String. String formatDate(BuildContext context, DateTime date) { diff --git a/packages/stream_chat_flutter/lib/src/utils/device_segmentation.dart b/packages/stream_chat_flutter/lib/src/utils/device_segmentation.dart index bd65820c5e..334e4cfe30 100644 --- a/packages/stream_chat_flutter/lib/src/utils/device_segmentation.dart +++ b/packages/stream_chat_flutter/lib/src/utils/device_segmentation.dart @@ -7,16 +7,12 @@ bool get isWeb => CurrentPlatform.isWeb; bool get isMobileDevice => CurrentPlatform.isIos || CurrentPlatform.isAndroid; /// Returns true if the app is running in a desktop device. -bool get isDesktopDevice => - CurrentPlatform.isMacOS || - CurrentPlatform.isWindows || - CurrentPlatform.isLinux; +bool get isDesktopDevice => CurrentPlatform.isMacOS || CurrentPlatform.isWindows || CurrentPlatform.isLinux; /// Returns true if the app is running on windows or linux platform. bool get isDesktopVideoPlayerSupported => // Dart VLC is not supported on MacOS. - !CurrentPlatform.isMacOS && - (CurrentPlatform.isWindows || CurrentPlatform.isLinux); + !CurrentPlatform.isMacOS && (CurrentPlatform.isWindows || CurrentPlatform.isLinux); /// Returns true if the app is running in a mobile or web. bool get isMobileDeviceOrWeb => isWeb || isMobileDevice; diff --git a/packages/stream_chat_flutter/lib/src/utils/extensions.dart b/packages/stream_chat_flutter/lib/src/utils/extensions.dart index 4fbb58a52a..9f5ddd94ea 100644 --- a/packages/stream_chat_flutter/lib/src/utils/extensions.dart +++ b/packages/stream_chat_flutter/lib/src/utils/extensions.dart @@ -23,8 +23,7 @@ extension IntExtension on int { if (this <= 0) return '0 B'; const suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; final i = (log(this) / log(_byteUnitConversionFactor)).floor(); - final numberValue = - (this / pow(_byteUnitConversionFactor, i)).toStringAsFixed(2); + final numberValue = (this / pow(_byteUnitConversionFactor, i)).toStringAsFixed(2); final suffix = suffixes[i]; return '$numberValue $suffix'; } @@ -62,8 +61,7 @@ extension StringExtension on String { /// Returns the biggest line of a text. String biggestLine() { if (contains('\n')) { - return split('\n') - .reduce((curr, next) => curr.length > next.length ? curr : next); + return split('\n').reduce((curr, next) => curr.length > next.length ? curr : next); } else { return this; } @@ -149,9 +147,9 @@ extension StringExtension on String { extension IterableExtension on Iterable { /// Insert any item inBetween the list items List insertBetween(T item) => expand((e) sync* { - yield item; - yield e; - }).skip(1).toList(growable: false); + yield item; + yield e; + }).skip(1).toList(growable: false); } /// Useful extension for [PlatformFile] @@ -263,8 +261,7 @@ extension InputDecorationX on InputDecoration { suffixIconConstraints: other.suffixIconConstraints, counter: other.counter, counterText: other.counterText, - counterStyle: - counterStyle?.merge(other.counterStyle) ?? other.counterStyle, + counterStyle: counterStyle?.merge(other.counterStyle) ?? other.counterStyle, filled: other.filled, fillColor: other.fillColor, focusColor: other.focusColor, @@ -291,8 +288,7 @@ extension BuildContextX on BuildContext { /// Retrieves current translations according to locale /// Defaults to [DefaultTranslations] - Translations get translations => - StreamChatLocalizations.of(this) ?? DefaultTranslations.instance; + Translations get translations => StreamChatLocalizations.of(this) ?? DefaultTranslations.instance; } /// Extension on [BorderRadius] @@ -384,8 +380,7 @@ extension UserListX on List { final entries = matchingUsers.entries.toList(growable: false) ..sort((prev, curr) { bool containsQuery(User user) => - normalize(user.id).contains(normalizedQuery) || - normalize(user.name).contains(normalizedQuery); + normalize(user.id).contains(normalizedQuery) || normalize(user.name).contains(normalizedQuery); final containsInPrev = containsQuery(prev.key); final containsInCurr = containsQuery(curr.key); @@ -425,8 +420,7 @@ extension MessageX on Message { var messageTextLength = min(text?.biggestLine().length ?? 0, 65); if (quotedMessage != null) { - var quotedMessageLength = - (min(quotedMessage!.text?.biggestLine().length ?? 0, 65)) + 8; + var quotedMessageLength = (min(quotedMessage!.text?.biggestLine().length ?? 0, 65)) + 8; if (quotedMessage!.attachments.isNotEmpty) { quotedMessageLength += 8; @@ -448,8 +442,7 @@ extension MessageX on Message { } /// It returns the message with the translated text if available locally - Message translate(String language) => - copyWith(text: i18n?['${language}_text'] ?? text); + Message translate(String language) => copyWith(text: i18n?['${language}_text'] ?? text); /// It returns the message replacing the mentioned user names with /// the respective user ids @@ -510,9 +503,9 @@ extension AttachmentPickerTypeX on AttachmentPickerType { AudiosPickerType() => FileType.audio, FilesPickerType() => FileType.any, _ => throw Exception( - 'Unsupported AttachmentPickerType: $this. ' - 'Only Images, Videos, Audios and Files are supported.', - ), + 'Unsupported AttachmentPickerType: $this. ' + 'Only Images, Videos, Audios and Files are supported.', + ), }; } } @@ -605,16 +598,11 @@ extension ChannelModelX on ChannelModel { // Otherwise, we return the names of the first `maxMembers` members sorted // alphabetically, followed by the number of remaining members if there are // more than `maxMembers` members. - final memberNames = otherMembers - .map((it) => it.user?.name) - .whereType() - .take(maxMembers) - .sorted(); + final memberNames = otherMembers.map((it) => it.user?.name).whereType().take(maxMembers).sorted(); return switch (otherMembers.length <= maxMembers) { true => memberNames.join(', '), - false => - '${memberNames.join(', ')} + ${otherMembers.length - maxMembers}', + false => '${memberNames.join(', ')} + ${otherMembers.length - maxMembers}', }; } } @@ -654,25 +642,25 @@ extension AttachmentPlaylistExtension on Iterable { ...map((it) { final uri = switch (it.uploadState) { Preparing() || InProgress() || Failed() => () { - if (CurrentPlatform.isWeb) { - final bytes = it.file?.bytes; - final mimeType = it.file?.mediaType?.mimeType; - if (bytes == null || mimeType == null) return null; + if (CurrentPlatform.isWeb) { + final bytes = it.file?.bytes; + final mimeType = it.file?.mediaType?.mimeType; + if (bytes == null || mimeType == null) return null; - return Uri.dataFromBytes(bytes, mimeType: mimeType); - } + return Uri.dataFromBytes(bytes, mimeType: mimeType); + } - final path = it.file?.path; - if (path == null) return null; + final path = it.file?.path; + if (path == null) return null; - return Uri.file(path, windows: CurrentPlatform.isWindows); - }(), + return Uri.file(path, windows: CurrentPlatform.isWindows); + }(), Success() => () { - final url = it.assetUrl; - if (url == null) return null; + final url = it.assetUrl; + if (url == null) return null; - return Uri.tryParse(url); - }(), + return Uri.tryParse(url); + }(), }; if (uri == null) return null; diff --git a/packages/stream_chat_flutter/lib/src/utils/helpers.dart b/packages/stream_chat_flutter/lib/src/utils/helpers.dart index 1e732bc40d..3f6226489f 100644 --- a/packages/stream_chat_flutter/lib/src/utils/helpers.dart +++ b/packages/stream_chat_flutter/lib/src/utils/helpers.dart @@ -113,10 +113,9 @@ Future showConfirmationBottomSheet( onPressed: () => Navigator.of(context).pop(false), style: TextButton.styleFrom( textStyle: chatThemeData.textTheme.bodyBold, - foregroundColor: - chatThemeData.colorTheme.textHighEmphasis - // ignore: deprecated_member_use - .withOpacity(0.5), + foregroundColor: chatThemeData.colorTheme.textHighEmphasis + // ignore: deprecated_member_use + .withOpacity(0.5), ), child: Text(cancelText), ), @@ -155,8 +154,7 @@ Future showInfoBottomSheet( }) { final chatThemeData = StreamChatTheme.of(context); return showModalBottomSheet( - backgroundColor: - theme?.colorTheme.barsBg ?? chatThemeData.colorTheme.barsBg, + backgroundColor: theme?.colorTheme.barsBg ?? chatThemeData.colorTheme.barsBg, context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -177,8 +175,7 @@ Future showInfoBottomSheet( ), Text( title, - style: theme?.textTheme.headlineBold ?? - chatThemeData.textTheme.headlineBold, + style: theme?.textTheme.headlineBold ?? chatThemeData.textTheme.headlineBold, ), const SizedBox( height: 7, @@ -188,10 +185,9 @@ Future showInfoBottomSheet( height: 36, ), Container( - // ignore: deprecated_member_use - color: theme?.colorTheme.textHighEmphasis.withOpacity(0.08) ?? - // ignore: deprecated_member_use - chatThemeData.colorTheme.textHighEmphasis.withOpacity(0.08), + color: + theme?.colorTheme.textHighEmphasis.withValues(alpha: 0.08) ?? + chatThemeData.colorTheme.textHighEmphasis.withValues(alpha: 0.08), height: 1, ), Center( @@ -203,8 +199,7 @@ Future showInfoBottomSheet( okText, style: TextStyle( // ignore: deprecated_member_use - color: theme?.colorTheme.textHighEmphasis.withOpacity(0.5) ?? - chatThemeData.colorTheme.accentPrimary, + color: theme?.colorTheme.textHighEmphasis.withOpacity(0.5) ?? chatThemeData.colorTheme.accentPrimary, fontWeight: FontWeight.w400, ), ), @@ -217,8 +212,7 @@ Future showInfoBottomSheet( } /// Get random png with initials -String getRandomPicUrl(User user) => - 'https://getstream.io/random_png/?id=${user.id}&name=${user.name}'; +String getRandomPicUrl(User user) => 'https://getstream.io/random_png/?id=${user.id}&name=${user.name}'; /// Get websiteName from [hostName] String? getWebsiteName(String hostName) { @@ -308,8 +302,7 @@ String fileSize(dynamic size, [int round = 2]) { return '${(_size / divider / divider / divider).toStringAsFixed(round)} GB'; } - if (_size < divider * divider * divider * divider * divider && - _size % divider == 0) { + if (_size < divider * divider * divider * divider * divider && _size % divider == 0) { final num r = _size / divider / divider / divider / divider; return '${r.toStringAsFixed(0)} TB'; } @@ -319,8 +312,7 @@ String fileSize(dynamic size, [int round = 2]) { return '${r.toStringAsFixed(round)} TB'; } - if (_size < divider * divider * divider * divider * divider * divider && - _size % divider == 0) { + if (_size < divider * divider * divider * divider * divider * divider && _size % divider == 0) { final num r = _size / divider / divider / divider / divider / divider; return '${r.toStringAsFixed(0)} PB'; } else { @@ -346,8 +338,7 @@ StreamSvgIcon getFileTypeImage([String? mimeType]) { 'application/zip' => StreamSvgIcons.filetypeCompressionZip, 'application/x-7z-compressed' => StreamSvgIcons.filetypeCompression7z, 'application/x-arj' => StreamSvgIcons.filetypeCompressionArj, - 'application/vnd.debian.binary-package' => - StreamSvgIcons.filetypeCompressionDeb, + 'application/vnd.debian.binary-package' => StreamSvgIcons.filetypeCompressionDeb, 'application/x-apple-diskimage' => StreamSvgIcons.filetypeCompressionPkg, 'application/x-rar-compressed' => StreamSvgIcons.filetypeCompressionRar, 'application/x-rpm' => StreamSvgIcons.filetypeCompressionRpm, @@ -357,20 +348,14 @@ StreamSvgIcon getFileTypeImage([String? mimeType]) { 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => StreamSvgIcons.filetypePresentationPptx, 'application/vnd.apple.keynote' => StreamSvgIcons.filetypePresentationKey, - 'application/vnd.oasis.opendocument.presentation' => - StreamSvgIcons.filetypePresentationOdp, + 'application/vnd.oasis.opendocument.presentation' => StreamSvgIcons.filetypePresentationOdp, 'application/vnd.ms-excel' => StreamSvgIcons.filetypeSpreadsheetXls, - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => - StreamSvgIcons.filetypeSpreadsheetXlsx, - 'application/vnd.ms-excel.sheet.macroEnabled.12' => - StreamSvgIcons.filetypeSpreadsheetXlsm, - 'application/vnd.oasis.opendocument.spreadsheet' => - StreamSvgIcons.filetypeSpreadsheetOds, + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => StreamSvgIcons.filetypeSpreadsheetXlsx, + 'application/vnd.ms-excel.sheet.macroEnabled.12' => StreamSvgIcons.filetypeSpreadsheetXlsm, + 'application/vnd.oasis.opendocument.spreadsheet' => StreamSvgIcons.filetypeSpreadsheetOds, 'application/msword' => StreamSvgIcons.filetypeTextDoc, - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => - StreamSvgIcons.filetypeTextDocx, - 'application/vnd.oasis.opendocument.text' => - StreamSvgIcons.filetypeTextOdt, + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => StreamSvgIcons.filetypeTextDocx, + 'application/vnd.oasis.opendocument.text' => StreamSvgIcons.filetypeTextOdt, 'text/plain' => StreamSvgIcons.filetypeTextTxt, 'application/rtf' => StreamSvgIcons.filetypeTextRtf, 'application/x-tex' => StreamSvgIcons.filetypeTextTex, diff --git a/packages/stream_chat_flutter/lib/src/utils/message_preview_formatter.dart b/packages/stream_chat_flutter/lib/src/utils/message_preview_formatter.dart index d7d0909f06..b43a9c298d 100644 --- a/packages/stream_chat_flutter/lib/src/utils/message_preview_formatter.dart +++ b/packages/stream_chat_flutter/lib/src/utils/message_preview_formatter.dart @@ -199,7 +199,7 @@ class StreamMessagePreviewFormatter implements MessagePreviewFormatter { return TextSpan(text: text, style: textStyle); }, - ) + ), ]; return TextSpan(children: children); diff --git a/packages/stream_chat_flutter/lib/src/utils/typedefs.dart b/packages/stream_chat_flutter/lib/src/utils/typedefs.dart index ab6a9d318a..ccc1fe6107 100644 --- a/packages/stream_chat_flutter/lib/src/utils/typedefs.dart +++ b/packages/stream_chat_flutter/lib/src/utils/typedefs.dart @@ -42,10 +42,11 @@ typedef ReplyMessageCallback = void Function(Message message); /// The action to perform when a specific image attachment in an [ImageGroup] /// is tapped or clicked. /// {@endtemplate} -typedef OnImageGroupAttachmentTap = void Function( - Message message, - Attachment attachment, -); +typedef OnImageGroupAttachmentTap = + void Function( + Message message, + Attachment attachment, + ); /// {@template onUserAvatarPress} /// The action to perform when a user's avatar is tapped, clicked, or @@ -68,11 +69,12 @@ typedef EditMessageInputBuilder = Widget Function(BuildContext, Message); /// {@template channelListHeaderTitleBuilder} /// A widget builder for custom [ChannelListHeader] title widgets. /// {@endtemplate} -typedef ChannelListHeaderTitleBuilder = Widget Function( - BuildContext context, - ConnectionStatus status, - StreamChatClient client, -); +typedef ChannelListHeaderTitleBuilder = + Widget Function( + BuildContext context, + ConnectionStatus status, + StreamChatClient client, + ); /// {@template channelTapCallback} /// The action to perform when a channel is tapped or clicked. @@ -97,11 +99,12 @@ typedef ViewInfoCallback = void Function(Channel); /// [defaultActionsModal] is the default [AttachmentActionsModal] configuration. /// Use [defaultActionsModal.copyWith] to easily customize it /// {@endtemplate} -typedef AttachmentActionsBuilder = Widget Function( - BuildContext context, - Attachment attachment, - AttachmentActionsModal defaultActionsModal, -); +typedef AttachmentActionsBuilder = + Widget Function( + BuildContext context, + Attachment attachment, + AttachmentActionsModal defaultActionsModal, + ); /// {@template errorListener} /// A callback that can be passed to [StreamMessageInput.onError]. @@ -110,10 +113,11 @@ typedef AttachmentActionsBuilder = Widget Function( /// /// It exists merely for error reporting, and should not be used otherwise. /// {@endtemplate} -typedef ErrorListener = void Function( - Object error, - StackTrace? stackTrace, -); +typedef ErrorListener = + void Function( + Object error, + StackTrace? stackTrace, + ); /// {@template attachmentLimitExceededListener} /// A callback that can be passed to @@ -123,45 +127,50 @@ typedef ErrorListener = void Function( /// /// It exists merely for showing custom error, and should not be used otherwise. /// {@endtemplate} -typedef AttachmentLimitExceedListener = void Function( - int limit, - String error, -); +typedef AttachmentLimitExceedListener = + void Function( + int limit, + String error, + ); /// {@template attachmentThumbnailBuilder} /// A widget builder for representing attachment thumbnails. /// {@endtemplate} -typedef AttachmentThumbnailBuilder = Widget Function( - BuildContext, - Attachment, -); +typedef AttachmentThumbnailBuilder = + Widget Function( + BuildContext, + Attachment, + ); /// {@template mentionTileBuilder} /// A widget builder for representing a custom mention tile. /// {@endtemplate} -typedef MentionTileBuilder = Widget Function( - BuildContext context, - Member member, -); +typedef MentionTileBuilder = + Widget Function( + BuildContext context, + Member member, + ); /// {@template mentionTileOverlayBuilder} /// A widget builder for representing a custom mention tile within a /// [UserMentionsOverlay]. /// {@endtemplate} -typedef MentionTileOverlayBuilder = Widget Function( - BuildContext context, - User user, -); +typedef MentionTileOverlayBuilder = + Widget Function( + BuildContext context, + User user, + ); /// {@template userMentionTileBuilder} /// A builder function for representing a custom user mention tile. /// /// Use [UserMentionTile] for the default implementation. /// {@endtemplate} -typedef UserMentionTileBuilder = Widget Function( - BuildContext context, - User user, -); +typedef UserMentionTileBuilder = + Widget Function( + BuildContext context, + User user, + ); /// {@template actionButtonBuilder} /// A widget builder for building a custom command button. @@ -169,10 +178,11 @@ typedef UserMentionTileBuilder = Widget Function( /// [commandButton] is the default [CommandButton] configuration, /// use [commandButton.copyWith] to easily customize it. /// {@endtemplate} -typedef CommandButtonBuilder = Widget Function( - BuildContext context, - CommandButton commandButton, -); +typedef CommandButtonBuilder = + Widget Function( + BuildContext context, + CommandButton commandButton, + ); /// {@template actionButtonBuilder} /// A widget builder for building a custom action button. @@ -180,27 +190,30 @@ typedef CommandButtonBuilder = Widget Function( /// [attachmentButton] is the default [AttachmentButton] configuration, /// use [attachmentButton.copyWith] to easily customize it. /// {@endtemplate} -typedef AttachmentButtonBuilder = Widget Function( - BuildContext context, - AttachmentButton attachmentButton, -); +typedef AttachmentButtonBuilder = + Widget Function( + BuildContext context, + AttachmentButton attachmentButton, + ); /// {@template quotedMessageAttachmentThumbnailBuilder} /// A widget builder for building a custom quoted message attachment thumbnail. /// {@endtemplate} -typedef QuotedMessageAttachmentThumbnailBuilder = Widget Function( - BuildContext, - Attachment, -); +typedef QuotedMessageAttachmentThumbnailBuilder = + Widget Function( + BuildContext, + Attachment, + ); /// {@template attachmentBuilder} /// A widget builder for representing attachments. /// {@endtemplate} -typedef AttachmentBuilder = Widget Function( - BuildContext, - Message, - List, -); +typedef AttachmentBuilder = + Widget Function( + BuildContext, + Message, + List, + ); /// {@template onQuotedMessageTap} /// The action to perform when a quoted message is tapped. @@ -237,10 +250,11 @@ typedef MessageSearchItemTapCallback = void Function(GetMessageResponse); /// {@template messageSearchItemBuilder} /// A widget builder used to create a custom [ListUserItem] from a [User]. /// {@endtemplate} -typedef MessageSearchItemBuilder = Widget Function( - BuildContext, - GetMessageResponse, -); +typedef MessageSearchItemBuilder = + Widget Function( + BuildContext, + GetMessageResponse, + ); /// {@template messageBuilder} /// A widget builder for creating custom message UI. @@ -248,12 +262,13 @@ typedef MessageSearchItemBuilder = Widget Function( /// [defaultMessageWidget] is the default [StreamMessageWidget] configuration. /// Use [defaultMessageWidget.copyWith] to customize it. /// {@endtemplate} -typedef MessageBuilder = Widget Function( - BuildContext, - MessageDetails, - List, - StreamMessageWidget defaultMessageWidget, -); +typedef MessageBuilder = + Widget Function( + BuildContext, + MessageDetails, + List, + StreamMessageWidget defaultMessageWidget, + ); /// {@template parentMessageBuilder} /// A widget builder for creating custom parent message UI. @@ -261,35 +276,39 @@ typedef MessageBuilder = Widget Function( /// [defaultMessageWidget] is the default [StreamMessageWidget] configuration. /// Use [defaultMessageWidget.copyWith] to customize it. /// {@endtemplate} -typedef ParentMessageBuilder = Widget Function( - BuildContext, - Message?, - StreamMessageWidget defaultMessageWidget, -); +typedef ParentMessageBuilder = + Widget Function( + BuildContext, + Message?, + StreamMessageWidget defaultMessageWidget, + ); /// {@template systemMessageBuilder} /// A widget builder for creating custom system messages. /// {@endtemplate} -typedef SystemMessageBuilder = Widget Function( - BuildContext, - Message, -); +typedef SystemMessageBuilder = + Widget Function( + BuildContext, + Message, + ); /// {@template ephemeralMessageBuilder} /// A widget builder for creating custom ephemeral messages. /// {@endtemplate} -typedef EphemeralMessageBuilder = Widget Function( - BuildContext, - Message, -); +typedef EphemeralMessageBuilder = + Widget Function( + BuildContext, + Message, + ); /// {@template moderatedMessageBuilder} /// A widget builder for creating custom moderated messages. /// {@endtemplate} -typedef ModeratedMessageBuilder = Widget Function( - BuildContext, - Message, -); +typedef ModeratedMessageBuilder = + Widget Function( + BuildContext, + Message, + ); /// {@template threadBuilder} /// A widget builder for creating custom thread UI. @@ -323,23 +342,25 @@ typedef ThreadTapCallback = void Function(Message, Widget?); /// ), /// ```dart /// {@endtemplate} -typedef SpacingWidgetBuilder = Widget Function( - BuildContext context, - List spacingTypes, -); +typedef SpacingWidgetBuilder = + Widget Function( + BuildContext context, + List spacingTypes, + ); /// {@template attachmentDownloader} /// A callback for downloading an attachment asset. /// {@endtemplate} /// Callback to download an attachment asset -typedef AttachmentDownloader = Future Function( - Attachment attachment, { - ProgressCallback? onReceiveProgress, - Map? queryParameters, - CancelToken? cancelToken, - bool deleteOnError, - Options? options, -}); +typedef AttachmentDownloader = + Future Function( + Attachment attachment, { + ProgressCallback? onReceiveProgress, + Map? queryParameters, + CancelToken? cancelToken, + bool deleteOnError, + Options? options, + }); /// Callback to receive the path once the attachment asset is downloaded typedef DownloadedPathCallback = void Function(String? path); @@ -366,10 +387,11 @@ typedef OnScrollToBottom = Function(int unreadCount); /// Widget builder for widgets that may require data from the /// [MessageInputController]. -typedef MessageRelatedBuilder = Widget Function( - BuildContext context, - StreamMessageInputController messageInputController, -); +typedef MessageRelatedBuilder = + Widget Function( + BuildContext context, + StreamMessageInputController messageInputController, + ); /// A function that returns true if the message is valid and can be sent. typedef MessageValidator = bool Function(Message message); diff --git a/packages/stream_chat_flutter/lib/src/video/video_thumbnail_image.dart b/packages/stream_chat_flutter/lib/src/video/video_thumbnail_image.dart index b53f6ebe86..8134b62191 100644 --- a/packages/stream_chat_flutter/lib/src/video/video_thumbnail_image.dart +++ b/packages/stream_chat_flutter/lib/src/video/video_thumbnail_image.dart @@ -41,8 +41,7 @@ import 'package:stream_chat_flutter/src/video/video_service.dart'; /// ``` /// {@end-tool} /// {@endtemplate} -class StreamVideoThumbnailImage - extends ImageProvider { +class StreamVideoThumbnailImage extends ImageProvider { /// {@macro video_thumbnail_image} const StreamVideoThumbnailImage({ required this.video, @@ -131,9 +130,7 @@ class StreamVideoThumbnailImage if (other.runtimeType != runtimeType) { return false; } - return other is StreamVideoThumbnailImage && - other.video == video && - other.scale == scale; + return other is StreamVideoThumbnailImage && other.video == video && other.scale == scale; } @override diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 86bcc75d83..7020cc5dfa 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -1,6 +1,5 @@ export 'package:jiffy/jiffy.dart'; -export 'package:photo_manager/photo_manager.dart' - show ThumbnailSize, ThumbnailFormat; +export 'package:photo_manager/photo_manager.dart' show ThumbnailSize, ThumbnailFormat; export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; export 'src/ai_assistant/ai_typing_indicator_view.dart'; diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index f1c1436724..19306c19f1 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -18,8 +18,8 @@ issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: cached_network_image: ^3.3.1 diff --git a/packages/stream_chat_flutter/test/conditional_parent_builder/conditional_parent_builder_test.dart b/packages/stream_chat_flutter/test/conditional_parent_builder/conditional_parent_builder_test.dart index c52c28ab4b..b029f97917 100644 --- a/packages/stream_chat_flutter/test/conditional_parent_builder/conditional_parent_builder_test.dart +++ b/packages/stream_chat_flutter/test/conditional_parent_builder/conditional_parent_builder_test.dart @@ -3,8 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/conditional_parent_builder/conditional_parent_builder.dart'; void main() { - testWidgets('ConditionalParentBuilder builds the parent widget', - (tester) async { + testWidgets('ConditionalParentBuilder builds the parent widget', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( @@ -26,8 +25,7 @@ void main() { expect(find.byType(Text), findsOneWidget); }); - testWidgets('ConditionalParentBuilder does not build the parent widget', - (tester) async { + testWidgets('ConditionalParentBuilder does not build the parent widget', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( diff --git a/packages/stream_chat_flutter/test/flutter_test_config.dart b/packages/stream_chat_flutter/test/flutter_test_config.dart index 7b00df9385..13bd9f624c 100644 --- a/packages/stream_chat_flutter/test/flutter_test_config.dart +++ b/packages/stream_chat_flutter/test/flutter_test_config.dart @@ -4,8 +4,7 @@ import 'dart:io'; import 'package:alchemist/alchemist.dart'; Future testExecutable(FutureOr Function() testMain) async { - final isRunningInCi = Platform.environment.containsKey('CI') || - Platform.environment.containsKey('GITHUB_ACTIONS'); + final isRunningInCi = Platform.environment.containsKey('CI') || Platform.environment.containsKey('GITHUB_ACTIONS'); return AlchemistConfig.runWithConfig( config: AlchemistConfig( diff --git a/packages/stream_chat_flutter/test/platform_widget_builder/desktop_widget_builder_test.dart b/packages/stream_chat_flutter/test/platform_widget_builder/desktop_widget_builder_test.dart index a7b867ce38..1136d8a5d1 100644 --- a/packages/stream_chat_flutter/test/platform_widget_builder/desktop_widget_builder_test.dart +++ b/packages/stream_chat_flutter/test/platform_widget_builder/desktop_widget_builder_test.dart @@ -1,5 +1,4 @@ -import 'package:flutter/foundation.dart' - show debugDefaultTargetPlatformOverride; +import 'package:flutter/foundation.dart' show debugDefaultTargetPlatformOverride; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/platform_widget_builder/platform_widget_builder.dart'; diff --git a/packages/stream_chat_flutter/test/platform_widget_builder/platform_widget_builder_test.dart b/packages/stream_chat_flutter/test/platform_widget_builder/platform_widget_builder_test.dart index cfb7e774f5..854eacf3f4 100644 --- a/packages/stream_chat_flutter/test/platform_widget_builder/platform_widget_builder_test.dart +++ b/packages/stream_chat_flutter/test/platform_widget_builder/platform_widget_builder_test.dart @@ -1,5 +1,4 @@ -import 'package:flutter/foundation.dart' - show debugDefaultTargetPlatformOverride; +import 'package:flutter/foundation.dart' show debugDefaultTargetPlatformOverride; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/platform_widget_builder/platform_widget_builder.dart'; diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/horizontal_scrollable_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/horizontal_scrollable_positioned_list_test.dart index 6e851ada5c..8bceed8ffe 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/horizontal_scrollable_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/horizontal_scrollable_positioned_list_test.dart @@ -58,119 +58,94 @@ void main() { expect(tester.getBottomRight(find.text('Item 9')).dx, screenWidth); expect(find.text('Item 10'), findsNothing); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemTrailingEdge, - 1 / 10); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemTrailingEdge, + 1 / 10, + ); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); testWidgets('List positioned with 0 at right', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, reverse: true); + await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener, reverse: true); expect(tester.getBottomRight(find.text('Item 0')).dx, screenWidth); expect(tester.getTopLeft(find.text('Item 9')).dx, 0); expect(find.text('Item 10'), findsNothing); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemTrailingEdge, - 1 / 10); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemTrailingEdge, + 1 / 10, + ); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); testWidgets('Scroll to 2 (already on screen)', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 2, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 2, duration: scrollDuration)); await tester.pump(); await tester.pump(scrollDuration); expect(find.text('Item 1'), findsNothing); expect(tester.getTopLeft(find.text('Item 2')).dx, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemTrailingEdge, - 1 / 10); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 2).itemTrailingEdge, + 1 / 10, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 11) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 11).itemTrailingEdge, + 1, + ); }); - testWidgets('Scroll to 100 (not already on screen)', - (WidgetTester tester) async { + testWidgets('Scroll to 100 (not already on screen)', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 99'), findsNothing); expect(find.text('Item 100'), findsOneWidget); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemTrailingEdge, - 1 / 10); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemTrailingEdge, + 1 / 10, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemTrailingEdge, + 1, + ); }); testWidgets('Jump to 100', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); itemScrollController.jumpTo(index: 100); await tester.pumpAndSettle(); @@ -179,39 +154,36 @@ void main() { expect(tester.getBottomRight(find.text('Item 109')).dy, screenHeight); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemTrailingEdge, - 1 / 10); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemTrailingEdge, + 1 / 10, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemLeadingEdge, - 9 / 10); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemLeadingEdge, + 9 / 10, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemTrailingEdge, + 1, + ); }); testWidgets('Scroll to 20 without fading', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); var fadeTransition = tester.widget(fadeTransitionFinder); final initialOpacity = fadeTransition.opacity; - unawaited( - itemScrollController.scrollTo(index: 20, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 20, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -225,8 +197,7 @@ void main() { expect(find.text('Item 20'), findsOneWidget); }); - testWidgets('padding test - centered sliver at left', - (WidgetTester tester) async { + testWidgets('padding test - centered sliver at left', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -235,25 +206,19 @@ void main() { ); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 1')), - const Offset(itemWidth + 10, 10)); - expect(tester.getBottomRight(find.text('Item 1')), - const Offset(10 + itemWidth * 2, screenHeight - 10)); + expect(tester.getTopLeft(find.text('Item 1')), const Offset(itemWidth + 10, 10)); + expect(tester.getBottomRight(find.text('Item 1')), const Offset(10 + itemWidth * 2, screenHeight - 10)); - unawaited( - itemScrollController.scrollTo(index: 490, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 490, duration: scrollDuration)); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(-100, 0)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(-100, 0)); await tester.pumpAndSettle(); - expect(tester.getBottomRight(find.text('Item 499')), - const Offset(screenWidth - 10, screenHeight - 10)); + expect(tester.getBottomRight(find.text('Item 499')), const Offset(screenWidth - 10, screenHeight - 10)); }); - testWidgets('padding test - centered sliver not at left', - (WidgetTester tester) async { + testWidgets('padding test - centered sliver not at left', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -262,19 +227,15 @@ void main() { padding: const EdgeInsets.all(10), ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(200, 0)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(200, 0)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 2')), - const Offset(10 + itemWidth * 2, 10)); - expect(tester.getTopLeft(find.text('Item 3')), - const Offset(10 + itemWidth * 3, 10)); + expect(tester.getTopLeft(find.text('Item 2')), const Offset(10 + itemWidth * 2, 10)); + expect(tester.getTopLeft(find.text('Item 3')), const Offset(10 + itemWidth * 3, 10)); }); - testWidgets('padding test - reversed - centered sliver at right', - (WidgetTester tester) async { + testWidgets('padding test - reversed - centered sliver at right', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -283,26 +244,23 @@ void main() { reverse: true, ); - expect(tester.getTopRight(find.text('Item 0')), - const Offset(screenWidth - 10, 10)); - expect(tester.getTopRight(find.text('Item 1')), - const Offset(screenWidth - (itemWidth + 10), 10)); - expect(tester.getBottomLeft(find.text('Item 1')), - const Offset(screenWidth - (10 + itemWidth * 2), screenHeight - 10)); + expect(tester.getTopRight(find.text('Item 0')), const Offset(screenWidth - 10, 10)); + expect(tester.getTopRight(find.text('Item 1')), const Offset(screenWidth - (itemWidth + 10), 10)); + expect( + tester.getBottomLeft(find.text('Item 1')), + const Offset(screenWidth - (10 + itemWidth * 2), screenHeight - 10), + ); - unawaited( - itemScrollController.scrollTo(index: 490, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 490, duration: scrollDuration)); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(100, 0)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(100, 0)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 499')), const Offset(10, 10)); }); - testWidgets('padding test - reversed - centered sliver not at right', - (WidgetTester tester) async { + testWidgets('padding test - reversed - centered sliver not at right', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -312,15 +270,11 @@ void main() { reverse: true, ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(-200, 0)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(-200, 0)); await tester.pumpAndSettle(); - expect(tester.getTopRight(find.text('Item 0')), - const Offset(screenWidth - 10, 10)); - expect(tester.getTopRight(find.text('Item 2')), - const Offset(screenWidth - (10 + itemWidth * 2), 10)); - expect(tester.getTopRight(find.text('Item 3')), - const Offset(screenWidth - (10 + itemWidth * 3), 10)); + expect(tester.getTopRight(find.text('Item 0')), const Offset(screenWidth - 10, 10)); + expect(tester.getTopRight(find.text('Item 2')), const Offset(screenWidth - (10 + itemWidth * 2), 10)); + expect(tester.getTopRight(find.text('Item 3')), const Offset(screenWidth - (10 + itemWidth * 3), 10)); }); } diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/positioned_list_test.dart index 887165a512..c43fea6fa9 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/positioned_list_test.dart @@ -53,16 +53,11 @@ void main() { expect(find.text('Item 4'), findsOneWidget); expect(find.text('Item 5'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemTrailingEdge, - 1 / 2); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemTrailingEdge, + 1 / 2, + ); }); testWidgets('List positioned with 0 at top', (WidgetTester tester) async { @@ -73,26 +68,13 @@ void main() { expect(find.text('Item 9'), findsOneWidget); expect(find.text('Item 10'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 10).itemLeadingEdge, 1); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 10) - .itemLeadingEdge, - 1); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 10) - .itemTrailingEdge, - 11 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 10).itemTrailingEdge, + 11 / 10, + ); }); testWidgets('List positioned with 5 at top', (WidgetTester tester) async { @@ -105,25 +87,15 @@ void main() { expect(find.text('Item 15'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemLeadingEdge, - -1 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemTrailingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemLeadingEdge, + -1 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemTrailingEdge, 0); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 14) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 14).itemTrailingEdge, + 1, + ); }); testWidgets('List positioned with 20 at bottom', (WidgetTester tester) async { @@ -134,69 +106,50 @@ void main() { expect(find.text('Item 19'), findsOneWidget); expect(find.text('Item 10'), findsOneWidget); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 10).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 10) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 19) - .itemLeadingEdge, - 9 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 19) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 19).itemLeadingEdge, + 9 / 10, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemLeadingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 19).itemTrailingEdge, + 1, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemLeadingEdge, 1); }); - testWidgets('List positioned with 20 at halfway', - (WidgetTester tester) async { + testWidgets('List positioned with 20 at halfway', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 20, anchor: 0.5); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemLeadingEdge, - 0.5); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemLeadingEdge, + 0.5, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemTrailingEdge, - 0.5 + itemHeight / screenHeight); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemTrailingEdge, + 0.5 + itemHeight / screenHeight, + ); }); - testWidgets('List positioned with 20 half off top of screen', - (WidgetTester tester) async { - await setUpWidgetTest(tester, - topItem: 20, anchor: -(itemHeight / screenHeight) / 2); + testWidgets('List positioned with 20 half off top of screen', (WidgetTester tester) async { + await setUpWidgetTest(tester, topItem: 20, anchor: -(itemHeight / screenHeight) / 2); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemLeadingEdge, - -(itemHeight / screenHeight) / 2); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemLeadingEdge, + -(itemHeight / screenHeight) / 2, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemTrailingEdge, - (itemHeight / screenHeight) / 2); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemTrailingEdge, + (itemHeight / screenHeight) / 2, + ); }); - testWidgets('List positioned with 5 at top then scroll up 2', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at top then scroll up 2', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 5); - await tester.drag( - find.byType(PositionedList), const Offset(0, itemHeight * 2)); + await tester.drag(find.byType(PositionedList), const Offset(0, itemHeight * 2)); await tester.pump(); expect(find.text('Item 2'), findsNothing); @@ -205,44 +158,33 @@ void main() { expect(find.text('Item 13'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - -1 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 3) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, + -1 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 3).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 12) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 12).itemTrailingEdge, + 1, + ); }); - testWidgets('List positioned with 5 at top then scroll down 1/2', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at top then scroll down 1/2', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 5); - await tester.drag( - find.byType(PositionedList), const Offset(0, -1 / 2 * itemHeight)); + await tester.drag(find.byType(PositionedList), const Offset(0, -1 / 2 * itemHeight)); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemTrailingEdge, - 1 / 20); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemTrailingEdge, + 1 / 20, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 14) - .itemLeadingEdge, - 17 / 20); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 14).itemLeadingEdge, + 17 / 20, + ); }); - testWidgets('List positioned with 0 at top scroll up 5', - (WidgetTester tester) async { + testWidgets('List positioned with 0 at top scroll up 5', (WidgetTester tester) async { final scrollController = ScrollController(); await setUpWidgetTest(tester, scrollController: scrollController); await tester.pump(); @@ -256,23 +198,16 @@ void main() { expect(find.text('Item 14'), findsOneWidget); expect(find.text('Item 15'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemLeadingEdge, - -1 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemLeadingEdge, + -1 / 10, + ); }); - testWidgets('List positioned with 5 at top then scroll up 2 programatically', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at top then scroll up 2 programatically', (WidgetTester tester) async { final scrollController = ScrollController(); - await setUpWidgetTest(tester, - topItem: 5, scrollController: scrollController); + await setUpWidgetTest(tester, topItem: 5, scrollController: scrollController); scrollController.jumpTo(-2 * itemHeight); await tester.pump(); @@ -283,92 +218,67 @@ void main() { expect(find.text('Item 13'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - -1 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 3) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, + -1 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 3).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 12) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 12).itemTrailingEdge, + 1, + ); }); - testWidgets( - 'List positioned with 5 at top then scroll down 20 programatically', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at top then scroll down 20 programatically', (WidgetTester tester) async { final scrollController = ScrollController(); - await setUpWidgetTest(tester, - topItem: 5, scrollController: scrollController); + await setUpWidgetTest(tester, topItem: 5, scrollController: scrollController); scrollController.jumpTo(itemHeight * 20); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 23) - .itemLeadingEdge, - -2 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 24) - .itemLeadingEdge, - -1 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 23).itemLeadingEdge, + -2 / 10, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 25) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 24).itemLeadingEdge, + -1 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 25).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemLeadingEdge, - -21 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemLeadingEdge, + -21 / 10, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - -20 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, + -20 / 10, + ); }); - testWidgets('List positioned with 5 at top and initial scroll offset', - (WidgetTester tester) async { - final scrollController = - ScrollController(initialScrollOffset: -2 * itemHeight); - await setUpWidgetTest(tester, - topItem: 5, scrollController: scrollController); + testWidgets('List positioned with 5 at top and initial scroll offset', (WidgetTester tester) async { + final scrollController = ScrollController(initialScrollOffset: -2 * itemHeight); + await setUpWidgetTest(tester, topItem: 5, scrollController: scrollController); expect(find.text('Item 2'), findsNothing); expect(find.text('Item 3'), findsOneWidget); expect(find.text('Item 12'), findsOneWidget); expect(find.text('Item 13'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 3).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 3) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 12) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 12).itemTrailingEdge, + 1, + ); }); - testWidgets('Does not crash when updated offscreen', - (WidgetTester tester) async { + testWidgets('Does not crash when updated offscreen', (WidgetTester tester) async { late StateSetter setState; var updated = false; // There's 0 relayout boundaries in this subtree. - final widget = StatefulBuilder(builder: (context, stateSetter) { - setState = stateSetter; - return Positioned( + final widget = StatefulBuilder( + builder: (context, stateSetter) { + setState = stateSetter; + return Positioned( left: 0, right: 0, child: PositionedList( @@ -378,17 +288,21 @@ void main() { // RenderIndexedSemantics to the render tree. addSemanticIndexes: updated, itemBuilder: (context, index) => const SizedBox(height: itemHeight), - )); - }); + ), + ); + }, + ); - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: Overlay( - initialEntries: [ - OverlayEntry(builder: (context) => widget, maintainState: true), - ], + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: Overlay( + initialEntries: [ + OverlayEntry(builder: (context) => widget, maintainState: true), + ], + ), ), - )); + ); // Insert a new opaque OverlayEntry that would prevent the first // OverlayEntry from doing re-layout. Since there's no relayout boundaries diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_positioned_list_test.dart index 828f17e390..3077126080 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_positioned_list_test.dart @@ -52,16 +52,11 @@ void main() { expect(find.text('Item 4'), findsOneWidget); expect(find.text('Item 5'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemTrailingEdge, - 1 / 2); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemTrailingEdge, + 1 / 2, + ); }); testWidgets('List positioned with 0 at bottom', (WidgetTester tester) async { @@ -72,16 +67,8 @@ void main() { expect(tester.getTopLeft(find.text('Item 9')).dy, 0); expect(find.text('Item 10'), findsNothing); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); testWidgets('List positioned with 5 at bottom', (WidgetTester tester) async { @@ -94,25 +81,15 @@ void main() { expect(find.text('Item 15'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemLeadingEdge, - -1 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemTrailingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemLeadingEdge, + -1 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemTrailingEdge, 0); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 14) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 14).itemTrailingEdge, + 1, + ); }); testWidgets('List positioned with 15 at bottom', (WidgetTester tester) async { @@ -134,53 +111,35 @@ void main() { expect(find.text('Item 5'), findsOneWidget); expect(find.text('Item 4'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 15).itemLeadingEdge, 1); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 15) - .itemLeadingEdge, - 1); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 14) - .itemTrailingEdge, - 1); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 14) - .itemLeadingEdge, - 9 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 14).itemTrailingEdge, + 1, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 14).itemLeadingEdge, + 9 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); }); - testWidgets('List positioned with 5 at bottom then scroll up 2', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at bottom then scroll up 2', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 5); - await tester.drag( - find.byType(PositionedList), const Offset(0, itemHeight * 2)); + await tester.drag(find.byType(PositionedList), const Offset(0, itemHeight * 2)); await tester.pump(); expect(find.text('Item 6'), findsNothing); expect(find.text('Item 7'), findsOneWidget); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 7).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 7) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 7) - .itemTrailingEdge, - 1 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 7).itemTrailingEdge, + 1 / 10, + ); }); - testWidgets('List positioned with 0 at bottom scroll to item 5', - (WidgetTester tester) async { + testWidgets('List positioned with 0 at bottom scroll to item 5', (WidgetTester tester) async { final scrollController = ScrollController(); await setUpWidgetTest(tester, scrollController: scrollController); await tester.pump(); @@ -194,24 +153,16 @@ void main() { expect(find.text('Item 14'), findsOneWidget); expect(find.text('Item 15'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemLeadingEdge, - -1 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemLeadingEdge, + -1 / 10, + ); }); - testWidgets( - 'List positioned with 5 at bottom then scroll up 2 programatically', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at bottom then scroll up 2 programatically', (WidgetTester tester) async { final scrollController = ScrollController(); - await setUpWidgetTest(tester, - topItem: 5, scrollController: scrollController); + await setUpWidgetTest(tester, topItem: 5, scrollController: scrollController); scrollController.jumpTo(itemHeight * 2); await tester.pump(); @@ -222,28 +173,19 @@ void main() { expect(find.text('Item 17'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 6) - .itemLeadingEdge, - -1 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 7) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 6).itemLeadingEdge, + -1 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 7).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 16) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 16).itemTrailingEdge, + 1, + ); }); - testWidgets('List positioned with 5 at bottom and initial scroll offset', - (WidgetTester tester) async { - final scrollController = - ScrollController(initialScrollOffset: itemHeight * 2); - await setUpWidgetTest(tester, - topItem: 5, scrollController: scrollController); + testWidgets('List positioned with 5 at bottom and initial scroll offset', (WidgetTester tester) async { + final scrollController = ScrollController(initialScrollOffset: itemHeight * 2); + await setUpWidgetTest(tester, topItem: 5, scrollController: scrollController); expect(find.text('Item 6'), findsNothing); expect(find.text('Item 7'), findsOneWidget); @@ -251,19 +193,13 @@ void main() { expect(find.text('Item 17'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 6) - .itemLeadingEdge, - -1 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 7) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 6).itemLeadingEdge, + -1 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 7).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 16) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 16).itemTrailingEdge, + 1, + ); }); } diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_scrollable_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_scrollable_positioned_list_test.dart index e2b16d8a4d..ddd1971659 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_scrollable_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_scrollable_positioned_list_test.dart @@ -51,122 +51,95 @@ void main() { expect(tester.getTopLeft(find.text('Item 9')).dy, 0); expect(find.text('Item 10'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); - testWidgets('Scroll to 1 then 2 (both already on screen)', - (WidgetTester tester) async { + testWidgets('Scroll to 1 then 2 (both already on screen)', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 1, duration: scrollDuration)); await tester.pump(); await tester.pump(scrollDuration); expect(find.text('Item 0'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 1) - .itemLeadingEdge, - 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 1).itemLeadingEdge, 0); expect(tester.getBottomRight(find.text('Item 1')).dy, screenHeight); - unawaited( - itemScrollController.scrollTo(index: 2, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 2, duration: scrollDuration)); await tester.pump(); await tester.pump(scrollDuration); expect(find.text('Item 1'), findsNothing); expect(tester.getBottomRight(find.text('Item 2')).dy, screenHeight); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 11) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 11).itemTrailingEdge, + 1, + ); }); - testWidgets('Scroll to 5 (already on screen) and then back to 0', - (WidgetTester tester) async { + testWidgets('Scroll to 5 (already on screen) and then back to 0', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 5, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 5, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 0'), findsOneWidget); expect(find.text('Item 9'), findsOneWidget); expect(find.text('Item 10'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); - testWidgets('Scroll to 100 (not already on screen)', - (WidgetTester tester) async { + testWidgets('Scroll to 100 (not already on screen)', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 99'), findsNothing); expect(find.text('Item 100'), findsOneWidget); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemTrailingEdge, + 1, + ); }); testWidgets('Jump to 100', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); itemScrollController.jumpTo(index: 100); await tester.pumpAndSettle(); @@ -175,19 +148,16 @@ void main() { expect(tester.getTopLeft(find.text('Item 109')).dy, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemTrailingEdge, + 1, + ); }); - testWidgets('padding test - centered sliver at bottom', - (WidgetTester tester) async { + testWidgets('padding test - centered sliver at bottom', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -195,26 +165,23 @@ void main() { padding: const EdgeInsets.all(10), ); - expect(tester.getBottomLeft(find.text('Item 0')), - const Offset(10, screenHeight - 10)); - expect(tester.getBottomLeft(find.text('Item 1')), - const Offset(10, screenHeight - (itemHeight + 10))); - expect(tester.getTopRight(find.text('Item 1')), - const Offset(screenWidth - 10, screenHeight - (10 + itemHeight * 2))); + expect(tester.getBottomLeft(find.text('Item 0')), const Offset(10, screenHeight - 10)); + expect(tester.getBottomLeft(find.text('Item 1')), const Offset(10, screenHeight - (itemHeight + 10))); + expect( + tester.getTopRight(find.text('Item 1')), + const Offset(screenWidth - 10, screenHeight - (10 + itemHeight * 2)), + ); - unawaited( - itemScrollController.scrollTo(index: 490, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 490, duration: scrollDuration)); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, 100)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, 100)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 499')), const Offset(10, 10)); }); - testWidgets('padding test - centered sliver not at bottom', - (WidgetTester tester) async { + testWidgets('padding test - centered sliver not at bottom', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -223,15 +190,11 @@ void main() { padding: const EdgeInsets.all(10), ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, -200)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, -200)); await tester.pumpAndSettle(); - expect(tester.getBottomLeft(find.text('Item 0')), - const Offset(10, screenHeight - 10)); - expect(tester.getBottomLeft(find.text('Item 2')), - const Offset(10, screenHeight - (10 + itemHeight * 2))); - expect(tester.getBottomLeft(find.text('Item 3')), - const Offset(10, screenHeight - (10 + itemHeight * 3))); + expect(tester.getBottomLeft(find.text('Item 0')), const Offset(10, screenHeight - 10)); + expect(tester.getBottomLeft(find.text('Item 2')), const Offset(10, screenHeight - (10 + itemHeight * 2))); + expect(tester.getBottomLeft(find.text('Item 3')), const Offset(10, screenHeight - (10 + itemHeight * 3))); }); } diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/scrollable_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/scrollable_positioned_list_test.dart index d3c19b2f0f..baa5196c49 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/scrollable_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/scrollable_positioned_list_test.dart @@ -52,8 +52,7 @@ void main() { 'index must be in the range of 0 to itemCount - 1', ); return SizedBox( - height: - variableHeight ? (itemHeight + (index % 13) * 5) : itemHeight, + height: variableHeight ? (itemHeight + (index % 13) * 5) : itemHeight, child: Text('Item $index'), ); }, @@ -85,24 +84,12 @@ void main() { expect(find.text('Item 9'), findsOneWidget); expect(find.text('Item 10'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); - expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 10), - isEmpty); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 10), isEmpty); }); - testWidgets('List positioned with 0 at top - use default values', - (WidgetTester tester) async { + testWidgets('List positioned with 0 at top - use default values', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); @@ -124,88 +111,68 @@ void main() { expect(find.text('Item 9'), findsOneWidget); expect(find.text('Item 10'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); testWidgets('List positioned with 5 at top', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, initialIndex: 5); + await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener, initialIndex: 5); expect(find.text('Item 4'), findsNothing); expect(find.text('Item 5'), findsOneWidget); - expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 4), - isEmpty); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 4), isEmpty); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); }); testWidgets('List positioned with 9 at middle', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, - initialIndex: 9, - initialAlignment: 0.5); + await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener, initialIndex: 9, initialAlignment: 0.5); expect(tester.getTopLeft(find.text('Item 9')).dy, screenHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - 0.5); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + 0.5, + ); }); - testWidgets('List positioned with 9 half way off top', - (WidgetTester tester) async { + testWidgets('List positioned with 9 half way off top', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, - initialIndex: 9, - initialAlignment: -(itemHeight / screenHeight) / 2); + await setUpWidgetTest( + tester, + itemPositionsListener: itemPositionsListener, + initialIndex: 9, + initialAlignment: -(itemHeight / screenHeight) / 2, + ); expect(tester.getTopLeft(find.text('Item 9')).dy, -itemHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - -(itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + -(itemHeight / screenHeight) / 2, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - (itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, + (itemHeight / screenHeight) / 2, + ); }); testWidgets('Scroll to 9 half way off top', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); final itemScrollController = ItemScrollController(); expect(itemScrollController.isAttached, false); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, - itemScrollController: itemScrollController); + await setUpWidgetTest( + tester, + itemPositionsListener: itemPositionsListener, + itemScrollController: itemScrollController, + ); expect(itemScrollController.isAttached, true); - unawaited(itemScrollController.scrollTo( - index: 9, - duration: scrollDuration, - alignment: -(itemHeight / screenHeight) / 2)); + unawaited( + itemScrollController.scrollTo(index: 9, duration: scrollDuration, alignment: -(itemHeight / screenHeight) / 2), + ); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); @@ -213,54 +180,51 @@ void main() { expect(tester.getTopLeft(find.text('Item 9')).dy, -itemHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - -(itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + -(itemHeight / screenHeight) / 2, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - (itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, + (itemHeight / screenHeight) / 2, + ); }); testWidgets('Jump to 9 half way off top', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); final itemScrollController = ItemScrollController(); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, - itemScrollController: itemScrollController); + await setUpWidgetTest( + tester, + itemPositionsListener: itemPositionsListener, + itemScrollController: itemScrollController, + ); - itemScrollController.jumpTo( - index: 9, alignment: -(itemHeight / screenHeight) / 2); + itemScrollController.jumpTo(index: 9, alignment: -(itemHeight / screenHeight) / 2); await tester.pump(); expect(tester.getTopLeft(find.text('Item 9')).dy, -itemHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - -(itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + -(itemHeight / screenHeight) / 2, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - (itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, + (itemHeight / screenHeight) / 2, + ); }); - testWidgets('List positioned with 9 at middle scroll to 15 at bottom', - (WidgetTester tester) async { + testWidgets('List positioned with 9 at middle scroll to 15 at bottom', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener, - initialIndex: 9, - initialAlignment: 0.5); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + initialIndex: 9, + initialAlignment: 0.5, + ); - unawaited(itemScrollController.scrollTo( - index: 16, duration: scrollDuration, alignment: 1)); + unawaited(itemScrollController.scrollTo(index: 16, duration: scrollDuration, alignment: 1)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); @@ -268,21 +232,21 @@ void main() { expect(tester.getBottomRight(find.text('Item 15')).dy, screenHeight); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 15) - .itemTrailingEdge, - 1.0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 15).itemTrailingEdge, + 1.0, + ); }); testWidgets('Scroll to 1 (already on screen)', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 1, duration: scrollDuration)); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -295,83 +259,61 @@ void main() { expect(find.text('Item 1'), findsOneWidget); expect(find.text('Item 10'), findsOneWidget); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 1).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 1) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 10) - .itemTrailingEdge, - 1); - expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 11), - isEmpty); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 10).itemTrailingEdge, + 1, + ); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 11), isEmpty); }); - testWidgets('Scroll to 1 then 2 (both already on screen)', - (WidgetTester tester) async { + testWidgets('Scroll to 1 then 2 (both already on screen)', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 1, duration: scrollDuration)); await tester.pump(); await tester.pump(scrollDuration); expect(find.text('Item 0'), findsNothing); expect(find.text('Item 1'), findsOneWidget); - unawaited( - itemScrollController.scrollTo(index: 2, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 2, duration: scrollDuration)); await tester.pump(); await tester.pump(scrollDuration); expect(find.text('Item 1'), findsNothing); expect(find.text('Item 2'), findsOneWidget); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 1), isEmpty); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 1), - isEmpty); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 11) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 11).itemTrailingEdge, + 1, + ); }); - testWidgets('Scroll to 5 (already on screen) and then back to 0', - (WidgetTester tester) async { + testWidgets('Scroll to 5 (already on screen) and then back to 0', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 5, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 5, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); @@ -380,30 +322,23 @@ void main() { expect(find.text('Item 9'), findsOneWidget); expect(find.text('Item 10'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); testWidgets('Scroll to 20 without fading', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); var fadeTransition = tester.widget(fadeTransitionFinder); final initialOpacity = fadeTransition.opacity; - unawaited( - itemScrollController.scrollTo(index: 20, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 20, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -417,16 +352,16 @@ void main() { expect(find.text('Item 20'), findsOneWidget); }); - testWidgets('Scroll to 100 (not already on screen)', - (WidgetTester tester) async { + testWidgets('Scroll to 100 (not already on screen)', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); @@ -435,26 +370,22 @@ void main() { expect(find.text('Item 100'), findsOneWidget); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemTrailingEdge, + 1, + ); await tester.pumpAndSettle(); }); - testWidgets('Scroll to 100 (not already on screen) front scroll view', - (WidgetTester tester) async { + testWidgets('Scroll to 100 (not already on screen) front scroll view', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); expect(fadeTransitionFinder.evaluate().length, 2); @@ -489,13 +420,11 @@ void main() { ); }); - testWidgets('Scroll to 100 (not already on screen) back scroll view', - (WidgetTester tester) async { + testWidgets('Scroll to 100 (not already on screen) back scroll view', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -506,23 +435,22 @@ void main() { expect(find.text('Item 25', skipOffstage: false), findsNothing); }); - testWidgets('Scroll to 100 (not already on screen) then back to 0', - (WidgetTester tester) async { + testWidgets('Scroll to 100 (not already on screen) then back to 0', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); expect(find.text('Item 0'), findsNothing); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pump(); await tester.pump(); expect( @@ -538,29 +466,18 @@ void main() { expect(find.text('Item 0'), findsOneWidget); expect(tester.getTopLeft(find.text('Item 0')).dy, 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); - testWidgets('Scroll to 100 then back to 0 back scroll view', - (WidgetTester tester) async { + testWidgets('Scroll to 100 then back to 0 back scroll view', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -571,19 +488,16 @@ void main() { await tester.pumpAndSettle(); }); - testWidgets('Scroll to 100 then back to 0 front scroll view', - (WidgetTester tester) async { + testWidgets('Scroll to 100 then back to 0 front scroll view', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -602,20 +516,17 @@ void main() { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -629,9 +540,11 @@ void main() { testWidgets('Jump to 100', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); itemScrollController.jumpTo(index: 100); await tester.pump(); @@ -642,24 +555,23 @@ void main() { expect(tester.getBottomLeft(find.text('Item 109')).dy, screenHeight); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemTrailingEdge, + 1, + ); }); - testWidgets('Jump to 100 and position at bottom', - (WidgetTester tester) async { + testWidgets('Jump to 100 and position at bottom', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); itemScrollController.jumpTo(index: 100, alignment: 1); await tester.pump(); @@ -669,23 +581,20 @@ void main() { expect(tester.getBottomLeft(find.text('Item 99')).dy, screenHeight); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 99) - .itemTrailingEdge, - 1.0); - expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 100), - isEmpty); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 99).itemTrailingEdge, + 1.0, + ); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 100), isEmpty); }); - testWidgets('Jump to 100 and position at middle', - (WidgetTester tester) async { + testWidgets('Jump to 100 and position at middle', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); itemScrollController.jumpTo(index: 100, alignment: 0.5); await tester.pump(); @@ -695,21 +604,21 @@ void main() { expect(tester.getTopLeft(find.text('Item 100')).dy, screenHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0.5); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0.5, + ); }); - testWidgets('Manually scroll a significant distance, jump to 100', - (WidgetTester tester) async { + testWidgets('Manually scroll a significant distance, jump to 100', (WidgetTester tester) async { // Test for https://github.com/google/flutter.widgets/issues/144. final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener, - variableHeight: true); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + variableHeight: true, + ); final listFinder = find.byType(ScrollablePositionedList); for (var i = 0; i < 5; i += 1) { @@ -723,16 +632,16 @@ void main() { expect(tester.getTopLeft(find.text('Item 100')).dy, 0); }, skip: true); - testWidgets('Scroll to 100 and position at bottom', - (WidgetTester tester) async { + testWidgets('Scroll to 100 and position at bottom', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited(itemScrollController.scrollTo( - index: 100, alignment: 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, alignment: 1, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); @@ -740,26 +649,22 @@ void main() { expect(tester.getBottomLeft(find.text('Item 99')).dy, screenHeight); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 99) - .itemTrailingEdge, - 1.0); - expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 100), - isEmpty); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 99).itemTrailingEdge, + 1.0, + ); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 100), isEmpty); }); - testWidgets('Scroll to 100 and position at middle', - (WidgetTester tester) async { + testWidgets('Scroll to 100 and position at middle', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited(itemScrollController.scrollTo( - index: 100, alignment: 0.5, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, alignment: 0.5, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); @@ -767,22 +672,21 @@ void main() { expect(tester.getTopLeft(find.text('Item 100')).dy, screenHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0.5); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0.5, + ); }); - testWidgets('Scroll to 9 and position at middle', - (WidgetTester tester) async { + testWidgets('Scroll to 9 and position at middle', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited(itemScrollController.scrollTo( - index: 9, alignment: 0.5, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 9, alignment: 0.5, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); @@ -790,22 +694,21 @@ void main() { expect(tester.getTopLeft(find.text('Item 9')).dy, screenHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - 0.5); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + 0.5, + ); }); - testWidgets('Scroll up a little then jump to 100', - (WidgetTester tester) async { + testWidgets('Scroll up a little then jump to 100', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, -10)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, -10)); await tester.pumpAndSettle(); itemScrollController.jumpTo(index: 100); @@ -817,24 +720,20 @@ void main() { expect(tester.getBottomLeft(find.text('Item 109')).dy, screenHeight); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemTrailingEdge, + 1, + ); }); - testWidgets('Scroll to 100 Jump to 0 Scroll to 100', - (WidgetTester tester) async { + testWidgets('Scroll to 100 Jump to 0 Scroll to 100', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); @@ -844,8 +743,7 @@ void main() { await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -859,13 +757,11 @@ void main() { expect(tester.getBottomLeft(find.text('Item 109')).dy, screenHeight); }); - testWidgets('Scroll to 100 stop before half way', - (WidgetTester tester) async { + testWidgets('Scroll to 100 stop before half way', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2 - scrollDuration ~/ 20); @@ -884,8 +780,7 @@ void main() { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -904,12 +799,10 @@ void main() { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2 - scrollDuration ~/ 20); @@ -927,21 +820,18 @@ void main() { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2 + scrollDuration ~/ 20); expect(find.text('Item 9', skipOffstage: false), findsOneWidget); - expect(tester.getBottomLeft(find.text('Item 100')).dy, - closeTo(screenHeight, tolerance)); + expect(tester.getBottomLeft(find.text('Item 100')).dy, closeTo(screenHeight, tolerance)); await tester.tap(find.byType(ScrollablePositionedList)); await tester.pump(); - expect(tester.getBottomLeft(find.text('Item 100')).dy, - closeTo(screenHeight, tolerance)); + expect(tester.getBottomLeft(find.text('Item 100')).dy, closeTo(screenHeight, tolerance)); expect(find.text('Item 9', skipOffstage: false), findsNothing); expect(fadeTransitionFinder, findsNWidgets(1)); @@ -952,12 +842,10 @@ void main() { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2 + scrollDuration ~/ 20); @@ -976,12 +864,10 @@ void main() { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -995,13 +881,11 @@ void main() { await tester.pumpAndSettle(); }); - testWidgets('Scroll to 100 jump to 250 half way', - (WidgetTester tester) async { + testWidgets('Scroll to 100 jump to 250 half way', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -1016,17 +900,14 @@ void main() { await tester.pumpAndSettle(); }); - testWidgets('Scroll to 250, scroll to 100, jump to 0 half way', - (WidgetTester tester) async { + testWidgets('Scroll to 250, scroll to 100, jump to 0 half way', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 250, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 250, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -1040,38 +921,32 @@ void main() { await tester.pumpAndSettle(); }); - testWidgets('Scroll to 100 scroll to 250 half way', - (WidgetTester tester) async { + testWidgets('Scroll to 100 scroll to 250 half way', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); - unawaited( - itemScrollController.scrollTo(index: 250, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 250, duration: scrollDuration)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 250')).dy, 0); expect(find.text('Item 100'), findsNothing); }); - testWidgets("Second scroll future doesn't complete until scroll is done", - (WidgetTester tester) async { + testWidgets("Second scroll future doesn't complete until scroll is done", (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); - final scrollFuture2 = - itemScrollController.scrollTo(index: 250, duration: scrollDuration); + final scrollFuture2 = itemScrollController.scrollTo(index: 250, duration: scrollDuration); var futureComplete = false; unawaited(scrollFuture2.then((_) => futureComplete = true)); @@ -1087,42 +962,33 @@ void main() { expect(futureComplete, isTrue); }); - testWidgets('Scroll to 250, scroll to 100, scroll to 0 half way', - (WidgetTester tester) async { + testWidgets('Scroll to 250, scroll to 100, scroll to 0 half way', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 250, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 250, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')).dy, 0); expect(find.text('Item 100'), findsNothing); }); - testWidgets( - 'Scroll to 100, scroll to 200, then scroll to 300 without waiting', - (WidgetTester tester) async { + testWidgets('Scroll to 100, scroll to 200, then scroll to 300 without waiting', (WidgetTester tester) async { // Possibly https://github.com/google/flutter.widgets/issues/171. final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); - unawaited( - itemScrollController.scrollTo(index: 200, duration: scrollDuration)); - unawaited( - itemScrollController.scrollTo(index: 300, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 200, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 300, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 100'), findsNothing); @@ -1138,9 +1004,11 @@ void main() { (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); itemScrollController.jumpTo(index: 400, alignment: 1); await tester.pumpAndSettle(); @@ -1150,12 +1018,10 @@ void main() { await tester.drag(listFinder, const Offset(0, -screenHeight)); await tester.pumpAndSettle(); - unawaited(itemScrollController.scrollTo( - index: 100, alignment: 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, alignment: 1, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited(itemScrollController.scrollTo( - index: 400, alignment: 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 400, alignment: 1, duration: scrollDuration)); await tester.pumpAndSettle(); final itemFinder = find.text('Item 399'); @@ -1166,12 +1032,9 @@ void main() { testWidgets('physics', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - physics: const BouncingScrollPhysics()); + await setUpWidgetTest(tester, itemScrollController: itemScrollController, physics: const BouncingScrollPhysics()); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, 50)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, 50)); await tester.pump(const Duration(milliseconds: 200)); expect(tester.getTopLeft(find.text('Item 0')).dy, greaterThan(0)); @@ -1179,14 +1042,12 @@ void main() { await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')).dy, 0); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); itemScrollController.jumpTo(index: 0); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, 50)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, 50)); await tester.pump(const Duration(milliseconds: 200)); expect(tester.getTopLeft(find.text('Item 0')).dy, greaterThan(0)); @@ -1197,47 +1058,51 @@ void main() { testWidgets('correct index sematics', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, initialIndex: 5); + await setUpWidgetTest(tester, itemScrollController: itemScrollController, initialIndex: 5); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, itemHeight * 2)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, itemHeight * 2)); await tester.pumpAndSettle(); - final indexSemantics3 = tester.widget(find.ancestor( - of: find.text('Item 3'), matching: find.byType(IndexedSemantics))); + final indexSemantics3 = tester.widget( + find.ancestor(of: find.text('Item 3'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics3.index, 3); - final indexSemantics4 = tester.widget(find.ancestor( - of: find.text('Item 4'), matching: find.byType(IndexedSemantics))); + final indexSemantics4 = tester.widget( + find.ancestor(of: find.text('Item 4'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics4.index, 4); - final indexSemantics5 = tester.widget(find.ancestor( - of: find.text('Item 5'), matching: find.byType(IndexedSemantics))); + final indexSemantics5 = tester.widget( + find.ancestor(of: find.text('Item 5'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics5.index, 5); - final indexSemantics6 = tester.widget(find.ancestor( - of: find.text('Item 6'), matching: find.byType(IndexedSemantics))); + final indexSemantics6 = tester.widget( + find.ancestor(of: find.text('Item 6'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics6.index, 6); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); itemScrollController.jumpTo(index: 0); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, itemHeight * 2)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, itemHeight * 2)); await tester.pumpAndSettle(); - final indexSemantics3b = tester.widget(find.ancestor( - of: find.text('Item 3'), matching: find.byType(IndexedSemantics))); + final indexSemantics3b = tester.widget( + find.ancestor(of: find.text('Item 3'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics3b.index, 3); - final indexSemantics4b = tester.widget(find.ancestor( - of: find.text('Item 4'), matching: find.byType(IndexedSemantics))); + final indexSemantics4b = tester.widget( + find.ancestor(of: find.text('Item 4'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics4b.index, 4); - final indexSemantics5b = tester.widget(find.ancestor( - of: find.text('Item 5'), matching: find.byType(IndexedSemantics))); + final indexSemantics5b = tester.widget( + find.ancestor(of: find.text('Item 5'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics5b.index, 5); - final indexSemantics6b = tester.widget(find.ancestor( - of: find.text('Item 6'), matching: find.byType(IndexedSemantics))); + final indexSemantics6b = tester.widget( + find.ancestor(of: find.text('Item 6'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics6b.index, 6); }); @@ -1252,8 +1117,7 @@ void main() { expect(find.byType(IndexedSemantics), findsNothing); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); itemScrollController.jumpTo(index: 0); await tester.pumpAndSettle(); @@ -1270,16 +1134,13 @@ void main() { itemScrollController: itemScrollController, ); - final customScrollView = - tester.widget(find.byType(UnboundedCustomScrollView)); + final customScrollView = tester.widget(find.byType(UnboundedCustomScrollView)); expect(customScrollView.semanticChildCount, 30); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); - final customScrollView2 = - tester.widget(find.byType(UnboundedCustomScrollView)); + final customScrollView2 = tester.widget(find.byType(UnboundedCustomScrollView)); expect(customScrollView2.semanticChildCount, 30); }); @@ -1290,16 +1151,13 @@ void main() { itemScrollController: itemScrollController, ); - final customScrollView = - tester.widget(find.byType(UnboundedCustomScrollView)); + final customScrollView = tester.widget(find.byType(UnboundedCustomScrollView)); expect(customScrollView.semanticChildCount, defaultItemCount); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); - final customScrollView2 = - tester.widget(find.byType(UnboundedCustomScrollView)); + final customScrollView2 = tester.widget(find.byType(UnboundedCustomScrollView)); expect(customScrollView2.semanticChildCount, defaultItemCount); }); @@ -1329,25 +1187,19 @@ void main() { ); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 1')), - const Offset(10, itemHeight + 10)); - expect(tester.getTopRight(find.text('Item 1')), - const Offset(screenWidth - 10, itemHeight + 10)); + expect(tester.getTopLeft(find.text('Item 1')), const Offset(10, itemHeight + 10)); + expect(tester.getTopRight(find.text('Item 1')), const Offset(screenWidth - 10, itemHeight + 10)); - unawaited( - itemScrollController.scrollTo(index: 490, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 490, duration: scrollDuration)); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, -100)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, -100)); await tester.pumpAndSettle(); - expect(tester.getBottomRight(find.text('Item 499')), - const Offset(screenWidth - 10, screenHeight - 10)); + expect(tester.getBottomRight(find.text('Item 499')), const Offset(screenWidth - 10, screenHeight - 10)); }); - testWidgets('padding test - centered not at top', - (WidgetTester tester) async { + testWidgets('padding test - centered not at top', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -1356,19 +1208,15 @@ void main() { padding: const EdgeInsets.all(10), ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, 200)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, 200)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 2')), - const Offset(10, 10 + itemHeight * 2)); - expect(tester.getTopLeft(find.text('Item 3')), - const Offset(10, 10 + itemHeight * 3)); + expect(tester.getTopLeft(find.text('Item 2')), const Offset(10, 10 + itemHeight * 2)); + expect(tester.getTopLeft(find.text('Item 3')), const Offset(10, 10 + itemHeight * 3)); }); - testWidgets('padding - first element centered - scroll up', - (WidgetTester tester) async { + testWidgets('padding - first element centered - scroll up', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -1376,15 +1224,13 @@ void main() { padding: const EdgeInsets.all(10), ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, 100)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, 100)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); }); - testWidgets('padding - last element centered - scroll down', - (WidgetTester tester) async { + testWidgets('padding - last element centered - scroll down', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -1392,12 +1238,10 @@ void main() { padding: const EdgeInsets.all(10), ); - unawaited(itemScrollController.scrollTo( - index: defaultItemCount - 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: defaultItemCount - 1, duration: scrollDuration)); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, -100)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, -100)); await tester.pumpAndSettle(); expect( @@ -1417,12 +1261,13 @@ void main() { ); expect( - tester - .widgetList(find.descendant( - of: find.byType(ScrollablePositionedList), - matching: find.byType(RepaintBoundary))) - .length, - lessThan(5)); + tester + .widgetList( + find.descendant(of: find.byType(ScrollablePositionedList), matching: find.byType(RepaintBoundary)), + ) + .length, + lessThan(5), + ); }); testWidgets('no automatic keep alives', (WidgetTester tester) async { @@ -1436,10 +1281,9 @@ void main() { ); expect( - find.descendant( - of: find.byType(ScrollablePositionedList), - matching: find.byType(AutomaticKeepAlive)), - findsNothing); + find.descendant(of: find.byType(ScrollablePositionedList), matching: find.byType(AutomaticKeepAlive)), + findsNothing, + ); }); testWidgets('Jump to end of list', (WidgetTester tester) async { @@ -1449,61 +1293,49 @@ void main() { itemScrollController.jumpTo(index: defaultItemCount - 1); await tester.pumpAndSettle(); - expect(tester.getBottomLeft(find.text('Item ${defaultItemCount - 1}')).dy, - screenHeight); + expect(tester.getBottomLeft(find.text('Item ${defaultItemCount - 1}')).dy, screenHeight); }); testWidgets('Scroll to end of list', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited(itemScrollController.scrollTo( - index: defaultItemCount - 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: defaultItemCount - 1, duration: scrollDuration)); await tester.pumpAndSettle(); - expect(tester.getBottomLeft(find.text('Item ${defaultItemCount - 1}')).dy, - screenHeight); + expect(tester.getBottomLeft(find.text('Item ${defaultItemCount - 1}')).dy, screenHeight); }); - testWidgets('Scroll to end of list, jump to beginning, jump to end', - (WidgetTester tester) async { + testWidgets('Scroll to end of list, jump to beginning, jump to end', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited(itemScrollController.scrollTo( - index: defaultItemCount - 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: defaultItemCount - 1, duration: scrollDuration)); await tester.pumpAndSettle(); itemScrollController.jumpTo(index: 0); await tester.pumpAndSettle(); itemScrollController.jumpTo(index: defaultItemCount - 1); await tester.pumpAndSettle(); - expect(tester.getBottomLeft(find.text('Item ${defaultItemCount - 1}')).dy, - screenHeight); + expect(tester.getBottomLeft(find.text('Item ${defaultItemCount - 1}')).dy, screenHeight); }); - testWidgets('Jump to end of list, scroll to beginning, scroll to end', - (WidgetTester tester) async { + testWidgets('Jump to end of list, scroll to beginning, scroll to end', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); itemScrollController.jumpTo(index: defaultItemCount - 1); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited(itemScrollController.scrollTo( - index: defaultItemCount - 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: defaultItemCount - 1, duration: scrollDuration)); await tester.pumpAndSettle(); - expect(tester.getBottomLeft(find.text('Item ${defaultItemCount - 1}')).dy, - screenHeight); + expect(tester.getBottomLeft(find.text('Item ${defaultItemCount - 1}')).dy, screenHeight); }); - testWidgets( - 'Jump to end of list, jump to beginning with alignment not at top', - (WidgetTester tester) async { + testWidgets('Jump to end of list, jump to beginning with alignment not at top', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); @@ -1518,11 +1350,9 @@ void main() { testWidgets("Short list, can't scroll past end", (WidgetTester tester) async { final itemScrollController = ItemScrollController(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, itemCount: 3); + await setUpWidgetTest(tester, itemScrollController: itemScrollController, itemCount: 3); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, -10)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, -10)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')).dy, 0); @@ -1536,9 +1366,7 @@ void main() { expect(find.byKey(key), findsOneWidget); }); - testWidgets( - 'Maintain programmatic position (9 half way off top) in page view', - (WidgetTester tester) async { + testWidgets('Maintain programmatic position (9 half way off top) in page view', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); final itemScrollController = ItemScrollController(); @@ -1563,14 +1391,13 @@ void main() { ), const Center( child: Text('Test'), - ) + ), ], ), ), ); - itemScrollController.jumpTo( - index: 9, alignment: -(itemHeight / screenHeight) / 2); + itemScrollController.jumpTo(index: 9, alignment: -(itemHeight / screenHeight) / 2); await tester.pump(); await tester.drag(find.byType(PageView), const Offset(-500, 0)); @@ -1582,19 +1409,16 @@ void main() { expect(tester.getTopLeft(find.text('Item 9')).dy, -itemHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - -(itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + -(itemHeight / screenHeight) / 2, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - (itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, + (itemHeight / screenHeight) / 2, + ); }); - testWidgets('Maintain user scroll position (1 half way off top) in page view', - (WidgetTester tester) async { + testWidgets('Maintain user scroll position (1 half way off top) in page view', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); final itemScrollController = ItemScrollController(); @@ -1619,14 +1443,13 @@ void main() { ), const Center( child: Text('Test'), - ) + ), ], ), ), ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, -itemHeight)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, -itemHeight)); await tester.pumpAndSettle(); final item0Bottom = tester.getBottomRight(find.text('Item 0')).dy; @@ -1641,15 +1464,13 @@ void main() { expect(tester.getBottomRight(find.text('Item 0')).dy, item0Bottom); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - -(itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, + -(itemHeight / screenHeight) / 2, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemTrailingEdge, - (itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemTrailingEdge, + (itemHeight / screenHeight) / 2, + ); }); testWidgets( @@ -1679,7 +1500,7 @@ void main() { ), const Center( child: Text('Test'), - ) + ), ], ), ), @@ -1690,8 +1511,7 @@ void main() { expect(tester.getBottomRight(find.text('Item 9')).dy, itemHeight); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, -itemHeight)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, -itemHeight)); await tester.pumpAndSettle(); final item9Bottom = tester.getBottomRight(find.text('Item 9')).dy; @@ -1706,28 +1526,24 @@ void main() { expect(tester.getBottomRight(find.text('Item 9')).dy, item9Bottom); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - -(itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + -(itemHeight / screenHeight) / 2, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - (itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, + (itemHeight / screenHeight) / 2, + ); }, ); testWidgets('List with no items', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, itemCount: 0); + await setUpWidgetTest(tester, itemScrollController: itemScrollController, itemCount: 0); expect(find.text('Item 0'), findsNothing); }); - testWidgets('Jump to 100 then set itemCount to 0', - (WidgetTester tester) async { + testWidgets('Jump to 100 then set itemCount to 0', (WidgetTester tester) async { tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); @@ -1774,8 +1590,7 @@ void main() { expect(itemPositionsListener.itemPositions.value.isEmpty, isTrue); }); - testWidgets('List positioned with 100 at top then set itemCount to 100', - (WidgetTester tester) async { + testWidgets('List positioned with 100 at top then set itemCount to 100', (WidgetTester tester) async { tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); @@ -1815,8 +1630,7 @@ void main() { expect(tester.getBottomLeft(find.text('Item 99')).dy, screenHeight); }); - testWidgets('List positioned with 499 at bottom then set itemCount to 100', - (WidgetTester tester) async { + testWidgets('List positioned with 499 at bottom then set itemCount to 100', (WidgetTester tester) async { tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); @@ -1862,8 +1676,7 @@ void main() { expect(find.text('Item 100', skipOffstage: false), findsOneWidget); }); - testWidgets('Scroll to 20 without fading small minCacheExtent', - (WidgetTester tester) async { + testWidgets('Scroll to 20 without fading small minCacheExtent', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); await setUpWidgetTest( @@ -1876,8 +1689,7 @@ void main() { var fadeTransition = tester.widget(fadeTransitionFinder); final initialOpacity = fadeTransition.opacity; - unawaited( - itemScrollController.scrollTo(index: 20, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 20, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -1891,8 +1703,7 @@ void main() { expect(find.text('Item 20'), findsOneWidget); }); - testWidgets('Scroll to 100 without fading for large minCacheExtent', - (WidgetTester tester) async { + testWidgets('Scroll to 100 without fading for large minCacheExtent', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( @@ -1906,8 +1717,7 @@ void main() { ); final initialOpacity = fadeTransition.opacity; - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -1920,8 +1730,7 @@ void main() { expect(find.text('Item 100'), findsOneWidget); }); - testWidgets('Position list when not enough above top item to fill viewport', - (WidgetTester tester) async { + testWidgets('Position list when not enough above top item to fill viewport', (WidgetTester tester) async { const alignment = 0.8; await setUpWidgetTest( @@ -1969,15 +1778,13 @@ void main() { key.value = const ValueKey('newKey'); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 100'), findsOneWidget); }); - testWidgets('Double rebuild with scroll controller', - (WidgetTester tester) async { + testWidgets('Double rebuild with scroll controller', (WidgetTester tester) async { tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); final outerKey = ValueNotifier(const ValueKey('outerKey')); @@ -2019,8 +1826,7 @@ void main() { listKey.value = const ValueKey('newListKey'); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 100'), findsOneWidget); @@ -2093,15 +1899,13 @@ void main() { key.value = const ValueKey('newKey'); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 70, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 70, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 70'), findsOneWidget); }); - testWidgets('Scroll after rebuild when resusing state', - (WidgetTester tester) async { + testWidgets('Scroll after rebuild when resusing state', (WidgetTester tester) async { tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); final containerKey = ValueNotifier(const ValueKey('key')); @@ -2137,15 +1941,13 @@ void main() { containerKey.value = const ValueKey('newKey'); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 70, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 70, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 70'), findsOneWidget); }); - testWidgets('Scroll after changing scroll controller', - (WidgetTester tester) async { + testWidgets('Scroll after changing scroll controller', (WidgetTester tester) async { tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); @@ -2183,65 +1985,63 @@ void main() { expect(itemScrollController0.isAttached, false); expect(itemScrollController1.isAttached, true); - unawaited( - itemScrollController1.scrollTo(index: 70, duration: scrollDuration)); + unawaited(itemScrollController1.scrollTo(index: 70, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 70'), findsOneWidget); }); - testWidgets('Scroll after swapping scroll controllers', - (WidgetTester tester) async { + testWidgets('Scroll after swapping scroll controllers', (WidgetTester tester) async { tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); final itemScrollController0 = ItemScrollController(); final itemScrollController1 = ItemScrollController(); - final topItemScrollControllerListenable = - ValueNotifier(itemScrollController0); - final bottomItemScrollControllerListenable = - ValueNotifier(itemScrollController1); - - await tester.pumpWidget(MaterialApp( - home: Column( - children: [ - Expanded( - child: ValueListenableBuilder( - valueListenable: topItemScrollControllerListenable, - builder: (context, itemScrollController, child) { - return ScrollablePositionedList.builder( - itemCount: 100, - itemScrollController: itemScrollController, - itemBuilder: (context, index) { - return SizedBox( - height: itemHeight, - child: Text('Item $index'), - ); - }, - ); - }, + final topItemScrollControllerListenable = ValueNotifier(itemScrollController0); + final bottomItemScrollControllerListenable = ValueNotifier(itemScrollController1); + + await tester.pumpWidget( + MaterialApp( + home: Column( + children: [ + Expanded( + child: ValueListenableBuilder( + valueListenable: topItemScrollControllerListenable, + builder: (context, itemScrollController, child) { + return ScrollablePositionedList.builder( + itemCount: 100, + itemScrollController: itemScrollController, + itemBuilder: (context, index) { + return SizedBox( + height: itemHeight, + child: Text('Item $index'), + ); + }, + ); + }, + ), ), - ), - Expanded( - child: ValueListenableBuilder( - valueListenable: bottomItemScrollControllerListenable, - builder: (context, itemScrollController, child) { - return ScrollablePositionedList.builder( - itemCount: 100, - itemScrollController: itemScrollController, - itemBuilder: (context, index) { - return SizedBox( - height: itemHeight, - child: Text('Item $index'), - ); - }, - ); - }, + Expanded( + child: ValueListenableBuilder( + valueListenable: bottomItemScrollControllerListenable, + builder: (context, itemScrollController, child) { + return ScrollablePositionedList.builder( + itemCount: 100, + itemScrollController: itemScrollController, + itemBuilder: (context, index) { + return SizedBox( + height: itemHeight, + child: Text('Item $index'), + ); + }, + ); + }, + ), ), - ), - ], + ], + ), ), - )); + ); await tester.pumpAndSettle(); expect(itemScrollController0.isAttached, true); @@ -2254,10 +2054,8 @@ void main() { expect(itemScrollController0.isAttached, true); expect(itemScrollController1.isAttached, true); - unawaited( - itemScrollController1.scrollTo(index: 70, duration: scrollDuration)); - unawaited( - itemScrollController0.scrollTo(index: 50, duration: scrollDuration)); + unawaited(itemScrollController1.scrollTo(index: 70, duration: scrollDuration)); + unawaited(itemScrollController0.scrollTo(index: 50, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 70'), findsOneWidget); diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_positioned_list_test.dart index f6dc2b5cef..f947d38bf7 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_positioned_list_test.dart @@ -68,24 +68,17 @@ void main() { expect(find.text('Separator 2'), findsNothing); expect(find.text('Item 3'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemTrailingEdge, - _screenProportion(numberOfItems: 3, numberOfSeparators: 2)); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 2).itemTrailingEdge, + _screenProportion(numberOfItems: 3, numberOfSeparators: 2), + ); }); - testWidgets('Short list centered at 1 scrolled up', - (WidgetTester tester) async { + testWidgets('Short list centered at 1 scrolled up', (WidgetTester tester) async { await setUpWidgetTest(tester, itemCount: 3, topItem: 1); - await tester.drag( - find.byType(PositionedList), const Offset(0, itemHeight * 2)); + await tester.drag(find.byType(PositionedList), const Offset(0, itemHeight * 2)); await tester.pumpAndSettle(); expect(find.text('Item 0'), findsOneWidget); @@ -96,16 +89,11 @@ void main() { expect(find.text('Separator 2'), findsNothing); expect(find.text('Item 3'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemTrailingEdge, - _screenProportion(numberOfItems: 3, numberOfSeparators: 2)); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 2).itemTrailingEdge, + _screenProportion(numberOfItems: 3, numberOfSeparators: 2), + ); }); testWidgets('List positioned with 0 at top', (WidgetTester tester) async { @@ -118,22 +106,13 @@ void main() { expect(find.text('Separator 6'), findsNothing); expect(find.text('Item 7'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemTrailingEdge, - 1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemTrailingEdge, + 1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 6) - .itemTrailingEdge, - 1); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 6).itemTrailingEdge, 1); }); testWidgets('List positioned with 5 at top', (WidgetTester tester) async { @@ -149,21 +128,15 @@ void main() { expect(find.text('Item 11'), findsOneWidget); expect(find.text('Separator 11'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 6) - .itemLeadingEdge, - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 6).itemLeadingEdge, + _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 11) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 11).itemTrailingEdge, + 1, + ); }); testWidgets('List positioned with 20 at bottom', (WidgetTester tester) async { @@ -179,82 +152,60 @@ void main() { expect(find.text('Separator 12'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 19) - .itemTrailingEdge, - 1 - _screenProportion(numberOfItems: 0, numberOfSeparators: 1)); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemLeadingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 19).itemTrailingEdge, + 1 - _screenProportion(numberOfItems: 0, numberOfSeparators: 1), + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemLeadingEdge, 1); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 13) - .itemLeadingEdge, - _screenProportion(numberOfItems: -0.5, numberOfSeparators: 0)); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 13).itemLeadingEdge, + _screenProportion(numberOfItems: -0.5, numberOfSeparators: 0), + ); }); - testWidgets('List positioned with item 20 at halfway', - (WidgetTester tester) async { + testWidgets('List positioned with item 20 at halfway', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 20, anchor: 0.5); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemLeadingEdge, - 0.5); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemLeadingEdge, + 0.5, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemTrailingEdge, - 0.5 + itemHeight / screenHeight); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemTrailingEdge, + 0.5 + itemHeight / screenHeight, + ); }); - testWidgets('List positioned with item 20 half off top of screen', - (WidgetTester tester) async { - await setUpWidgetTest(tester, - topItem: 20, anchor: -(itemHeight / screenHeight) / 2); + testWidgets('List positioned with item 20 half off top of screen', (WidgetTester tester) async { + await setUpWidgetTest(tester, topItem: 20, anchor: -(itemHeight / screenHeight) / 2); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemLeadingEdge, - _screenProportion(numberOfItems: -0.5, numberOfSeparators: 0)); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemLeadingEdge, + _screenProportion(numberOfItems: -0.5, numberOfSeparators: 0), + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemTrailingEdge, - _screenProportion(numberOfItems: 0.5, numberOfSeparators: 0)); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemTrailingEdge, + _screenProportion(numberOfItems: 0.5, numberOfSeparators: 0), + ); }); - testWidgets('List positioned with 5 at top then scroll up 2 items', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at top then scroll up 2 items', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 5); - await tester.drag(find.byType(PositionedList), - const Offset(0, 2 * (itemHeight + separatorHeight))); + await tester.drag(find.byType(PositionedList), const Offset(0, 2 * (itemHeight + separatorHeight))); await tester.pump(); expect(find.text('Separator 2'), findsNothing); expect(find.text('Item 3'), findsOneWidget); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - _screenProportion(numberOfItems: -1, numberOfSeparators: -1)); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 3) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, + _screenProportion(numberOfItems: -1, numberOfSeparators: -1), + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 3).itemLeadingEdge, 0); }); } -double _screenProportion( - {required double numberOfItems, required double numberOfSeparators}) => - (numberOfItems * itemHeight + numberOfSeparators * separatorHeight) / - screenHeight; +double _screenProportion({required double numberOfItems, required double numberOfSeparators}) => + (numberOfItems * itemHeight + numberOfSeparators * separatorHeight) / screenHeight; diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_scrollable_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_scrollable_positioned_list_test.dart index d4d99595dd..d6ef7bc527 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_scrollable_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_scrollable_positioned_list_test.dart @@ -75,30 +75,17 @@ void main() { expect(find.text('Separator 6'), findsNothing); expect(find.text('Item 7'), findsNothing); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemTrailingEdge, - 1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 5).itemTrailingEdge, + 1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 6) - .itemTrailingEdge, - 1); - expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 7), - isEmpty); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 6).itemTrailingEdge, 1); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 7), isEmpty); }); - testWidgets('List positioned with 0 at top - use default values', - (WidgetTester tester) async { + testWidgets('List positioned with 0 at top - use default values', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); @@ -126,32 +113,19 @@ void main() { expect(find.text('Separator 6'), findsNothing); expect(find.text('Item 7'), findsNothing); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemTrailingEdge, - 1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 5).itemTrailingEdge, + 1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 6) - .itemTrailingEdge, - 1); - expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 7), - isEmpty); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 6).itemTrailingEdge, 1); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 7), isEmpty); }); testWidgets('List positioned with 5 at top', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, initialIndex: 5); + await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener, initialIndex: 5); expect(find.text('Item 4'), findsNothing); expect(find.text('Separator 4'), findsNothing); @@ -161,58 +135,44 @@ void main() { expect(find.text('Item 11'), findsOneWidget); expect(find.text('Separator 11'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 4), - isEmpty); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 4), isEmpty); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); }); testWidgets('List positioned with 9 at middle', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, - initialIndex: 9, - initialAlignment: 0.5); + await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener, initialIndex: 9, initialAlignment: 0.5); expect(tester.getTopLeft(find.text('Item 9')).dy, screenHeight / 2); - expect(tester.getTopLeft(find.text('Item 8')).dy, - screenHeight / 2 - itemHeight - separatorHeight); - expect(tester.getTopLeft(find.text('Item 10')).dy, - screenHeight / 2 + itemHeight + separatorHeight); + expect(tester.getTopLeft(find.text('Item 8')).dy, screenHeight / 2 - itemHeight - separatorHeight); + expect(tester.getTopLeft(find.text('Item 10')).dy, screenHeight / 2 + itemHeight + separatorHeight); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - 0.5); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + 0.5, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 8) - .itemLeadingEdge, - 0.5 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 8).itemLeadingEdge, + 0.5 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 10) - .itemLeadingEdge, - 0.5 + _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 10).itemLeadingEdge, + 0.5 + _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); }); testWidgets('Scroll to 9 half way off top', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); final itemScrollController = ItemScrollController(); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, - itemScrollController: itemScrollController); - - unawaited(itemScrollController.scrollTo( - index: 9, - duration: scrollDuration, - alignment: -(itemHeight / screenHeight) / 2)); + await setUpWidgetTest( + tester, + itemPositionsListener: itemPositionsListener, + itemScrollController: itemScrollController, + ); + + unawaited( + itemScrollController.scrollTo(index: 9, duration: scrollDuration, alignment: -(itemHeight / screenHeight) / 2), + ); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); @@ -220,76 +180,68 @@ void main() { expect(tester.getTopLeft(find.text('Item 9')).dy, -itemHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - _screenProportion(numberOfItems: -0.5, numberOfSeparators: 0)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + _screenProportion(numberOfItems: -0.5, numberOfSeparators: 0), + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - _screenProportion(numberOfItems: 0.5, numberOfSeparators: 0)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, + _screenProportion(numberOfItems: 0.5, numberOfSeparators: 0), + ); }); testWidgets('Jump to 9 half way off top', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); final itemScrollController = ItemScrollController(); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, - itemScrollController: itemScrollController); + await setUpWidgetTest( + tester, + itemPositionsListener: itemPositionsListener, + itemScrollController: itemScrollController, + ); - itemScrollController.jumpTo( - index: 9, alignment: -(itemHeight / screenHeight) / 2); + itemScrollController.jumpTo(index: 9, alignment: -(itemHeight / screenHeight) / 2); await tester.pump(); expect(tester.getTopLeft(find.text('Item 9')).dy, -itemHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - _screenProportion(numberOfItems: -0.5, numberOfSeparators: 0)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + _screenProportion(numberOfItems: -0.5, numberOfSeparators: 0), + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - _screenProportion(numberOfItems: 0.5, numberOfSeparators: 0)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, + _screenProportion(numberOfItems: 0.5, numberOfSeparators: 0), + ); }); - testWidgets('List positioned with 9 at middle scroll to 16 at bottom', - (WidgetTester tester) async { + testWidgets('List positioned with 9 at middle scroll to 16 at bottom', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener, - initialIndex: 9, - initialAlignment: 0.5); - - unawaited(itemScrollController.scrollTo( - index: 16, duration: scrollDuration, alignment: 1)); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + initialIndex: 9, + initialAlignment: 0.5, + ); + + unawaited(itemScrollController.scrollTo(index: 16, duration: scrollDuration, alignment: 1)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); - expect(tester.getBottomRight(find.text('Item 15')).dy, - screenHeight - separatorHeight); + expect(tester.getBottomRight(find.text('Item 15')).dy, screenHeight - separatorHeight); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 15) - .itemTrailingEdge, - 1 - _screenProportion(numberOfItems: 0, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 15).itemTrailingEdge, + 1 - _screenProportion(numberOfItems: 0, numberOfSeparators: 1), + ); }); testWidgets('physics', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - physics: const BouncingScrollPhysics()); + await setUpWidgetTest(tester, itemScrollController: itemScrollController, physics: const BouncingScrollPhysics()); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, 50)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, 50)); await tester.pump(const Duration(milliseconds: 200)); expect(tester.getTopLeft(find.text('Item 0')).dy, greaterThan(0)); @@ -297,14 +249,12 @@ void main() { await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')).dy, 0); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); itemScrollController.jumpTo(index: 0); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, 50)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, 50)); await tester.pump(const Duration(milliseconds: 200)); expect(tester.getTopLeft(find.text('Item 0')).dy, greaterThan(0)); @@ -316,15 +266,16 @@ void main() { testWidgets('correct index semantics', (WidgetTester tester) async { await setUpWidgetTest(tester, initialIndex: 5); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, itemHeight * 4)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, itemHeight * 4)); await tester.pumpAndSettle(); - final indexSemantics3 = tester.widget(find.ancestor( - of: find.text('Item 3'), matching: find.byType(IndexedSemantics))); + final indexSemantics3 = tester.widget( + find.ancestor(of: find.text('Item 3'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics3.index, 3); - final indexSemantics4 = tester.widget(find.ancestor( - of: find.text('Item 4'), matching: find.byType(IndexedSemantics))); + final indexSemantics4 = tester.widget( + find.ancestor(of: find.text('Item 4'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics4.index, 4); }); @@ -339,8 +290,7 @@ void main() { expect(find.byType(IndexedSemantics), findsNothing); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.byType(IndexedSemantics), findsNothing); @@ -355,16 +305,13 @@ void main() { itemScrollController: itemScrollController, ); - final customScrollView = - tester.widget(find.byType(UnboundedCustomScrollView)); + final customScrollView = tester.widget(find.byType(UnboundedCustomScrollView)); expect(customScrollView.semanticChildCount, 30); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); - final customScrollView2 = - tester.widget(find.byType(UnboundedCustomScrollView)); + final customScrollView2 = tester.widget(find.byType(UnboundedCustomScrollView)); expect(customScrollView2.semanticChildCount, 30); }); @@ -375,16 +322,13 @@ void main() { itemScrollController: itemScrollController, ); - final customScrollView = - tester.widget(find.byType(UnboundedCustomScrollView)); + final customScrollView = tester.widget(find.byType(UnboundedCustomScrollView)); expect(customScrollView.semanticChildCount, defaultItemCount); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); - final customScrollView2 = - tester.widget(find.byType(UnboundedCustomScrollView)); + final customScrollView2 = tester.widget(find.byType(UnboundedCustomScrollView)); expect(customScrollView2.semanticChildCount, defaultItemCount); }); @@ -397,25 +341,19 @@ void main() { ); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 1')), - const Offset(10, itemHeight + 10 + separatorHeight)); - expect(tester.getTopRight(find.text('Item 1')), - const Offset(screenWidth - 10, itemHeight + 10 + separatorHeight)); + expect(tester.getTopLeft(find.text('Item 1')), const Offset(10, itemHeight + 10 + separatorHeight)); + expect(tester.getTopRight(find.text('Item 1')), const Offset(screenWidth - 10, itemHeight + 10 + separatorHeight)); - unawaited( - itemScrollController.scrollTo(index: 494, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 494, duration: scrollDuration)); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, -500)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, -500)); await tester.pumpAndSettle(); - expect(tester.getBottomRight(find.text('Item 499')), - const Offset(screenWidth - 10, screenHeight - 10)); + expect(tester.getBottomRight(find.text('Item 499')), const Offset(screenWidth - 10, screenHeight - 10)); }); - testWidgets('padding test - centered sliver not at top', - (WidgetTester tester) async { + testWidgets('padding test - centered sliver not at top', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -424,17 +362,15 @@ void main() { padding: const EdgeInsets.all(10), ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, 200)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, 200)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 2')), - const Offset(10, 10 + 2 * (separatorHeight + itemHeight))); + expect(tester.getTopLeft(find.text('Item 2')), const Offset(10, 10 + 2 * (separatorHeight + itemHeight))); expect( - tester.getTopRight(find.text('Item 3')), - const Offset( - screenWidth - 10, 10 + 3 * (itemHeight + separatorHeight))); + tester.getTopRight(find.text('Item 3')), + const Offset(screenWidth - 10, 10 + 3 * (itemHeight + separatorHeight)), + ); }); testWidgets('no repaint bounderies', (WidgetTester tester) async { @@ -448,12 +384,13 @@ void main() { ); expect( - tester - .widgetList(find.descendant( - of: find.byType(ScrollablePositionedList), - matching: find.byType(RepaintBoundary))) - .length, - lessThan(5)); + tester + .widgetList( + find.descendant(of: find.byType(ScrollablePositionedList), matching: find.byType(RepaintBoundary)), + ) + .length, + lessThan(5), + ); }); testWidgets('no automatic keep alives', (WidgetTester tester) async { @@ -467,10 +404,9 @@ void main() { ); expect( - find.descendant( - of: find.byType(ScrollablePositionedList), - matching: find.byType(AutomaticKeepAlive)), - findsNothing); + find.descendant(of: find.byType(ScrollablePositionedList), matching: find.byType(AutomaticKeepAlive)), + findsNothing, + ); }); testWidgets('List can be keyed', (WidgetTester tester) async { @@ -481,8 +417,7 @@ void main() { expect(find.byKey(key), findsOneWidget); }); - testWidgets('Empty list then update to single item list', - (WidgetTester tester) async { + testWidgets('Empty list then update to single item list', (WidgetTester tester) async { tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); @@ -522,8 +457,7 @@ void main() { expect(find.text('Separator 0'), findsNothing); }); - testWidgets('ItemPositions: Empty list then update to 10 items list', - (WidgetTester tester) async { + testWidgets('ItemPositions: Empty list then update to 10 items list', (WidgetTester tester) async { tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); @@ -570,30 +504,16 @@ void main() { expect(find.text('Item 7'), findsNothing); expect(itemPositionsListener.itemPositions.value, isNotEmpty); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemTrailingEdge, - 1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 5).itemTrailingEdge, + 1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 6) - .itemTrailingEdge, - 1); - expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 7), - isEmpty); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 6).itemTrailingEdge, 1); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 7), isEmpty); }); } -double _screenProportion( - {required double numberOfItems, required double numberOfSeparators}) => - (numberOfItems * itemHeight + numberOfSeparators * separatorHeight) / - screenHeight; +double _screenProportion({required double numberOfItems, required double numberOfSeparators}) => + (numberOfItems * itemHeight + numberOfSeparators * separatorHeight) / screenHeight; diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/seperated_horizontal_scrollable_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/seperated_horizontal_scrollable_positioned_list_test.dart index 88b0fae0ac..b0c151666e 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/seperated_horizontal_scrollable_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/seperated_horizontal_scrollable_positioned_list_test.dart @@ -56,105 +56,90 @@ void main() { await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener); expect(tester.getTopLeft(find.text('Item 0')).dx, 0); - expect(tester.getBottomLeft(find.text('Item 1')).dx, - itemWidth + separatorWidth); + expect(tester.getBottomLeft(find.text('Item 1')).dx, itemWidth + separatorWidth); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 1) - .itemLeadingEdge, - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 1).itemLeadingEdge, + _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); }); testWidgets('Scroll to 2 (already on screen)', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 2, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 2, duration: scrollDuration)); await tester.pump(); await tester.pump(scrollDuration); expect(find.text('Item 1'), findsNothing); expect(tester.getTopLeft(find.text('Item 2')).dx, 0); - expect( - tester.getTopLeft(find.text('Item 3')).dx, itemWidth + separatorWidth); + expect(tester.getTopLeft(find.text('Item 3')).dx, itemWidth + separatorWidth); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 3) - .itemLeadingEdge, - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 3).itemLeadingEdge, + _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); }); - testWidgets('Scroll to 100 (not already on screen)', - (WidgetTester tester) async { + testWidgets('Scroll to 100 (not already on screen)', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 99'), findsNothing); expect(find.text('Item 100'), findsOneWidget); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 101) - .itemLeadingEdge, - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 101).itemLeadingEdge, + _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); }); testWidgets('Jump to 100', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); itemScrollController.jumpTo(index: 100); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 100')).dx, 0); - expect(tester.getTopLeft(find.text('Item 101')).dx, - itemWidth + separatorWidth); + expect(tester.getTopLeft(find.text('Item 101')).dx, itemWidth + separatorWidth); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 101) - .itemLeadingEdge, - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 101).itemLeadingEdge, + _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); }); - testWidgets('padding test - centered sliver at left', - (WidgetTester tester) async { + testWidgets('padding test - centered sliver at left', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -163,25 +148,22 @@ void main() { ); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 1')), - const Offset(itemWidth + 10 + separatorWidth, 10)); - expect(tester.getBottomRight(find.text('Item 1')), - const Offset(10 + itemWidth * 2 + separatorWidth, screenHeight - 10)); + expect(tester.getTopLeft(find.text('Item 1')), const Offset(itemWidth + 10 + separatorWidth, 10)); + expect( + tester.getBottomRight(find.text('Item 1')), + const Offset(10 + itemWidth * 2 + separatorWidth, screenHeight - 10), + ); - unawaited( - itemScrollController.scrollTo(index: 494, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 494, duration: scrollDuration)); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(-500, 0)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(-500, 0)); await tester.pumpAndSettle(); - expect(tester.getBottomRight(find.text('Item 499')), - const Offset(screenWidth - 10, screenHeight - 10)); + expect(tester.getBottomRight(find.text('Item 499')), const Offset(screenWidth - 10, screenHeight - 10)); }); - testWidgets('padding test - centered sliver not at left', - (WidgetTester tester) async { + testWidgets('padding test - centered sliver not at left', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); await setUpWidgetTest( @@ -192,27 +174,19 @@ void main() { padding: const EdgeInsets.all(10), ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(300, 0)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(300, 0)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 2')), - const Offset(10 + 2 * (itemWidth + separatorWidth), 10)); - expect(tester.getTopLeft(find.text('Item 3')), - const Offset(10 + 3 * (itemWidth + separatorWidth), 10)); + expect(tester.getTopLeft(find.text('Item 2')), const Offset(10 + 2 * (itemWidth + separatorWidth), 10)); + expect(tester.getTopLeft(find.text('Item 3')), const Offset(10 + 3 * (itemWidth + separatorWidth), 10)); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - closeTo( - 10 / screenWidth + 2 * ((itemWidth + separatorWidth) / screenWidth), - tolerance)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, + closeTo(10 / screenWidth + 2 * ((itemWidth + separatorWidth) / screenWidth), tolerance), + ); }); } -double _screenProportion( - {required double numberOfItems, required double numberOfSeparators}) => - (numberOfItems * itemWidth + numberOfSeparators * separatorWidth) / - screenHeight; +double _screenProportion({required double numberOfItems, required double numberOfSeparators}) => + (numberOfItems * itemWidth + numberOfSeparators * separatorWidth) / screenHeight; diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_position_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_position_list_test.dart index 8cf4303c4f..2c13c0d601 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_position_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_position_list_test.dart @@ -32,28 +32,28 @@ void main() { MaterialApp( // Use flex layout to ensure that the minimum height is not limited to // screenHeight. - home: Column(children: [ - // Use Constrained to make max height not more than screenHeight - ConstrainedBox( - constraints: const BoxConstraints( - maxHeight: screenHeight, maxWidth: screenWidth), - child: PositionedList( - key: key, - itemCount: itemCount, - positionedIndex: topItem, - alignment: anchor, - controller: scrollController, - itemBuilder: (context, index) => SizedBox( - height: itemHeight, - child: Text('Item $index'), + home: Column( + children: [ + // Use Constrained to make max height not more than screenHeight + ConstrainedBox( + constraints: const BoxConstraints(maxHeight: screenHeight, maxWidth: screenWidth), + child: PositionedList( + key: key, + itemCount: itemCount, + positionedIndex: topItem, + alignment: anchor, + controller: scrollController, + itemBuilder: (context, index) => SizedBox( + height: itemHeight, + child: Text('Item $index'), + ), + itemPositionsNotifier: itemPositionsNotifier as ItemPositionsNotifier, + shrinkWrap: true, + reverse: reverse, ), - itemPositionsNotifier: - itemPositionsNotifier as ItemPositionsNotifier, - shrinkWrap: true, - reverse: reverse, ), - ), - ]), + ], + ), ), ); } @@ -64,28 +64,21 @@ void main() { await setUpWidgetTest(tester, itemCount: itemCount, key: key); await tester.pump(); - expect( - tester.getBottomRight(find.text('Item 4')).dy, itemHeight * itemCount); + expect(tester.getBottomRight(find.text('Item 4')).dy, itemHeight * itemCount); expect(find.text('Item 4'), findsOneWidget); expect(find.text('Item 5'), findsNothing); final positionList = find.byKey(key); expect(tester.getBottomRight(positionList).dy, itemHeight * itemCount); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemTrailingEdge, - 1.0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemTrailingEdge, + 1.0, + ); }); - testWidgets('List positioned with 0 at top and shrink wrap', - (WidgetTester tester) async { + testWidgets('List positioned with 0 at top and shrink wrap', (WidgetTester tester) async { await setUpWidgetTest(tester); await tester.pump(); @@ -93,30 +86,16 @@ void main() { expect(find.text('Item 9'), findsOneWidget); expect(find.text('Item 10'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 10).itemLeadingEdge, 1); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 10) - .itemLeadingEdge, - 1); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 10) - .itemTrailingEdge, - 11 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 10).itemTrailingEdge, + 11 / 10, + ); }); - testWidgets('List positioned with 5 at top and shrink wrap', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at top and shrink wrap', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 5); await tester.pump(); @@ -126,29 +105,18 @@ void main() { expect(find.text('Item 15'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemLeadingEdge, - -1 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemTrailingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemLeadingEdge, + -1 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemTrailingEdge, 0); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 14) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 14).itemTrailingEdge, + 1, + ); }); - testWidgets('List positioned with 20 at bottom and shrink wrap', - (WidgetTester tester) async { + testWidgets('List positioned with 20 at bottom and shrink wrap', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 20, anchor: 1); await tester.pump(); @@ -156,69 +124,50 @@ void main() { expect(find.text('Item 19'), findsOneWidget); expect(find.text('Item 10'), findsOneWidget); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 10).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 10) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 19) - .itemLeadingEdge, - 9 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 19) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 19).itemLeadingEdge, + 9 / 10, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemLeadingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 19).itemTrailingEdge, + 1, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemLeadingEdge, 1); }); - testWidgets('List positioned with 20 at halfway and shrink wrap', - (WidgetTester tester) async { + testWidgets('List positioned with 20 at halfway and shrink wrap', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 20, anchor: 0.5); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemLeadingEdge, - 0.5); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemLeadingEdge, + 0.5, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemTrailingEdge, - 0.5 + itemHeight / screenHeight); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemTrailingEdge, + 0.5 + itemHeight / screenHeight, + ); }); - testWidgets('List positioned with 20 half off top of screen and shrink wrap', - (WidgetTester tester) async { - await setUpWidgetTest(tester, - topItem: 20, anchor: -(itemHeight / screenHeight) / 2); + testWidgets('List positioned with 20 half off top of screen and shrink wrap', (WidgetTester tester) async { + await setUpWidgetTest(tester, topItem: 20, anchor: -(itemHeight / screenHeight) / 2); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemLeadingEdge, - -(itemHeight / screenHeight) / 2); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemLeadingEdge, + -(itemHeight / screenHeight) / 2, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemTrailingEdge, - (itemHeight / screenHeight) / 2); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemTrailingEdge, + (itemHeight / screenHeight) / 2, + ); }); - testWidgets('List positioned with 5 at top then scroll up 2 and shrink wrap', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at top then scroll up 2 and shrink wrap', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 5); - await tester.drag( - find.byType(PositionedList), const Offset(0, itemHeight * 2)); + await tester.drag(find.byType(PositionedList), const Offset(0, itemHeight * 2)); await tester.pump(); expect(find.text('Item 2'), findsNothing); @@ -227,45 +176,33 @@ void main() { expect(find.text('Item 13'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - -1 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 3) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, + -1 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 3).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 12) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 12).itemTrailingEdge, + 1, + ); }); - testWidgets( - 'List positioned with 5 at top then scroll down 1/2 and shrink wrap', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at top then scroll down 1/2 and shrink wrap', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 5); - await tester.drag( - find.byType(PositionedList), const Offset(0, -1 / 2 * itemHeight)); + await tester.drag(find.byType(PositionedList), const Offset(0, -1 / 2 * itemHeight)); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemTrailingEdge, - 1 / 20); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemTrailingEdge, + 1 / 20, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 14) - .itemLeadingEdge, - 17 / 20); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 14).itemLeadingEdge, + 17 / 20, + ); }); - testWidgets('List positioned with 0 at top scroll up 5 and shrink wrap', - (WidgetTester tester) async { + testWidgets('List positioned with 0 at top scroll up 5 and shrink wrap', (WidgetTester tester) async { final scrollController = ScrollController(); await setUpWidgetTest(tester, scrollController: scrollController); await tester.pump(); @@ -279,24 +216,18 @@ void main() { expect(find.text('Item 14'), findsOneWidget); expect(find.text('Item 15'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemLeadingEdge, - -1 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemLeadingEdge, + -1 / 10, + ); }); testWidgets( '''List positioned with 5 at top then scroll up 2 programatically and shrink wrap''', (WidgetTester tester) async { final scrollController = ScrollController(); - await setUpWidgetTest(tester, - topItem: 5, scrollController: scrollController); + await setUpWidgetTest(tester, topItem: 5, scrollController: scrollController); scrollController.jumpTo(-2 * itemHeight); await tester.pump(); @@ -307,20 +238,17 @@ void main() { expect(find.text('Item 13'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - -1 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, + -1 / 10, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 3) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 3).itemLeadingEdge, + 0, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 12) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 12).itemTrailingEdge, + 1, + ); }, ); @@ -328,93 +256,70 @@ void main() { '''List positioned with 5 at top then scroll down 20 programatically and shrink wrap''', (WidgetTester tester) async { final scrollController = ScrollController(); - await setUpWidgetTest(tester, - topItem: 5, scrollController: scrollController); + await setUpWidgetTest(tester, topItem: 5, scrollController: scrollController); scrollController.jumpTo(itemHeight * 20); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 23) - .itemLeadingEdge, - -2 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 23).itemLeadingEdge, + -2 / 10, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 24) - .itemLeadingEdge, - -1 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 24).itemLeadingEdge, + -1 / 10, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 25) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 25).itemLeadingEdge, + 0, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemLeadingEdge, - -21 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemLeadingEdge, + -21 / 10, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - -20 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, + -20 / 10, + ); }, ); - testWidgets( - 'List positioned with 5 at top and initial scroll offset and shrink wrap', - (WidgetTester tester) async { - final scrollController = - ScrollController(initialScrollOffset: -2 * itemHeight); - await setUpWidgetTest(tester, - topItem: 5, scrollController: scrollController); + testWidgets('List positioned with 5 at top and initial scroll offset and shrink wrap', (WidgetTester tester) async { + final scrollController = ScrollController(initialScrollOffset: -2 * itemHeight); + await setUpWidgetTest(tester, topItem: 5, scrollController: scrollController); expect(find.text('Item 2'), findsNothing); expect(find.text('Item 3'), findsOneWidget); expect(find.text('Item 12'), findsOneWidget); expect(find.text('Item 13'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 3).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 3) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 12) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 12).itemTrailingEdge, + 1, + ); }); - testWidgets('short List with reverse and shrink wrap', - (WidgetTester tester) async { + testWidgets('short List with reverse and shrink wrap', (WidgetTester tester) async { const itemCount = 5; const key = Key('short_list'); - await setUpWidgetTest(tester, - itemCount: itemCount, key: key, reverse: true); + await setUpWidgetTest(tester, itemCount: itemCount, key: key, reverse: true); await tester.pump(); expect(find.text('Item 4'), findsOneWidget); expect(find.text('Item 5'), findsNothing); - expect( - tester.getBottomRight(find.text('Item 0')).dy, itemHeight * itemCount); + expect(tester.getBottomRight(find.text('Item 0')).dy, itemHeight * itemCount); expect(tester.getTopLeft(find.text('Item 4')).dy, 0); final positionList = find.byKey(key); expect(tester.getBottomRight(positionList).dy, itemHeight * itemCount); expect(tester.getTopLeft(positionList).dy, 0); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemTrailingEdge, - 1.0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemTrailingEdge, + 1.0, + ); }); testWidgets('test nested positioned list', (WidgetTester tester) async { @@ -432,13 +337,14 @@ void main() { itemBuilder: (context, index) { if (index == 0) { return PositionedList( - key: key, - itemCount: itemCount, - shrinkWrap: true, - itemBuilder: (context, idx) => SizedBox( - height: itemHeight, - child: Text('Item $idx'), - )); + key: key, + itemCount: itemCount, + shrinkWrap: true, + itemBuilder: (context, idx) => SizedBox( + height: itemHeight, + child: Text('Item $idx'), + ), + ); } else { return SizedBox( height: itemHeight, @@ -461,15 +367,10 @@ void main() { expect(tester.getBottomRight(positionList).dy, itemHeight * itemCount); expect(tester.getTopLeft(positionList).dy, 0); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemTrailingEdge, - 5.0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemTrailingEdge, + 5.0, + ); }); } diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_scrollable_position_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_scrollable_position_list_test.dart index 37830e00c9..7b0655b150 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_scrollable_position_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_scrollable_position_list_test.dart @@ -29,31 +29,31 @@ void main() { MaterialApp( // Use flex layout to ensure that the minimum height is not limited to // screenHeight. - home: Column(children: [ - // Use Constrained to make max height not more than screenHeight - ConstrainedBox( - constraints: const BoxConstraints( - maxHeight: screenHeight, maxWidth: screenWidth), - child: ScrollablePositionedList.builder( - itemCount: itemCount, - initialScrollIndex: initialIndex, - itemScrollController: itemScrollController, - itemBuilder: (context, index) => SizedBox( - height: itemHeight, - child: Text('Item $index'), + home: Column( + children: [ + // Use Constrained to make max height not more than screenHeight + ConstrainedBox( + constraints: const BoxConstraints(maxHeight: screenHeight, maxWidth: screenWidth), + child: ScrollablePositionedList.builder( + itemCount: itemCount, + initialScrollIndex: initialIndex, + itemScrollController: itemScrollController, + itemBuilder: (context, index) => SizedBox( + height: itemHeight, + child: Text('Item $index'), + ), + itemPositionsListener: itemPositionsListener, + shrinkWrap: true, + padding: padding, ), - itemPositionsListener: itemPositionsListener, - shrinkWrap: true, - padding: padding, ), - ), - ]), + ], + ), ), ); } - testWidgets('List positioned with 0 at top and shrink wrap', - (WidgetTester tester) async { + testWidgets('List positioned with 0 at top and shrink wrap', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener); @@ -61,123 +61,95 @@ void main() { expect(tester.getBottomRight(find.text('Item 9')).dy, screenHeight); expect(find.text('Item 10'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); - testWidgets('Scroll to 1 then 2 (both already on screen) with shrink wrap', - (WidgetTester tester) async { + testWidgets('Scroll to 1 then 2 (both already on screen) with shrink wrap', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 1, duration: scrollDuration)); await tester.pump(); await tester.pump(scrollDuration); expect(find.text('Item 0'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 1) - .itemLeadingEdge, - 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 1).itemLeadingEdge, 0); expect(tester.getTopLeft(find.text('Item 1')).dy, 0); - unawaited( - itemScrollController.scrollTo(index: 2, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 2, duration: scrollDuration)); await tester.pump(); await tester.pump(scrollDuration); expect(find.text('Item 1'), findsNothing); expect(tester.getTopLeft(find.text('Item 2')).dy, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 11) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 11).itemTrailingEdge, + 1, + ); }); - testWidgets( - 'Scroll to 5 (already on screen) and then back to 0 with shrink wrap', - (WidgetTester tester) async { + testWidgets('Scroll to 5 (already on screen) and then back to 0 with shrink wrap', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 5, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 5, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 0'), findsOneWidget); expect(find.text('Item 9'), findsOneWidget); expect(find.text('Item 10'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); - testWidgets('Scroll to 100 (not already on screen) with shrink wrap', - (WidgetTester tester) async { + testWidgets('Scroll to 100 (not already on screen) with shrink wrap', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 99'), findsNothing); expect(find.text('Item 100'), findsOneWidget); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemTrailingEdge, + 1, + ); }); testWidgets('Jump to 100 with shrink wrap', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); itemScrollController.jumpTo(index: 100); await tester.pumpAndSettle(); @@ -186,19 +158,16 @@ void main() { expect(tester.getBottomRight(find.text('Item 109')).dy, screenHeight); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemTrailingEdge, + 1, + ); }); - testWidgets('padding test - centered sliver at bottom with shrink wrap', - (WidgetTester tester) async { + testWidgets('padding test - centered sliver at bottom with shrink wrap', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -207,25 +176,19 @@ void main() { ); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 1')), - const Offset(10, itemHeight + 10)); - expect(tester.getBottomRight(find.text('Item 1')), - const Offset(screenWidth - 10, 10 + itemHeight * 2)); + expect(tester.getTopLeft(find.text('Item 1')), const Offset(10, itemHeight + 10)); + expect(tester.getBottomRight(find.text('Item 1')), const Offset(screenWidth - 10, 10 + itemHeight * 2)); - unawaited( - itemScrollController.scrollTo(index: 490, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 490, duration: scrollDuration)); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, -100)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, -100)); await tester.pumpAndSettle(); - expect(tester.getTopLeft(find.text('Item 499')), - const Offset(10, screenHeight - itemHeight - 10)); + expect(tester.getTopLeft(find.text('Item 499')), const Offset(10, screenHeight - itemHeight - 10)); }); - testWidgets('padding test - centered sliver not at bottom', - (WidgetTester tester) async { + testWidgets('padding test - centered sliver not at bottom', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -234,14 +197,11 @@ void main() { padding: const EdgeInsets.all(10), ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, 200)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, 200)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 2')), - const Offset(10, 10 + itemHeight * 2)); - expect(tester.getTopLeft(find.text('Item 3')), - const Offset(10, 10 + itemHeight * 3)); + expect(tester.getTopLeft(find.text('Item 2')), const Offset(10, 10 + itemHeight * 2)); + expect(tester.getTopLeft(find.text('Item 3')), const Offset(10, 10 + itemHeight * 3)); }); } diff --git a/packages/stream_chat_flutter/test/src/attachment/attachment_handler_test.dart b/packages/stream_chat_flutter/test/src/attachment/attachment_handler_test.dart index 6765434452..b7f449e271 100644 --- a/packages/stream_chat_flutter/test/src/attachment/attachment_handler_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/attachment_handler_test.dart @@ -17,8 +17,7 @@ void main() { final attachmentHandler = MockAttachmentHandler(); - when(() => attachmentHandler.downloadAttachment(attachment)) - .thenAnswer((invocation) async => 'filePath'); + when(() => attachmentHandler.downloadAttachment(attachment)).thenAnswer((invocation) async => 'filePath'); expect( await attachmentHandler.downloadAttachment(attachment), @@ -31,15 +30,13 @@ void main() { title: 'test giphy attachment', type: 'giphy', extraData: const { - 'original': - 'https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti', + 'original': 'https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti', }, ); final attachmentHandler = MockAttachmentHandler(); - when(() => attachmentHandler.downloadAttachment(attachment)) - .thenAnswer((invocation) async => 'filePath'); + when(() => attachmentHandler.downloadAttachment(attachment)).thenAnswer((invocation) async => 'filePath'); expect( await attachmentHandler.downloadAttachment(attachment), @@ -56,8 +53,7 @@ void main() { final attachmentHandler = MockAttachmentHandler(); - when(() => attachmentHandler.downloadAttachment(attachment)) - .thenAnswer((invocation) async => 'filePath'); + when(() => attachmentHandler.downloadAttachment(attachment)).thenAnswer((invocation) async => 'filePath'); expect( await attachmentHandler.downloadAttachment(attachment), diff --git a/packages/stream_chat_flutter/test/src/attachment/attachment_upload_state_builder_test.dart b/packages/stream_chat_flutter/test/src/attachment/attachment_upload_state_builder_test.dart index f87bcdedf5..104f254be7 100644 --- a/packages/stream_chat_flutter/test/src/attachment/attachment_upload_state_builder_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/attachment_upload_state_builder_test.dart @@ -5,9 +5,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import '../mocks.dart'; void main() { - testWidgets( - 'AttachmentUploadStateBuilder returns Offstage when message is sent', - (tester) async { + testWidgets('AttachmentUploadStateBuilder returns Offstage when message is sent', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( diff --git a/packages/stream_chat_flutter/test/src/attachment/builder/voice_recording_attachment_playlist_builder.dart b/packages/stream_chat_flutter/test/src/attachment/builder/voice_recording_attachment_playlist_builder.dart index eef7c8279e..5e55598a3c 100644 --- a/packages/stream_chat_flutter/test/src/attachment/builder/voice_recording_attachment_playlist_builder.dart +++ b/packages/stream_chat_flutter/test/src/attachment/builder/voice_recording_attachment_playlist_builder.dart @@ -74,13 +74,15 @@ Widget _wrapWithStreamChatApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center(child: widget), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center(child: widget), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/attachment/file_attachment_test.dart b/packages/stream_chat_flutter/test/src/attachment/file_attachment_test.dart index 578eabe6fa..d3105d2457 100644 --- a/packages/stream_chat_flutter/test/src/attachment/file_attachment_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/file_attachment_test.dart @@ -25,10 +25,12 @@ void main() { channel: channel, child: SizedBox( child: StreamFileAttachment( - constraints: BoxConstraints.tight(const Size( - 300, - 300, - )), + constraints: BoxConstraints.tight( + const Size( + 300, + 300, + ), + ), message: Message(), file: Attachment( type: 'file', diff --git a/packages/stream_chat_flutter/test/src/attachment/gallery_attachment_test.dart b/packages/stream_chat_flutter/test/src/attachment/gallery_attachment_test.dart index 8b08044a00..d14591c69e 100644 --- a/packages/stream_chat_flutter/test/src/attachment/gallery_attachment_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/gallery_attachment_test.dart @@ -22,8 +22,7 @@ void main() { Attachment( type: 'image', title: 'example.png', - imageUrl: - 'https://logowik.com/content/uploads/images/flutter5786.jpg', + imageUrl: 'https://logowik.com/content/uploads/images/flutter5786.jpg', extraData: const { 'mime_type': 'png', }, @@ -31,8 +30,7 @@ void main() { Attachment( type: 'image', title: 'example.png', - imageUrl: - 'https://logowik.com/content/uploads/images/flutter5786.jpg', + imageUrl: 'https://logowik.com/content/uploads/images/flutter5786.jpg', extraData: const { 'mime_type': 'png', }, @@ -47,10 +45,12 @@ void main() { channel: channel, child: SizedBox( child: StreamGalleryAttachment( - constraints: BoxConstraints.tight(const Size( - 300, - 300, - )), + constraints: BoxConstraints.tight( + const Size( + 300, + 300, + ), + ), message: Message(), attachments: attachments, itemBuilder: (context, index) { diff --git a/packages/stream_chat_flutter/test/src/attachment/giphy_attachment_test.dart b/packages/stream_chat_flutter/test/src/attachment/giphy_attachment_test.dart index 42a604c791..a61b47e080 100644 --- a/packages/stream_chat_flutter/test/src/attachment/giphy_attachment_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/giphy_attachment_test.dart @@ -25,16 +25,17 @@ void main() { channel: channel, child: SizedBox( child: StreamGiphyAttachment( - constraints: BoxConstraints.tight(const Size( - 300, - 300, - )), + constraints: BoxConstraints.tight( + const Size( + 300, + 300, + ), + ), message: Message(), giphy: Attachment( type: 'giphy', title: 'example.gif', - imageUrl: - 'https://media.giphy.com/media/35H0pwQNaO2iLTnnBf/giphy.gif', + imageUrl: 'https://media.giphy.com/media/35H0pwQNaO2iLTnnBf/giphy.gif', extraData: const { 'mime_type': 'gif', }, diff --git a/packages/stream_chat_flutter/test/src/attachment/image_attachment_test.dart b/packages/stream_chat_flutter/test/src/attachment/image_attachment_test.dart index 1bab6234d1..d30cce0941 100644 --- a/packages/stream_chat_flutter/test/src/attachment/image_attachment_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/image_attachment_test.dart @@ -26,16 +26,17 @@ void main() { channel: channel, child: SizedBox( child: StreamImageAttachment( - constraints: BoxConstraints.tight(const Size( - 300, - 300, - )), + constraints: BoxConstraints.tight( + const Size( + 300, + 300, + ), + ), message: Message(), image: Attachment( type: 'image', title: 'example.png', - imageUrl: - 'https://logowik.com/content/uploads/images/flutter5786.jpg', + imageUrl: 'https://logowik.com/content/uploads/images/flutter5786.jpg', extraData: const { 'mime_type': 'png', }, diff --git a/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_playlist_test.dart b/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_playlist_test.dart index fc3ffd107a..b6a4fbfc52 100644 --- a/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_playlist_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_playlist_test.dart @@ -255,13 +255,15 @@ Widget _wrapWithStreamChatApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: widget, - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: widget, + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart b/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart index 529e14f133..d0becb3e9b 100644 --- a/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart @@ -35,8 +35,7 @@ void main() { // Verify key components are present expect(find.byType(AudioControlButton), findsOneWidget); expect(find.byType(StreamAudioWaveformSlider), findsOneWidget); - expect( - find.bySvgIcon(StreamSvgIcons.filetypeAudioM4a), findsOneWidget); + expect(find.bySvgIcon(StreamSvgIcons.filetypeAudioM4a), findsOneWidget); }, ); @@ -284,13 +283,15 @@ Widget _wrapWithStreamChatApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center(child: widget), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center(child: widget), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/attachment_actions_modal/attachment_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/attachment_actions_modal/attachment_actions_modal_test.dart index 08213b0981..511089188c 100644 --- a/packages/stream_chat_flutter/test/src/attachment_actions_modal/attachment_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment_actions_modal/attachment_actions_modal_test.dart @@ -26,8 +26,7 @@ class MockAttachmentDownloader extends Mock { void main() { setUpAll(() { - registerFallbackValue( - MaterialPageRoute(builder: (context) => const SizedBox())); + registerFallbackValue(MaterialPageRoute(builder: (context) => const SizedBox())); registerFallbackValue(Message()); }); @@ -265,8 +264,7 @@ void main() { final clientState = MockClientState(); final mockChannel = MockChannel(); - when(() => mockChannel.updateMessage(any())) - .thenAnswer((_) async => UpdateMessageResponse()); + when(() => mockChannel.updateMessage(any())).thenAnswer((_) async => UpdateMessageResponse()); when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); @@ -306,11 +304,15 @@ void main() { ), ); await tester.tap(find.text('Delete')); - verify(() => mockChannel.updateMessage(message.copyWith( + verify( + () => mockChannel.updateMessage( + message.copyWith( attachments: [ message.attachments[1], ], - ))).called(1); + ), + ), + ).called(1); }, ); @@ -321,8 +323,7 @@ void main() { final clientState = MockClientState(); final mockChannel = MockChannel(); - when(() => mockChannel.updateMessage(any())) - .thenAnswer((_) async => UpdateMessageResponse()); + when(() => mockChannel.updateMessage(any())).thenAnswer((_) async => UpdateMessageResponse()); when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); @@ -357,9 +358,13 @@ void main() { ), ); await tester.tap(find.text('Delete')); - verify(() => mockChannel.updateMessage(message.copyWith( + verify( + () => mockChannel.updateMessage( + message.copyWith( attachments: [], - ))).called(1); + ), + ), + ).called(1); }, ); @@ -371,8 +376,7 @@ void main() { final clientState = MockClientState(); final mockChannel = MockChannel(); - when(() => mockChannel.deleteMessage(any())) - .thenAnswer((_) async => EmptyResponse()); + when(() => mockChannel.deleteMessage(any())).thenAnswer((_) async => EmptyResponse()); when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); diff --git a/packages/stream_chat_flutter/test/src/audio/audio_playlist_controller_test.dart b/packages/stream_chat_flutter/test/src/audio/audio_playlist_controller_test.dart index 2a5cb09b5c..1f4df27c0a 100644 --- a/packages/stream_chat_flutter/test/src/audio/audio_playlist_controller_test.dart +++ b/packages/stream_chat_flutter/test/src/audio/audio_playlist_controller_test.dart @@ -52,12 +52,9 @@ void main() { speedController = PublishSubject(); // Default mock behaviors - when(() => mockPlayer.playerStateStream) - .thenAnswer((_) => stateController.stream); - when(() => mockPlayer.positionStream) - .thenAnswer((_) => positionController.stream); - when(() => mockPlayer.speedStream) - .thenAnswer((_) => speedController.stream); + when(() => mockPlayer.playerStateStream).thenAnswer((_) => stateController.stream); + when(() => mockPlayer.positionStream).thenAnswer((_) => positionController.stream); + when(() => mockPlayer.speedStream).thenAnswer((_) => speedController.stream); controller = StreamAudioPlaylistController.raw( player: mockPlayer, @@ -124,7 +121,7 @@ void main() { PlaylistTrack( title: 'new-track.mp3', uri: Uri.parse('https://example.com/new-track.mp3'), - ) + ), ]; await controller.updatePlaylist(newTracks); diff --git a/packages/stream_chat_flutter/test/src/audio/audio_sampling_test.dart b/packages/stream_chat_flutter/test/src/audio/audio_sampling_test.dart index a1f2918274..e48ab7a14c 100644 --- a/packages/stream_chat_flutter/test/src/audio/audio_sampling_test.dart +++ b/packages/stream_chat_flutter/test/src/audio/audio_sampling_test.dart @@ -76,8 +76,7 @@ void main() { countMap[value] = (countMap[value] ?? 0) + 1; } // Each value should appear either 2 or 3 times - expect( - countMap.values.every((count) => count == 2 || count == 3), isTrue); + expect(countMap.values.every((count) => count == 2 || count == 3), isTrue); }); test('returns original data when target size is smaller', () { diff --git a/packages/stream_chat_flutter/test/src/avatars/gradient_avatar_test.dart b/packages/stream_chat_flutter/test/src/avatars/gradient_avatar_test.dart index e63fe05246..478f834361 100644 --- a/packages/stream_chat_flutter/test/src/avatars/gradient_avatar_test.dart +++ b/packages/stream_chat_flutter/test/src/avatars/gradient_avatar_test.dart @@ -28,8 +28,7 @@ void main() { child: SizedBox( width: 100, height: 100, - child: StreamGradientAvatar( - name: 'demo user', userId: 'demo123'), + child: StreamGradientAvatar(name: 'demo user', userId: 'demo123'), ), ), ), @@ -368,16 +367,18 @@ Widget _wrapWithMaterialApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Container( - padding: const EdgeInsets.all(16), - child: Center(child: widget), - ), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Container( + padding: const EdgeInsets.all(16), + child: Center(child: widget), + ), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/avatars/group_avatar_test.dart b/packages/stream_chat_flutter/test/src/avatars/group_avatar_test.dart index 3cc769213d..304de393d2 100644 --- a/packages/stream_chat_flutter/test/src/avatars/group_avatar_test.dart +++ b/packages/stream_chat_flutter/test/src/avatars/group_avatar_test.dart @@ -16,8 +16,12 @@ void main() { late MockChannel channel; late MockChannelState channelState; - late final member = Member(user: User(id: 'alice', name: 'Alice')); - late final member2 = Member(user: User(id: 'bob', name: 'Bob')); + late final member = Member( + user: User(id: 'alice', name: 'Alice'), + ); + late final member2 = Member( + user: User(id: 'bob', name: 'Bob'), + ); setUpAll(() { client = MockClient(); diff --git a/packages/stream_chat_flutter/test/src/avatars/user_avatar_test.dart b/packages/stream_chat_flutter/test/src/avatars/user_avatar_test.dart index 6a95d3637e..6ec007a992 100644 --- a/packages/stream_chat_flutter/test/src/avatars/user_avatar_test.dart +++ b/packages/stream_chat_flutter/test/src/avatars/user_avatar_test.dart @@ -28,15 +28,17 @@ void main() { home: StreamChat( client: client, streamChatThemeData: StreamChatThemeData.light(), - child: Builder(builder: (context) { - return Scaffold( - body: Center( - child: StreamUserAvatar( - user: user, + child: Builder( + builder: (context) { + return Scaffold( + body: Center( + child: StreamUserAvatar( + user: user, + ), ), - ), - ); - }), + ); + }, + ), ), ), ); diff --git a/packages/stream_chat_flutter/test/src/bottom_sheets/attachment_modal_sheet_test.dart b/packages/stream_chat_flutter/test/src/bottom_sheets/attachment_modal_sheet_test.dart index 0779c67e87..a1ba65dc58 100644 --- a/packages/stream_chat_flutter/test/src/bottom_sheets/attachment_modal_sheet_test.dart +++ b/packages/stream_chat_flutter/test/src/bottom_sheets/attachment_modal_sheet_test.dart @@ -11,21 +11,23 @@ void main() { await tester.pumpWidget( MaterialApp( home: Scaffold( - body: Builder(builder: (context) { - return Center( - child: ElevatedButton( - child: const Text('Show Modal'), - onPressed: () => showModalBottomSheet( - context: context, - builder: (_) => AttachmentModalSheet( - onFileTap: () {}, - onPhotoTap: () {}, - onVideoTap: () {}, + body: Builder( + builder: (context) { + return Center( + child: ElevatedButton( + child: const Text('Show Modal'), + onPressed: () => showModalBottomSheet( + context: context, + builder: (_) => AttachmentModalSheet( + onFileTap: () {}, + onPhotoTap: () {}, + onVideoTap: () {}, + ), ), ), - ), - ); - }), + ); + }, + ), ), ), ); @@ -42,15 +44,17 @@ void main() { await tester.pumpWidget( MaterialApp( home: Scaffold( - body: Builder(builder: (context) { - return Center( - child: AttachmentModalSheet( - onPhotoTap: () => called = 1, - onFileTap: () {}, - onVideoTap: () {}, - ), - ); - }), + body: Builder( + builder: (context) { + return Center( + child: AttachmentModalSheet( + onPhotoTap: () => called = 1, + onFileTap: () {}, + onVideoTap: () {}, + ), + ); + }, + ), ), ), ); @@ -68,15 +72,17 @@ void main() { await tester.pumpWidget( MaterialApp( home: Scaffold( - body: Builder(builder: (context) { - return Center( - child: AttachmentModalSheet( - onPhotoTap: () {}, - onVideoTap: () => called = 1, - onFileTap: () {}, - ), - ); - }), + body: Builder( + builder: (context) { + return Center( + child: AttachmentModalSheet( + onPhotoTap: () {}, + onVideoTap: () => called = 1, + onFileTap: () {}, + ), + ); + }, + ), ), ), ); @@ -94,15 +100,17 @@ void main() { await tester.pumpWidget( MaterialApp( home: Scaffold( - body: Builder(builder: (context) { - return Center( - child: AttachmentModalSheet( - onPhotoTap: () {}, - onVideoTap: () {}, - onFileTap: () => called = 1, - ), - ); - }), + body: Builder( + builder: (context) { + return Center( + child: AttachmentModalSheet( + onPhotoTap: () {}, + onVideoTap: () {}, + onFileTap: () => called = 1, + ), + ); + }, + ), ), ), ); @@ -121,15 +129,17 @@ void main() { constraints: const BoxConstraints.tightFor(width: 300, height: 300), builder: () => MaterialAppWrapper( home: Scaffold( - body: Builder(builder: (context) { - return Center( - child: AttachmentModalSheet( - onPhotoTap: () {}, - onVideoTap: () {}, - onFileTap: () {}, - ), - ); - }), + body: Builder( + builder: (context) { + return Center( + child: AttachmentModalSheet( + onPhotoTap: () {}, + onVideoTap: () {}, + onFileTap: () {}, + ), + ); + }, + ), ), ), ); diff --git a/packages/stream_chat_flutter/test/src/bottom_sheets/error_alert_sheet_test.dart b/packages/stream_chat_flutter/test/src/bottom_sheets/error_alert_sheet_test.dart index 7dc89683b8..4915e5f6bb 100644 --- a/packages/stream_chat_flutter/test/src/bottom_sheets/error_alert_sheet_test.dart +++ b/packages/stream_chat_flutter/test/src/bottom_sheets/error_alert_sheet_test.dart @@ -9,18 +9,15 @@ import '../mocks.dart'; void main() { group('ErrorAlertSheet tests', () { - const methodChannel = - MethodChannel('dev.fluttercommunity.plus/connectivity_status'); + const methodChannel = MethodChannel('dev.fluttercommunity.plus/connectivity_status'); setUp(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, - (MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(methodChannel, ( + MethodCall methodCall, + ) async { if (methodCall.method == 'listen') { try { - await TestDefaultBinaryMessengerBinding - .instance.defaultBinaryMessenger - .handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage( methodChannel.name, methodChannel.codec.encodeSuccessEnvelope(['wifi']), (_) {}, @@ -93,8 +90,7 @@ void main() { ); tearDown(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(methodChannel, null); }); }); } diff --git a/packages/stream_chat_flutter/test/src/channel/channel_header_test.dart b/packages/stream_chat_flutter/test/src/channel/channel_header_test.dart index 0518fd84fd..fbdc6b4d88 100644 --- a/packages/stream_chat_flutter/test/src/channel/channel_header_test.dart +++ b/packages/stream_chat_flutter/test/src/channel/channel_header_test.dart @@ -28,8 +28,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(user); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(user)); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -37,23 +36,19 @@ void main() { when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); - when(() => channel.imageStream) - .thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); + when(() => channel.imageStream).thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); when(() => channel.image).thenReturn('https://bit.ly/321RmWb'); when(() => channelState.unreadCount).thenReturn(1); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connected)); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connected)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -99,8 +94,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(user); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(user)); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -108,18 +102,16 @@ void main() { when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); - when(() => channel.imageStream) - .thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); + when(() => channel.imageStream).thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); when(() => channel.image).thenReturn('https://bit.ly/321RmWb'); when(() => channelState.unreadCount).thenReturn(1); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -128,13 +120,10 @@ void main() { user: User(id: 'user-id'), ), ]); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); - when(() => client.wsConnectionStatus) - .thenReturn(ConnectionStatus.disconnected); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); + when(() => client.wsConnectionStatus).thenReturn(ConnectionStatus.disconnected); when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(1)); await tester.pumpWidget( MaterialApp( @@ -155,13 +144,8 @@ void main() { // wait for the initial state to be rendered. await tester.pumpAndSettle(); - expect( - tester - .widget(find.byType(StreamInfoTile)) - .showMessage, - true); - expect(tester.widget(find.byType(StreamInfoTile)).message, - 'Disconnected'); + expect(tester.widget(find.byType(StreamInfoTile)).showMessage, true); + expect(tester.widget(find.byType(StreamInfoTile)).message, 'Disconnected'); }, ); @@ -177,8 +161,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(user); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(user)); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -186,18 +169,16 @@ void main() { when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); - when(() => channel.imageStream) - .thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); + when(() => channel.imageStream).thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); when(() => channel.image).thenReturn('https://bit.ly/321RmWb'); when(() => channelState.unreadCount).thenReturn(1); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -206,11 +187,9 @@ void main() { user: User(id: 'user-id'), ), ]); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(1)); await tester.pumpWidget( MaterialApp( @@ -231,13 +210,8 @@ void main() { await tester.pump(); - expect( - tester - .widget(find.byType(StreamInfoTile)) - .showMessage, - true); - expect(tester.widget(find.byType(StreamInfoTile)).message, - 'Reconnecting...'); + expect(tester.widget(find.byType(StreamInfoTile)).showMessage, true); + expect(tester.widget(find.byType(StreamInfoTile)).message, 'Reconnecting...'); }, ); @@ -253,28 +227,28 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(user); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(user)); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); when(() => channel.isMuted).thenReturn(false); when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ - 'name': 'test', - })); + when(() => channel.extraDataStream).thenAnswer( + (i) => Stream.value({ + 'name': 'test', + }), + ); when(() => channel.extraData).thenReturn({ 'name': 'test', }); when(() => channelState.unreadCount).thenReturn(1); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -283,10 +257,8 @@ void main() { user: User(id: 'user-id'), ), ]); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(1)); await tester.pumpWidget( MaterialAppWrapper( @@ -337,8 +309,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(user); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(user)); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -346,18 +317,16 @@ void main() { when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); - when(() => channel.imageStream) - .thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); + when(() => channel.imageStream).thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); when(() => channel.image).thenReturn('https://bit.ly/321RmWb'); when(() => channelState.unreadCount).thenReturn(1); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -366,8 +335,7 @@ void main() { user: User(id: 'user-id'), ), ]); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); await tester.pumpWidget( MaterialApp( @@ -391,16 +359,10 @@ void main() { expect(find.byType(StreamBackButton), findsNothing); expect( - tester - .widget(find.byType(StreamChannelInfo)) - .showTypingIndicator, + tester.widget(find.byType(StreamChannelInfo)).showTypingIndicator, false, ); - expect( - tester - .widget(find.byType(StreamInfoTile)) - .showMessage, - false); + expect(tester.widget(find.byType(StreamInfoTile)).showMessage, false); }, ); @@ -416,8 +378,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(user); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(user)); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -425,18 +386,16 @@ void main() { when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); - when(() => channel.imageStream) - .thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); + when(() => channel.imageStream).thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); when(() => channel.image).thenReturn('https://bit.ly/321RmWb'); when(() => channelState.unreadCount).thenReturn(1); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -445,11 +404,9 @@ void main() { user: User(id: 'user-id'), ), ]); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(1)); var backPressed = false; var imageTapped = false; @@ -500,8 +457,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(user); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(user)); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -509,23 +465,19 @@ void main() { when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); - when(() => channel.imageStream) - .thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); + when(() => channel.imageStream).thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); when(() => channel.image).thenReturn('https://bit.ly/321RmWb'); when(() => channelState.unreadCount).thenReturn(1); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connected)); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connected)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ diff --git a/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart b/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart index 6bcde77973..685c75f589 100644 --- a/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart +++ b/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart @@ -21,8 +21,7 @@ void main() { when(() => channel.client).thenReturn(client); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); - when(() => channel.imageStream) - .thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); + when(() => channel.imageStream).thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); when(() => channel.image).thenReturn('https://bit.ly/321RmWb'); await tester.pumpWidget( @@ -42,8 +41,7 @@ void main() { // wait for the initial state to be rendered. await tester.pumpAndSettle(); - final image = - tester.widget(find.byType(CachedNetworkImage)); + final image = tester.widget(find.byType(CachedNetworkImage)); expect(image.imageUrl, 'https://bit.ly/321RmWb'); }, ); @@ -76,7 +74,7 @@ void main() { id: 'user-id2', image: 'testimage', ), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -90,7 +88,7 @@ void main() { Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]); when(() => clientState.usersStream).thenAnswer( (i) => Stream.value({ @@ -121,8 +119,7 @@ void main() { // wait for the initial state to be rendered. await tester.pumpAndSettle(); - final image = - tester.widget(find.byType(CachedNetworkImage)); + final image = tester.widget(find.byType(CachedNetworkImage)); expect(image.imageUrl, 'testimage'); }, ); @@ -167,8 +164,7 @@ void main() { ), ]; when(() => channelState.members).thenReturn(members); - when(() => channelState.membersStream) - .thenAnswer((_) => Stream.value(members)); + when(() => channelState.membersStream).thenAnswer((_) => Stream.value(members)); await tester.pumpWidget( MaterialApp( @@ -187,8 +183,7 @@ void main() { // wait for the initial state to be rendered. await tester.pumpAndSettle(); - final image = - tester.widget(find.byType(StreamGroupAvatar)); + final image = tester.widget(find.byType(StreamGroupAvatar)); final otherMembers = members.where((it) => it.userId != currentUser.id); expect( image.members.map((it) => it.user?.id), @@ -211,8 +206,7 @@ void main() { when(() => channel.client).thenReturn(client); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); - when(() => channel.imageStream) - .thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); + when(() => channel.imageStream).thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); when(() => channel.image).thenReturn('https://bit.ly/321RmWb'); await tester.pumpWidget( diff --git a/packages/stream_chat_flutter/test/src/channel/channel_list_header_test.dart b/packages/stream_chat_flutter/test/src/channel/channel_list_header_test.dart index 6193d08d97..d136ce40fb 100644 --- a/packages/stream_chat_flutter/test/src/channel/channel_list_header_test.dart +++ b/packages/stream_chat_flutter/test/src/channel/channel_list_header_test.dart @@ -14,8 +14,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connected)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connected)); await tester.pumpWidget( MaterialApp( @@ -29,8 +28,7 @@ void main() { ); await tester.pumpAndSettle(); - final userAvatar = - tester.widget(find.byType(StreamUserAvatar)); + final userAvatar = tester.widget(find.byType(StreamUserAvatar)); expect(userAvatar.user, clientState.currentUser); expect(find.byType(StreamNeumorphicButton), findsOneWidget); expect(find.text('Stream Chat'), findsOneWidget); @@ -45,8 +43,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); await tester.pumpWidget( MaterialApp( @@ -74,8 +71,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); await tester.pumpWidget( MaterialApp( @@ -103,8 +99,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); await tester.pumpWidget( MaterialApp( @@ -141,8 +136,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); var tapped = false; @@ -175,8 +169,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); var tapped = 0; await tester.pumpWidget( diff --git a/packages/stream_chat_flutter/test/src/channel/channel_name_test.dart b/packages/stream_chat_flutter/test/src/channel/channel_name_test.dart index 22cea8a99b..5bd913308c 100644 --- a/packages/stream_chat_flutter/test/src/channel/channel_name_test.dart +++ b/packages/stream_chat_flutter/test/src/channel/channel_name_test.dart @@ -25,14 +25,13 @@ void main() { when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); when(() => channelState.unreadCount).thenReturn(1); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (_) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -45,14 +44,14 @@ void main() { Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]); when(() => channelState.messagesStream).thenAnswer( (i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]), ); diff --git a/packages/stream_chat_flutter/test/src/channel/stream_message_preview_text_test.dart b/packages/stream_chat_flutter/test/src/channel/stream_message_preview_text_test.dart index a8e6f674d9..d886fb8271 100644 --- a/packages/stream_chat_flutter/test/src/channel/stream_message_preview_text_test.dart +++ b/packages/stream_chat_flutter/test/src/channel/stream_message_preview_text_test.dart @@ -293,8 +293,7 @@ void main() { }); group('Poll Tests', () { - testWidgets('renders poll with latest voter (current user)', - (tester) async { + testWidgets('renders poll with latest voter (current user)', (tester) async { final voterPoll = Poll( name: 'Favorite Color?', options: const [ @@ -321,8 +320,7 @@ void main() { expect(find.text('📊 You voted: "Favorite Color?"'), findsOneWidget); }); - testWidgets('renders poll with latest voter (another user)', - (tester) async { + testWidgets('renders poll with latest voter (another user)', (tester) async { final voter = User(id: 'voter-id', name: 'Voter'); final voterPoll = Poll( name: 'Favorite Color?', diff --git a/packages/stream_chat_flutter/test/src/dialogs/confirmation_dialog_test.dart b/packages/stream_chat_flutter/test/src/dialogs/confirmation_dialog_test.dart index 27e74c5892..671837c4d7 100644 --- a/packages/stream_chat_flutter/test/src/dialogs/confirmation_dialog_test.dart +++ b/packages/stream_chat_flutter/test/src/dialogs/confirmation_dialog_test.dart @@ -18,12 +18,9 @@ void main() { child: StreamChatTheme( data: StreamChatThemeData.light(), child: ConfirmationDialog( - titleText: context.translations - .toggleMuteUnmuteUserText(isMuted: false), - promptText: context.translations - .toggleMuteUnmuteUserQuestion(isMuted: false), - affirmativeText: context.translations - .toggleMuteUnmuteAction(isMuted: false), + titleText: context.translations.toggleMuteUnmuteUserText(isMuted: false), + promptText: context.translations.toggleMuteUnmuteUserQuestion(isMuted: false), + affirmativeText: context.translations.toggleMuteUnmuteAction(isMuted: false), onConfirmation: () {}, ), ), @@ -36,8 +33,7 @@ void main() { expect(find.byType(AlertDialog), findsOneWidget); expect(find.text('Mute User'), findsOneWidget); - expect(find.text('Are you sure you want to mute this user?'), - findsOneWidget); + expect(find.text('Are you sure you want to mute this user?'), findsOneWidget); expect(find.text('MUTE'), findsOneWidget); }); @@ -53,12 +49,9 @@ void main() { child: StreamChatTheme( data: StreamChatThemeData.light(), child: ConfirmationDialog( - titleText: context.translations - .toggleMuteUnmuteUserText(isMuted: false), - promptText: context.translations - .toggleMuteUnmuteUserQuestion(isMuted: false), - affirmativeText: context.translations - .toggleMuteUnmuteAction(isMuted: false), + titleText: context.translations.toggleMuteUnmuteUserText(isMuted: false), + promptText: context.translations.toggleMuteUnmuteUserQuestion(isMuted: false), + affirmativeText: context.translations.toggleMuteUnmuteAction(isMuted: false), onConfirmation: () {}, ), ), diff --git a/packages/stream_chat_flutter/test/src/fakes.dart b/packages/stream_chat_flutter/test/src/fakes.dart index 3c54a85aa6..d488a3f365 100644 --- a/packages/stream_chat_flutter/test/src/fakes.dart +++ b/packages/stream_chat_flutter/test/src/fakes.dart @@ -12,9 +12,7 @@ const String kApplicationDocumentsPath = 'applicationDocumentsPath'; const String kExternalCachePath = 'externalCachePath'; const String kExternalStoragePath = 'externalStoragePath'; -class FakePathProviderPlatform extends Fake - with MockPlatformInterfaceMixin - implements PathProviderPlatform { +class FakePathProviderPlatform extends Fake with MockPlatformInterfaceMixin implements PathProviderPlatform { @override Future getTemporaryPath() async { return kTemporaryPath; @@ -58,9 +56,7 @@ class FakePathProviderPlatform extends Fake } } -class AllNullFakePathProviderPlatform extends Fake - with MockPlatformInterfaceMixin - implements PathProviderPlatform { +class AllNullFakePathProviderPlatform extends Fake with MockPlatformInterfaceMixin implements PathProviderPlatform { @override Future getTemporaryPath() async { return null; @@ -104,9 +100,7 @@ class AllNullFakePathProviderPlatform extends Fake } } -class FakeRecordPlatform extends Fake - with MockPlatformInterfaceMixin - implements RecordPlatform { +class FakeRecordPlatform extends Fake with MockPlatformInterfaceMixin implements RecordPlatform { @override Future create(String recorderId) async {} @@ -143,9 +137,7 @@ class FakeRecordPlatform extends Fake Future dispose(String recorderId) async {} } -class FakeConnectivityPlatform extends Fake - with MockPlatformInterfaceMixin - implements ConnectivityPlatform { +class FakeConnectivityPlatform extends Fake with MockPlatformInterfaceMixin implements ConnectivityPlatform { @override Future> checkConnectivity() { return Future.value([ConnectivityResult.wifi]); diff --git a/packages/stream_chat_flutter/test/src/full_screen_media/full_screen_media_test.dart b/packages/stream_chat_flutter/test/src/full_screen_media/full_screen_media_test.dart index 43cb1bb35a..c10b20f155 100644 --- a/packages/stream_chat_flutter/test/src/full_screen_media/full_screen_media_test.dart +++ b/packages/stream_chat_flutter/test/src/full_screen_media/full_screen_media_test.dart @@ -36,7 +36,7 @@ void main() { Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -49,24 +49,24 @@ void main() { Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]); when(() => channelState.messagesStream).thenAnswer( (i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]), ); - when(() => channelState.typingEvents).thenAnswer((i) => { - User(id: 'other-user', extraData: const {'name': 'demo'}): - Event(type: EventType.typingStart), - }); + when(() => channelState.typingEvents).thenAnswer( + (i) => { + User(id: 'other-user', extraData: const {'name': 'demo'}): Event(type: EventType.typingStart), + }, + ); when(() => channelState.typingEventsStream).thenAnswer( (i) => Stream.value({ - User(id: 'other-user', extraData: const {'name': 'demo'}): - Event(type: EventType.typingStart), + User(id: 'other-user', extraData: const {'name': 'demo'}): Event(type: EventType.typingStart), }), ); @@ -81,22 +81,24 @@ void main() { attachment, ], ); - await tester.pumpWidget(MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: StreamFullScreenMedia( - mediaAttachmentPackages: [ - StreamAttachmentPackage( - attachment: attachment, - message: message, - ), - ], + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: StreamFullScreenMedia( + mediaAttachmentPackages: [ + StreamAttachmentPackage( + attachment: attachment, + message: message, + ), + ], + ), ), ), ), - )); + ); // wait for the initial state to be rendered. await tester.pump(Duration.zero); diff --git a/packages/stream_chat_flutter/test/src/gallery/gallery_footer_test.dart b/packages/stream_chat_flutter/test/src/gallery/gallery_footer_test.dart index 7480968063..250890eb71 100644 --- a/packages/stream_chat_flutter/test/src/gallery/gallery_footer_test.dart +++ b/packages/stream_chat_flutter/test/src/gallery/gallery_footer_test.dart @@ -13,8 +13,7 @@ void main() { late MockClientState clientState; late MockChannel channel; late MockChannelState channelState; - const methodChannel = - MethodChannel('dev.fluttercommunity.plus/connectivity_status'); + const methodChannel = MethodChannel('dev.fluttercommunity.plus/connectivity_status'); setUpAll(() { client = MockClient(); @@ -41,13 +40,12 @@ void main() { }); setUp(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, (MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(methodChannel, ( + MethodCall methodCall, + ) async { if (methodCall.method == 'listen') { try { - await TestDefaultBinaryMessengerBinding - .instance.defaultBinaryMessenger - .handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage( methodChannel.name, methodChannel.codec.encodeSuccessEnvelope(['wifi']), (_) {}, @@ -112,7 +110,6 @@ void main() { ); tearDown(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(methodChannel, null); }); } diff --git a/packages/stream_chat_flutter/test/src/gallery/gallery_header_test.dart b/packages/stream_chat_flutter/test/src/gallery/gallery_header_test.dart index f29f9e596d..d2106caa2f 100644 --- a/packages/stream_chat_flutter/test/src/gallery/gallery_header_test.dart +++ b/packages/stream_chat_flutter/test/src/gallery/gallery_header_test.dart @@ -13,8 +13,7 @@ void main() { late MockClientState clientState; late MockChannel channel; late MockChannelState channelState; - const methodChannel = - MethodChannel('dev.fluttercommunity.plus/connectivity_status'); + const methodChannel = MethodChannel('dev.fluttercommunity.plus/connectivity_status'); setUpAll(() { client = MockClient(); @@ -41,13 +40,12 @@ void main() { }); setUp(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, (MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(methodChannel, ( + MethodCall methodCall, + ) async { if (methodCall.method == 'listen') { try { - await TestDefaultBinaryMessengerBinding - .instance.defaultBinaryMessenger - .handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage( methodChannel.name, methodChannel.codec.encodeSuccessEnvelope(['wifi']), (_) {}, @@ -118,7 +116,6 @@ void main() { ); tearDown(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(methodChannel, null); }); } diff --git a/packages/stream_chat_flutter/test/src/icons/stream_svg_icon_test.dart b/packages/stream_chat_flutter/test/src/icons/stream_svg_icon_test.dart index 5c4fc86d9c..47f28610a5 100644 --- a/packages/stream_chat_flutter/test/src/icons/stream_svg_icon_test.dart +++ b/packages/stream_chat_flutter/test/src/icons/stream_svg_icon_test.dart @@ -181,13 +181,15 @@ Widget _wrapWithMaterialApp( data: ThemeData(brightness: brightness), child: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center(child: widget), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center(child: widget), + ); + }, + ), ), ), ); diff --git a/packages/stream_chat_flutter/test/src/indicators/typing_indicator_test.dart b/packages/stream_chat_flutter/test/src/indicators/typing_indicator_test.dart index bcc2f13701..0a55b81f2c 100644 --- a/packages/stream_chat_flutter/test/src/indicators/typing_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/indicators/typing_indicator_test.dart @@ -35,7 +35,7 @@ void main() { Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -48,43 +48,45 @@ void main() { Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]); when(() => channelState.messagesStream).thenAnswer( (i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]), ); - when(() => channelState.typingEvents).thenAnswer((i) => { - User(id: 'other-user', extraData: const {'name': 'demo'}): - Event(type: EventType.typingStart), - }); + when(() => channelState.typingEvents).thenAnswer( + (i) => { + User(id: 'other-user', extraData: const {'name': 'demo'}): Event(type: EventType.typingStart), + }, + ); when(() => channelState.typingEventsStream).thenAnswer( (i) => Stream.value({ - User(id: 'other-user', extraData: const {'name': 'demo'}): - Event(type: EventType.typingStart), + User(id: 'other-user', extraData: const {'name': 'demo'}): Event(type: EventType.typingStart), }), ); const typingKey = Key('typing'); - await tester.pumpWidget(MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: const Scaffold( - body: StreamTypingIndicator( - key: typingKey, + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: const Scaffold( + body: StreamTypingIndicator( + key: typingKey, + ), ), ), ), ), - )); + ); // wait for the initial state to be rendered. await tester.pump(Duration.zero); diff --git a/packages/stream_chat_flutter/test/src/indicators/unread_indicator_test.dart b/packages/stream_chat_flutter/test/src/indicators/unread_indicator_test.dart index 71f91c0235..c6fe1a058a 100644 --- a/packages/stream_chat_flutter/test/src/indicators/unread_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/indicators/unread_indicator_test.dart @@ -30,20 +30,21 @@ void main() { }); when(() => clientState.totalUnreadCount).thenReturn(10); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(10)); - - await tester.pumpWidget(MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: const Scaffold( - body: StreamUnreadIndicator(), + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(10)); + + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: const Scaffold( + body: StreamUnreadIndicator(), + ), ), ), ), - )); + ); // wait for the initial state to be rendered. await tester.pumpAndSettle(); @@ -70,22 +71,23 @@ void main() { when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); when(() => channelState.unreadCount).thenReturn(0); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(0)); - - await tester.pumpWidget(MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamUnreadIndicator.channels( - cid: channel.cid, + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(0)); + + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: StreamUnreadIndicator.channels( + cid: channel.cid, + ), ), ), ), ), - )); + ); // wait for the initial state to be rendered. await tester.pumpAndSettle(); @@ -112,22 +114,23 @@ void main() { when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); when(() => channelState.unreadCount).thenReturn(100); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(100)); - - await tester.pumpWidget(MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamUnreadIndicator.channels( - cid: channel.cid, + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(100)); + + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: StreamUnreadIndicator.channels( + cid: channel.cid, + ), ), ), ), ), - )); + ); // wait for the initial state to be rendered. await tester.pumpAndSettle(); diff --git a/packages/stream_chat_flutter/test/src/indicators/upload_progress_indicator_test.dart b/packages/stream_chat_flutter/test/src/indicators/upload_progress_indicator_test.dart index 09a9237d81..d7250346cd 100644 --- a/packages/stream_chat_flutter/test/src/indicators/upload_progress_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/indicators/upload_progress_indicator_test.dart @@ -6,8 +6,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import '../material_app_wrapper.dart'; void main() { - testWidgets('StreamUploadProgressIndicator at 0% with no background', - (tester) async { + testWidgets('StreamUploadProgressIndicator at 0% with no background', (tester) async { await tester.pumpWidget( MaterialApp( home: StreamChatTheme( @@ -28,8 +27,7 @@ void main() { expect(find.text('0%'), findsOneWidget); }); - testWidgets('StreamUploadProgressIndicator at 50% with no background', - (tester) async { + testWidgets('StreamUploadProgressIndicator at 50% with no background', (tester) async { await tester.pumpWidget( MaterialApp( home: StreamChatTheme( @@ -50,8 +48,7 @@ void main() { expect(find.text('50%'), findsOneWidget); }); - testWidgets('StreamUploadProgressIndicator at 100% with no background', - (tester) async { + testWidgets('StreamUploadProgressIndicator at 100% with no background', (tester) async { await tester.pumpWidget( MaterialApp( home: StreamChatTheme( @@ -72,8 +69,7 @@ void main() { expect(find.text('100%'), findsOneWidget); }); - testWidgets('StreamUploadProgressIndicator at 50% with background', - (tester) async { + testWidgets('StreamUploadProgressIndicator at 50% with background', (tester) async { await tester.pumpWidget( MaterialApp( home: StreamChatTheme( @@ -91,9 +87,7 @@ void main() { ); final backgroundColor = - ((find.byType(DecoratedBox).evaluate().first.widget as DecoratedBox) - .decoration as BoxDecoration) - .color; + ((find.byType(DecoratedBox).evaluate().first.widget as DecoratedBox).decoration as BoxDecoration).color; expect(const Color(0x99000000), backgroundColor); }); diff --git a/packages/stream_chat_flutter/test/src/material_app_wrapper.dart b/packages/stream_chat_flutter/test/src/material_app_wrapper.dart index 904b9a080d..b0e354997e 100644 --- a/packages/stream_chat_flutter/test/src/material_app_wrapper.dart +++ b/packages/stream_chat_flutter/test/src/material_app_wrapper.dart @@ -13,16 +13,15 @@ class MaterialAppWrapper extends MaterialApp { TransitionBuilder? builder, Widget? home, }) : super( - key: key, - builder: builder, - localizationsDelegates: localizations, - supportedLocales: localeOverrides ?? const [Locale('en')], - theme: theme?.copyWith(platform: platform) ?? - ThemeData(platform: platform, useMaterial3: false), - debugShowCheckedModeBanner: false, - home: home, - navigatorObservers: [ - if (navigatorObserver != null) navigatorObserver, - ], - ); + key: key, + builder: builder, + localizationsDelegates: localizations, + supportedLocales: localeOverrides ?? const [Locale('en')], + theme: theme?.copyWith(platform: platform) ?? ThemeData(platform: platform, useMaterial3: false), + debugShowCheckedModeBanner: false, + home: home, + navigatorObservers: [ + if (navigatorObserver != null) navigatorObserver, + ], + ); } diff --git a/packages/stream_chat_flutter/test/src/message_action/message_action_item_test.dart b/packages/stream_chat_flutter/test/src/message_action/message_action_item_test.dart index 1edf748ec1..5dd3a2ac1e 100644 --- a/packages/stream_chat_flutter/test/src/message_action/message_action_item_test.dart +++ b/packages/stream_chat_flutter/test/src/message_action/message_action_item_test.dart @@ -150,18 +150,20 @@ Widget _wrapWithMaterialApp( debugShowCheckedModeBanner: false, home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: child, + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: child, + ), ), - ), - ); - }), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/message_action/message_actions_builder_test.dart b/packages/stream_chat_flutter/test/src/message_action/message_actions_builder_test.dart index e6960c0712..7d3cf82fc0 100644 --- a/packages/stream_chat_flutter/test/src/message_action/message_actions_builder_test.dart +++ b/packages/stream_chat_flutter/test/src/message_action/message_actions_builder_test.dart @@ -30,9 +30,9 @@ Message createTestMessage({ replyCount: replyCount, moderation: switch (type) { MessageType.error => const Moderation( - action: ModerationAction.bounce, - originalText: 'Original message text that violated policy', - ), + action: ModerationAction.bounce, + originalText: 'Original message text that violated policy', + ), _ => null, }, ); @@ -83,10 +83,12 @@ void main() { await tester.pumpWidget( StreamChatTheme( data: StreamChatThemeData.light(), - child: Builder(builder: (ctx) { - context = ctx; - return const SizedBox.shrink(); - }), + child: Builder( + builder: (ctx) { + context = ctx; + return const SizedBox.shrink(); + }, + ), ), ); return context; @@ -437,8 +439,7 @@ void main() { // Thread message final channel = _getChannelWithCapabilities(allChannelCapabilities); final threadMessage = createTestMessage(parentId: 'parent-message-id'); - final actionsForThreadMessage = - StreamMessageActionsBuilder.buildActions( + final actionsForThreadMessage = StreamMessageActionsBuilder.buildActions( context: context, message: threadMessage, channel: channel, diff --git a/packages/stream_chat_flutter/test/src/message_input/attachment_button_test.dart b/packages/stream_chat_flutter/test/src/message_input/attachment_button_test.dart index 7ac928d201..21b9b050ef 100644 --- a/packages/stream_chat_flutter/test/src/message_input/attachment_button_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/attachment_button_test.dart @@ -78,9 +78,7 @@ void main() { home: Scaffold( body: Center( child: AttachmentButton( - color: StreamChatThemeData.light() - .messageInputTheme - .actionButtonIdleColor, + color: StreamChatThemeData.light().messageInputTheme.actionButtonIdleColor, onPressed: () {}, ), ), diff --git a/packages/stream_chat_flutter/test/src/message_input/attachment_picker/stream_attachment_picker_test.dart b/packages/stream_chat_flutter/test/src/message_input/attachment_picker/stream_attachment_picker_test.dart index 8675d05452..71f3b67ca8 100644 --- a/packages/stream_chat_flutter/test/src/message_input/attachment_picker/stream_attachment_picker_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/attachment_picker/stream_attachment_picker_test.dart @@ -72,8 +72,7 @@ void main() { await tester.tap(find.text('Show Picker')); await tester.pumpAndSettle(); - final bottomSheet = - tester.widget( + final bottomSheet = tester.widget( find.byType(StreamSystemAttachmentPickerBottomSheet), ); @@ -121,8 +120,7 @@ void main() { await tester.tap(find.text('Show Picker')); await tester.pumpAndSettle(); - final bottomSheet = - tester.widget( + final bottomSheet = tester.widget( find.byType(StreamSystemAttachmentPickerBottomSheet), ); @@ -245,16 +243,14 @@ void main() { await tester.tap(find.text('Show Picker')); await tester.pumpAndSettle(); - final bottomSheet = - tester.widget( + final bottomSheet = tester.widget( find.byType(StreamSystemAttachmentPickerBottomSheet), ); // All options should support images expect( bottomSheet.options.every( - (option) => - option.supportedTypes.contains(AttachmentPickerType.images), + (option) => option.supportedTypes.contains(AttachmentPickerType.images), ), isTrue, ); diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/audio_recorder_controller_test.dart b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/audio_recorder_controller_test.dart index 86ad93a548..7fb19aba7b 100644 --- a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/audio_recorder_controller_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/audio_recorder_controller_test.dart @@ -33,8 +33,7 @@ void main() { when(() => mockRecorder.dispose()).thenAnswer((_) async {}); amplitudeController = PublishSubject(); - when(() => mockRecorder.onAmplitudeChanged(any())) - .thenAnswer((_) => amplitudeController.stream); + when(() => mockRecorder.onAmplitudeChanged(any())).thenAnswer((_) => amplitudeController.stream); controller = StreamAudioRecorderController.raw( config: config, @@ -53,8 +52,7 @@ void main() { group('startRecord', () { setUp(() { - when(() => mockRecorder.start(config, path: any(named: 'path'))) - .thenAnswer((_) async {}); + when(() => mockRecorder.start(config, path: any(named: 'path'))).thenAnswer((_) async {}); }); test( @@ -86,8 +84,7 @@ void main() { setUp(() async { when(() => mockRecorder.hasPermission()).thenAnswer((_) async => true); when(() => mockRecorder.stop()).thenAnswer((_) async => testPath); - when(() => mockRecorder.start(config, path: any(named: 'path'))) - .thenAnswer((_) async {}); + when(() => mockRecorder.start(config, path: any(named: 'path'))).thenAnswer((_) async {}); }); test('stops recording and updates state to stopped', () async { @@ -122,8 +119,7 @@ void main() { setUp(() { when(() => mockRecorder.hasPermission()).thenAnswer((_) async => true); when(() => mockRecorder.cancel()).thenAnswer((_) async {}); - when(() => mockRecorder.start(config, path: any(named: 'path'))) - .thenAnswer((_) async {}); + when(() => mockRecorder.start(config, path: any(named: 'path'))).thenAnswer((_) async {}); }); test('cancels recording and returns to idle state', () async { @@ -208,8 +204,7 @@ void main() { group('amplitude changes', () { setUp(() { when(() => mockRecorder.hasPermission()).thenAnswer((_) async => true); - when(() => mockRecorder.start(config, path: any(named: 'path'))) - .thenAnswer((_) async {}); + when(() => mockRecorder.start(config, path: any(named: 'path'))).thenAnswer((_) async {}); }); test('updates waveform data when amplitude changes', () async { diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart index 736649f03a..b644f5ae1b 100644 --- a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart @@ -637,8 +637,7 @@ void main() { goldenTest( '[${brightness.name}] -> should look fine in recording hold state', - fileName: - 'stream_audio_recorder_button_recording_hold_${brightness.name}', + fileName: 'stream_audio_recorder_button_recording_hold_${brightness.name}', constraints: const BoxConstraints.tightFor(width: 400, height: 160), builder: () => _wrapWithStreamChatApp( brightness: brightness, @@ -653,8 +652,7 @@ void main() { goldenTest( '[${brightness.name}] -> should look fine in recording locked state', - fileName: - 'stream_audio_recorder_button_recording_locked_${brightness.name}', + fileName: 'stream_audio_recorder_button_recording_locked_${brightness.name}', constraints: const BoxConstraints.tightFor(width: 400, height: 160), builder: () => _wrapWithStreamChatApp( brightness: brightness, @@ -672,8 +670,7 @@ void main() { goldenTest( '[${brightness.name}] -> should look fine in recording stopped state', - fileName: - 'stream_audio_recorder_button_recording_stopped_${brightness.name}', + fileName: 'stream_audio_recorder_button_recording_stopped_${brightness.name}', constraints: const BoxConstraints.tightFor(width: 400, height: 160), builder: () => _wrapWithStreamChatApp( brightness: brightness, @@ -699,20 +696,22 @@ Widget _wrapWithStreamChatApp( home: Portal( child: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - bottomNavigationBar: Material( - elevation: 10, - color: theme.colorTheme.barsBg, - child: Padding( - padding: const EdgeInsets.all(8), - child: widget, + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + bottomNavigationBar: Material( + elevation: 10, + color: theme.colorTheme.barsBg, + child: Padding( + padding: const EdgeInsets.all(8), + child: widget, + ), ), - ), - ); - }), + ); + }, + ), ), ), ); diff --git a/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart b/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart index cfb48916e2..c1e9b7014a 100644 --- a/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart @@ -20,9 +20,11 @@ void main() { testWidgets( 'checks message input features', (WidgetTester tester) async { - await tester.pumpWidget(buildWidget( - const StreamMessageInput(), - )); + await tester.pumpWidget( + buildWidget( + const StreamMessageInput(), + ), + ); // wait for the initial state to be rendered. await tester.pumpAndSettle(); @@ -62,7 +64,7 @@ void main() { Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -75,14 +77,14 @@ void main() { Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]); when(() => channelState.messagesStream).thenAnswer( (i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]), ); @@ -122,12 +124,12 @@ void main() { await tester.pumpAndSettle(); expect( - find.descendant( - of: find.byType(StreamMessageValueListenableBuilder), - matching: find.byWidgetPredicate((w) => - w is Padding && - w.padding == const EdgeInsets.only(left: 50))), - findsOneWidget); + find.descendant( + of: find.byType(StreamMessageValueListenableBuilder), + matching: find.byWidgetPredicate((w) => w is Padding && w.padding == const EdgeInsets.only(left: 50)), + ), + findsOneWidget, + ); }, ); @@ -145,12 +147,12 @@ void main() { await tester.pumpAndSettle(); expect( - find.descendant( - of: find.byType(DropTarget), - matching: find.byWidgetPredicate((w) => - w is Container && - w.margin == const EdgeInsets.only(left: 50))), - findsOneWidget); + find.descendant( + of: find.byType(DropTarget), + matching: find.byWidgetPredicate((w) => w is Container && w.margin == const EdgeInsets.only(left: 50)), + ), + findsOneWidget, + ); }, ); @@ -381,14 +383,14 @@ void main() { Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]); when(() => channelState.messagesStream).thenAnswer( (i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]), ); }); @@ -548,7 +550,7 @@ MaterialApp buildWidget(StreamMessageInput input) { Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -561,14 +563,14 @@ MaterialApp buildWidget(StreamMessageInput input) { Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]); when(() => channelState.messagesStream).thenAnswer( (i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]), ); diff --git a/packages/stream_chat_flutter/test/src/message_input/stream_message_send_button_test.dart b/packages/stream_chat_flutter/test/src/message_input/stream_message_send_button_test.dart index a9e4d95052..a566f8c747 100644 --- a/packages/stream_chat_flutter/test/src/message_input/stream_message_send_button_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/stream_message_send_button_test.dart @@ -176,20 +176,22 @@ Widget _wrapWithStreamChatApp( debugShowCheckedModeBanner: false, home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - bottomNavigationBar: Material( - elevation: 10, - color: theme.colorTheme.barsBg, - child: Padding( - padding: const EdgeInsets.all(8), - child: widget, + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + bottomNavigationBar: Material( + elevation: 10, + color: theme.colorTheme.barsBg, + child: Padding( + padding: const EdgeInsets.all(8), + child: widget, + ), ), - ), - ); - }), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/message_list_view/bottom_row_test.dart b/packages/stream_chat_flutter/test/src/message_list_view/bottom_row_test.dart index 4f3059b99c..960411932f 100644 --- a/packages/stream_chat_flutter/test/src/message_list_view/bottom_row_test.dart +++ b/packages/stream_chat_flutter/test/src/message_list_view/bottom_row_test.dart @@ -11,14 +11,13 @@ void main() { setUp(() { channel = MockChannel(); - when(() => channel.on(any(), any(), any(), any())) - .thenAnswer((_) => const Stream.empty()); + when(() => channel.on(any(), any(), any(), any())).thenAnswer((_) => const Stream.empty()); channelClientState = MockChannelState(); when(() => channel.state).thenReturn(channelClientState); when(() => channelClientState.messages).thenReturn([ Message( id: 'parentId', - ) + ), ]); }); diff --git a/packages/stream_chat_flutter/test/src/message_list_view/message_list_view_test.dart b/packages/stream_chat_flutter/test/src/message_list_view/message_list_view_test.dart index 0fd7ae3eeb..96db684e56 100644 --- a/packages/stream_chat_flutter/test/src/message_list_view/message_list_view_test.dart +++ b/packages/stream_chat_flutter/test/src/message_list_view/message_list_view_test.dart @@ -18,34 +18,25 @@ void main() { clientState = MockClientState(); when(() => client.state).thenAnswer((_) => clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'testid')); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(OwnUser(id: 'testid'))); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(OwnUser(id: 'testid'))); channel = MockChannel(); - when(() => channel.on(any(), any(), any(), any())) - .thenAnswer((_) => const Stream.empty()); + when(() => channel.on(any(), any(), any(), any())).thenAnswer((_) => const Stream.empty()); channelClientState = MockChannelState(); when(() => channel.client).thenReturn(client); when(() => channel.state).thenReturn(channelClientState); - when(() => channelClientState.threadsStream) - .thenAnswer((_) => const Stream.empty()); - when(() => channelClientState.messagesStream) - .thenAnswer((_) => const Stream.empty()); + when(() => channelClientState.threadsStream).thenAnswer((_) => const Stream.empty()); + when(() => channelClientState.messagesStream).thenAnswer((_) => const Stream.empty()); when(() => channelClientState.messages).thenReturn([]); when(() => channelClientState.isUpToDate).thenReturn(true); - when(() => channelClientState.isUpToDateStream) - .thenAnswer((_) => Stream.value(true)); - when(() => channelClientState.unreadCountStream) - .thenAnswer((_) => Stream.value(0)); + when(() => channelClientState.isUpToDateStream).thenAnswer((_) => Stream.value(true)); + when(() => channelClientState.unreadCountStream).thenAnswer((_) => Stream.value(0)); when(() => channelClientState.unreadCount).thenReturn(0); - when(() => channelClientState.readStream) - .thenAnswer((_) => const Stream.empty()); + when(() => channelClientState.readStream).thenAnswer((_) => const Stream.empty()); when(() => channelClientState.read).thenReturn([]); - when(() => channelClientState.membersStream) - .thenAnswer((_) => const Stream.empty()); + when(() => channelClientState.membersStream).thenAnswer((_) => const Stream.empty()); when(() => channelClientState.members).thenReturn([]); when(() => channelClientState.currentUserRead).thenReturn(null); - when(() => channelClientState.currentUserReadStream) - .thenAnswer((_) => const Stream.empty()); + when(() => channelClientState.currentUserReadStream).thenAnswer((_) => const Stream.empty()); }); // https://github.com/GetStream/stream-chat-flutter/issues/674 @@ -71,8 +62,7 @@ void main() { expect(find.byKey(emptyWidgetKey), findsOneWidget); }); - testWidgets('renders a non empty message list view with custom background', - (tester) async { + testWidgets('renders a non empty message list view with custom background', (tester) async { final message = Message( id: 'message1', text: 'Hello world!', @@ -136,8 +126,7 @@ void main() { ); }); - testWidgets('renders a non empty message list view with unread messages', - (tester) async { + testWidgets('renders a non empty message list view with unread messages', (tester) async { final user = OwnUser(id: 'testid'); final message = Message( id: 'message1', @@ -148,8 +137,7 @@ void main() { ), ); - when(() => channelClientState.read) - .thenReturn([Read(lastRead: DateTime.now(), user: user)]); + when(() => channelClientState.read).thenReturn([Read(lastRead: DateTime.now(), user: user)]); when(() => channelClientState.messagesStream).thenAnswer( (_) => Stream.value([message]), diff --git a/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart index c035a9cb4a..9f1dbab6ee 100644 --- a/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart @@ -238,19 +238,21 @@ Widget _wrapWithMaterialApp( data: StreamChatConfigurationData(reactionIcons: reactionIcons), child: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: ColoredBox( - color: theme.colorTheme.overlay, - child: Padding( - padding: const EdgeInsets.all(8), - child: child, + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: ColoredBox( + color: theme.colorTheme.overlay, + child: Padding( + padding: const EdgeInsets.all(8), + child: child, + ), ), - ), - ); - }), + ); + }, + ), ), ), ), diff --git a/packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart index 2b942ad0de..ed301bb778 100644 --- a/packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart @@ -255,19 +255,21 @@ Widget _wrapWithMaterialApp( data: StreamChatConfigurationData(reactionIcons: reactionIcons), child: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: ColoredBox( - color: theme.colorTheme.overlay, - child: Padding( - padding: const EdgeInsets.all(8), - child: child, + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: ColoredBox( + color: theme.colorTheme.overlay, + child: Padding( + padding: const EdgeInsets.all(8), + child: child, + ), ), - ), - ); - }), + ); + }, + ), ), ), ), diff --git a/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart index 59e76cf347..fa8a649810 100644 --- a/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart @@ -121,19 +121,21 @@ Widget _wrapWithMaterialApp( debugShowCheckedModeBanner: false, home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: ColoredBox( - color: theme.colorTheme.overlay, - child: Padding( - padding: const EdgeInsets.all(8), - child: child, + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: ColoredBox( + color: theme.colorTheme.overlay, + child: Padding( + padding: const EdgeInsets.all(8), + child: child, + ), ), - ), - ); - }), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/message_widget/deleted_message_test.dart b/packages/stream_chat_flutter/test/src/message_widget/deleted_message_test.dart index 6fc7bfe6eb..1768a5cbc3 100644 --- a/packages/stream_chat_flutter/test/src/message_widget/deleted_message_test.dart +++ b/packages/stream_chat_flutter/test/src/message_widget/deleted_message_test.dart @@ -62,8 +62,7 @@ void main() { }); when(() => clientState.totalUnreadCount).thenReturn(10); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(10)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(10)); final materialTheme = ThemeData.light( useMaterial3: false, @@ -117,8 +116,7 @@ void main() { }); when(() => clientState.totalUnreadCount).thenReturn(10); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(10)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(10)); final materialTheme = ThemeData.dark( useMaterial3: false, @@ -172,8 +170,7 @@ void main() { }); when(() => clientState.totalUnreadCount).thenReturn(10); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(10)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(10)); final materialTheme = ThemeData.light( useMaterial3: false, diff --git a/packages/stream_chat_flutter/test/src/message_widget/message_text_test.dart b/packages/stream_chat_flutter/test/src/message_widget/message_text_test.dart index 1b294f9c21..47949604af 100644 --- a/packages/stream_chat_flutter/test/src/message_widget/message_text_test.dart +++ b/packages/stream_chat_flutter/test/src/message_widget/message_text_test.dart @@ -46,35 +46,39 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(currentUser); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(currentUser)); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(currentUser)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); when(() => channel.isMuted).thenReturn(false); when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ - 'name': 'test', - })); + when(() => channel.extraDataStream).thenAnswer( + (i) => Stream.value({ + 'name': 'test', + }), + ); when(() => channel.extraData).thenReturn({ 'name': 'test', }); - await tester.pumpWidget(MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamMessageText( + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: StreamMessageText( message: Message( text: 'demo', ), - messageTheme: streamTheme.otherMessageTheme), + messageTheme: streamTheme.otherMessageTheme, + ), + ), ), ), ), - )); + ); // wait for the initial state to be rendered. await tester.pumpAndSettle(); @@ -98,8 +102,7 @@ void main() { setUp(() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(currentUser); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(currentUser)); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(currentUser)); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -202,8 +205,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(currentUser); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(currentUser)); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(currentUser)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); diff --git a/packages/stream_chat_flutter/test/src/misc/audio_waveform_test.dart b/packages/stream_chat_flutter/test/src/misc/audio_waveform_test.dart index e9a8b75902..d4eca37902 100644 --- a/packages/stream_chat_flutter/test/src/misc/audio_waveform_test.dart +++ b/packages/stream_chat_flutter/test/src/misc/audio_waveform_test.dart @@ -218,13 +218,15 @@ Widget _wrapWithMaterialApp( debugShowCheckedModeBanner: false, home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center(child: widget), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center(child: widget), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/misc/back_button_test.dart b/packages/stream_chat_flutter/test/src/misc/back_button_test.dart index c4ae6efc2a..5025c69c86 100644 --- a/packages/stream_chat_flutter/test/src/misc/back_button_test.dart +++ b/packages/stream_chat_flutter/test/src/misc/back_button_test.dart @@ -122,8 +122,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.totalUnreadCount).thenAnswer((_) => 0); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((_) => Stream.value(0)); + when(() => clientState.totalUnreadCountStream).thenAnswer((_) => Stream.value(0)); await tester.pumpWidget( MaterialApp( diff --git a/packages/stream_chat_flutter/test/src/misc/date_divider_test.dart b/packages/stream_chat_flutter/test/src/misc/date_divider_test.dart index af500a2039..a9eb6fb83a 100644 --- a/packages/stream_chat_flutter/test/src/misc/date_divider_test.dart +++ b/packages/stream_chat_flutter/test/src/misc/date_divider_test.dart @@ -49,8 +49,7 @@ void main() { child: Scaffold( body: StreamDateDivider( dateTime: testDate, - formatter: (context, date) => - 'Custom: ${date.day}/${date.month}', + formatter: (context, date) => 'Custom: ${date.day}/${date.month}', ), ), ), diff --git a/packages/stream_chat_flutter/test/src/misc/reaction_bubble_test.dart b/packages/stream_chat_flutter/test/src/misc/reaction_bubble_test.dart index 2dd7ef0b19..b51094fdf4 100644 --- a/packages/stream_chat_flutter/test/src/misc/reaction_bubble_test.dart +++ b/packages/stream_chat_flutter/test/src/misc/reaction_bubble_test.dart @@ -38,8 +38,7 @@ void main() { ), ], borderColor: theme.ownMessageTheme.reactionsBorderColor!, - backgroundColor: - theme.ownMessageTheme.reactionsBackgroundColor!, + backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!, maskColor: theme.ownMessageTheme.reactionsMaskColor!, ), ), @@ -78,8 +77,7 @@ void main() { ), ], borderColor: theme.ownMessageTheme.reactionsBorderColor!, - backgroundColor: - theme.ownMessageTheme.reactionsBackgroundColor!, + backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!, maskColor: theme.ownMessageTheme.reactionsMaskColor!, ), ), @@ -126,8 +124,7 @@ void main() { ), ], borderColor: theme.ownMessageTheme.reactionsBorderColor!, - backgroundColor: - theme.ownMessageTheme.reactionsBackgroundColor!, + backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!, maskColor: theme.ownMessageTheme.reactionsMaskColor!, ), ), @@ -174,8 +171,7 @@ void main() { ), ], borderColor: theme.ownMessageTheme.reactionsBorderColor!, - backgroundColor: - theme.ownMessageTheme.reactionsBackgroundColor!, + backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!, maskColor: theme.ownMessageTheme.reactionsMaskColor!, ), ), diff --git a/packages/stream_chat_flutter/test/src/misc/system_message_test.dart b/packages/stream_chat_flutter/test/src/misc/system_message_test.dart index bbbb8dd896..03e4234d09 100644 --- a/packages/stream_chat_flutter/test/src/misc/system_message_test.dart +++ b/packages/stream_chat_flutter/test/src/misc/system_message_test.dart @@ -32,27 +32,28 @@ void main() { }); when(() => clientState.totalUnreadCount).thenReturn(10); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(10)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(10)); var tapped = false; - await tester.pumpWidget(MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamSystemMessage( - onMessageTap: (m) => tapped = true, - message: Message( - text: 'demo message', + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: StreamSystemMessage( + onMessageTap: (m) => tapped = true, + message: Message( + text: 'demo message', + ), ), ), ), ), ), - )); + ); // wait for the initial state to be rendered. await tester.pumpAndSettle(); @@ -90,8 +91,7 @@ void main() { }); when(() => clientState.totalUnreadCount).thenReturn(10); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(10)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(10)); return MaterialAppWrapper( theme: ThemeData.light(), @@ -142,8 +142,7 @@ void main() { }); when(() => clientState.totalUnreadCount).thenReturn(10); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(10)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(10)); return MaterialAppWrapper( theme: ThemeData.dark(), diff --git a/packages/stream_chat_flutter/test/src/misc/thread_header_test.dart b/packages/stream_chat_flutter/test/src/misc/thread_header_test.dart index b7cbe4e96c..ba7b86056f 100644 --- a/packages/stream_chat_flutter/test/src/misc/thread_header_test.dart +++ b/packages/stream_chat_flutter/test/src/misc/thread_header_test.dart @@ -27,14 +27,13 @@ void main() { when(() => channel.name).thenReturn('test'); when(() => channel.nameStream).thenAnswer((i) => Stream.value('test')); when(() => channelState.unreadCount).thenReturn(1); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -43,25 +42,25 @@ void main() { user: User(id: 'user-id'), ), ]); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(1)); - await tester.pumpWidget(MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamThreadHeader( - parent: Message(), + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: StreamThreadHeader( + parent: Message(), + ), ), ), ), ), - )); + ); // wait for the initial state to be rendered. await tester.pumpAndSettle(); @@ -99,14 +98,13 @@ void main() { 'name': 'test', }); when(() => channelState.unreadCount).thenReturn(1); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -117,28 +115,30 @@ void main() { ]); var tapped = false; - await tester.pumpWidget(MaterialAppWrapper( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamThreadHeader( - parent: Message(), - subtitle: const Text('subtitle'), - leading: const Text('leading'), - title: const Text('title'), - onTitleTap: () { - tapped = true; - }, - actions: const [ - Text('action'), - ], + await tester.pumpWidget( + MaterialAppWrapper( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: StreamThreadHeader( + parent: Message(), + subtitle: const Text('subtitle'), + leading: const Text('leading'), + title: const Text('title'), + onTitleTap: () { + tapped = true; + }, + actions: const [ + Text('action'), + ], + ), ), ), ), ), - )); + ); // wait for the initial state to be rendered. await tester.pumpAndSettle(); diff --git a/packages/stream_chat_flutter/test/src/misc/timestamp_test.dart b/packages/stream_chat_flutter/test/src/misc/timestamp_test.dart index f08263ae77..0be181e0b6 100644 --- a/packages/stream_chat_flutter/test/src/misc/timestamp_test.dart +++ b/packages/stream_chat_flutter/test/src/misc/timestamp_test.dart @@ -34,13 +34,15 @@ Widget _wrapWithMaterialApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center(child: widget), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center(child: widget), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/mocks.dart b/packages/stream_chat_flutter/test/src/mocks.dart index 5418d29303..4e7c8872f1 100644 --- a/packages/stream_chat_flutter/test/src/mocks.dart +++ b/packages/stream_chat_flutter/test/src/mocks.dart @@ -6,8 +6,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class MockClient extends Mock implements StreamChatClient { MockClient() { when(() => wsConnectionStatus).thenReturn(ConnectionStatus.connected); - when(() => wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connected)); + when(() => wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connected)); } } @@ -95,8 +94,7 @@ class MockAttachment extends Mock implements Attachment {} class MockVlcManagerDesktop extends Mock implements VlcManagerDesktop {} -class MockStreamMemberListController extends Mock - implements StreamMemberListController { +class MockStreamMemberListController extends Mock implements StreamMemberListController { @override PagedValue value = const PagedValue.loading(); } diff --git a/packages/stream_chat_flutter/test/src/poll/creator/poll_option_reorderable_list_view_test.dart b/packages/stream_chat_flutter/test/src/poll/creator/poll_option_reorderable_list_view_test.dart index d0d6ab7e66..05ee6e69fd 100644 --- a/packages/stream_chat_flutter/test/src/poll/creator/poll_option_reorderable_list_view_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/creator/poll_option_reorderable_list_view_test.dart @@ -53,15 +53,17 @@ void main() { testWidgets('should enforce minimum options requirement', (tester) async { var optionsChanged = []; - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 3, max: null), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - ], - onOptionsChanged: (options) => optionsChanged = options, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 3, max: null), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + ], + onOptionsChanged: (options) => optionsChanged = options, + ), ), - )); + ); // Should automatically add options to meet minimum requirement final textFields = find.byType(TextField); @@ -73,16 +75,18 @@ void main() { }); testWidgets('should respect maximum options limit', (tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: null, max: 3), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - PollOptionItem(text: 'Option 3'), - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: null, max: 3), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + PollOptionItem(text: 'Option 3'), + ], + ), ), - )); + ); // Find the add button final addButton = find.byType(FilledButton); @@ -94,15 +98,17 @@ void main() { }); testWidgets('should respect both min and max options', (tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 2, max: 4), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 2, max: 4), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + ], + ), ), - )); + ); // Should have 2 options initially (meeting minimum) final textFields = find.byType(TextField); @@ -133,15 +139,17 @@ void main() { testWidgets( 'should work with unlimited options when max is null', (tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 2, max: null), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 2, max: null), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + ], + ), ), - )); + ); // Add button should be enabled for unlimited options final addButton = find.byType(FilledButton); @@ -153,14 +161,16 @@ void main() { group('Auto-Focus Functionality', () { testWidgets('should auto-focus on newly added option', (tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 1, max: null), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 1, max: null), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + ], + ), ), - )); + ); // Find the add button and tap it final addButton = find.byType(FilledButton); @@ -182,16 +192,18 @@ void main() { testWidgets( 'should disable add button when empty option exists', (tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: null, max: 5), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - PollOptionItem(text: ''), // Empty option - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: null, max: 5), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + PollOptionItem(text: ''), // Empty option + ], + ), ), - )); + ); // Find the add button final addButton = find.byType(FilledButton); @@ -206,15 +218,17 @@ void main() { testWidgets( 'should enable add button when no empty options exist', (tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: null, max: 5), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: null, max: 5), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + ], + ), ), - )); + ); // Find the add button final addButton = find.byType(FilledButton); @@ -229,15 +243,17 @@ void main() { testWidgets( 'should re-enable add button after filling empty option', (tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: null, max: 5), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: ''), // Empty option - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: null, max: 5), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: ''), // Empty option + ], + ), ), - )); + ); // Initially, add button should be disabled var addButton = find.byType(FilledButton); @@ -263,16 +279,18 @@ void main() { (tester) async { var optionsChanged = []; - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 2, max: 5), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - ], - onOptionsChanged: (options) => optionsChanged = options, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 2, max: 5), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + ], + onOptionsChanged: (options) => optionsChanged = options, + ), ), - )); + ); // Find the add button and tap it final addButton = find.byType(FilledButton); @@ -291,13 +309,15 @@ void main() { (tester) async { var optionsChanged = []; - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 2, max: null), - initialOptions: const [], // No initial options - onOptionsChanged: (options) => optionsChanged = options, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 2, max: null), + initialOptions: const [], // No initial options + onOptionsChanged: (options) => optionsChanged = options, + ), ), - )); + ); // Should auto-add options to meet minimum requirement final textFields = find.byType(TextField); @@ -307,27 +327,31 @@ void main() { ); testWidgets('should handle updating initial options', (tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - initialOptions: [ - PollOptionItem(text: 'Option 1'), - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + initialOptions: [ + PollOptionItem(text: 'Option 1'), + ], + ), ), - )); + ); // Initially should have 1 option expect(find.byType(TextField), findsNWidgets(1)); // Update with new options - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - PollOptionItem(text: 'Option 3'), - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + PollOptionItem(text: 'Option 3'), + ], + ), ), - )); + ); // Should now have 3 options expect(find.byType(TextField), findsNWidgets(3)); @@ -336,16 +360,18 @@ void main() { group('Delete Option Functionality', () { testWidgets('should show delete confirmation dialog', (tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 2, max: null), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - PollOptionItem(text: 'Option 3'), - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 2, max: null), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + PollOptionItem(text: 'Option 3'), + ], + ), ), - )); + ); // Find the delete buttons final deleteButtons = find.bySvgIcon(StreamSvgIcons.delete); @@ -368,17 +394,19 @@ void main() { testWidgets('should delete option when confirmed', (tester) async { var optionsChanged = []; - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 2, max: null), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - PollOptionItem(text: 'Option 3'), - ], - onOptionsChanged: (options) => optionsChanged = options, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 2, max: null), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + PollOptionItem(text: 'Option 3'), + ], + onOptionsChanged: (options) => optionsChanged = options, + ), ), - )); + ); // Initially should have 3 options expect(find.byType(TextField), findsNWidgets(3)); @@ -400,17 +428,19 @@ void main() { testWidgets('should not delete option when cancelled', (tester) async { var optionsChanged = []; - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 2, max: null), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - PollOptionItem(text: 'Option 3'), - ], - onOptionsChanged: (options) => optionsChanged = options, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 2, max: null), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + PollOptionItem(text: 'Option 3'), + ], + onOptionsChanged: (options) => optionsChanged = options, + ), ), - )); + ); // Initially should have 3 options expect(find.byType(TextField), findsNWidgets(3)); @@ -436,13 +466,15 @@ void main() { final option1 = PollOptionItem(text: 'Option 1'); final option2 = PollOptionItem(text: 'Option 2'); - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 2, max: null), - initialOptions: [option1, option2], - onOptionsChanged: (options) => optionsChanged = options, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 2, max: null), + initialOptions: [option1, option2], + onOptionsChanged: (options) => optionsChanged = options, + ), ), - )); + ); // Should have 2 options (minimum) expect(find.byType(TextField), findsNWidgets(2)); diff --git a/packages/stream_chat_flutter/test/src/poll/creator/poll_question_text_field_test.dart b/packages/stream_chat_flutter/test/src/poll/creator/poll_question_text_field_test.dart index 8a07c4e7a9..8a77888e51 100644 --- a/packages/stream_chat_flutter/test/src/poll/creator/poll_question_text_field_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/creator/poll_question_text_field_test.dart @@ -47,18 +47,20 @@ Widget _wrapWithMaterialApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: widget, + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: widget, + ), ), - ), - ); - }), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/poll/creator/stream_poll_creator_widget_test.dart b/packages/stream_chat_flutter/test/src/poll/creator/stream_poll_creator_widget_test.dart index ba45ab36cb..78eb1b99ae 100644 --- a/packages/stream_chat_flutter/test/src/poll/creator/stream_poll_creator_widget_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/creator/stream_poll_creator_widget_test.dart @@ -28,8 +28,7 @@ void main() { expect(find.byType(PollSwitchListTile), findsNWidgets(4)); }); - testWidgets('StreamPollCreatorWidget updates poll state correctly', - (tester) async { + testWidgets('StreamPollCreatorWidget updates poll state correctly', (tester) async { final controller = StreamPollController( config: const PollConfig( nameRange: (min: 1, max: 150), diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/poll_footer_test.dart b/packages/stream_chat_flutter/test/src/poll/interactor/poll_footer_test.dart index 1a61932345..516f8259d5 100644 --- a/packages/stream_chat_flutter/test/src/poll/interactor/poll_footer_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/interactor/poll_footer_test.dart @@ -20,13 +20,15 @@ void main() async { testWidgets( 'End Vote button is visible and enabled for the creator on open poll', (WidgetTester tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollFooter( - poll: poll.copyWith(createdBy: currentUser), - currentUser: currentUser, - onEndVote: () {}, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollFooter( + poll: poll.copyWith(createdBy: currentUser), + currentUser: currentUser, + onEndVote: () {}, + ), ), - )); + ); final endVoteButton = find.ancestor( of: find.text('End Vote'), @@ -45,13 +47,15 @@ void main() async { testWidgets( 'End Vote button is not visible for non-creator', (WidgetTester tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollFooter( - poll: poll, - currentUser: currentUser, - onEndVote: () {}, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollFooter( + poll: poll, + currentUser: currentUser, + onEndVote: () {}, + ), ), - )); + ); final endVoteButton = find.ancestor( of: find.text('End Vote'), @@ -65,16 +69,18 @@ void main() async { testWidgets( 'End Vote button is not visible for closed poll', (WidgetTester tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollFooter( - poll: poll.copyWith( - isClosed: true, - createdBy: currentUser, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollFooter( + poll: poll.copyWith( + isClosed: true, + createdBy: currentUser, + ), + currentUser: currentUser, + onEndVote: () {}, ), - currentUser: currentUser, - onEndVote: () {}, ), - )); + ); final endVoteButton = find.ancestor( of: find.text('End Vote'), @@ -88,13 +94,15 @@ void main() async { testWidgets( 'Add Comment button is visible and enabled when poll allows answers', (WidgetTester tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollFooter( - poll: poll.copyWith(allowAnswers: true), - currentUser: currentUser, - onAddComment: () {}, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollFooter( + poll: poll.copyWith(allowAnswers: true), + currentUser: currentUser, + onAddComment: () {}, + ), ), - )); + ); final addCommentButton = find.ancestor( of: find.text('Add a comment'), @@ -112,16 +120,18 @@ void main() async { testWidgets( 'Add Comment button is not visible when poll is closed', (WidgetTester tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollFooter( - poll: poll.copyWith( - isClosed: true, - allowAnswers: true, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollFooter( + poll: poll.copyWith( + isClosed: true, + allowAnswers: true, + ), + currentUser: currentUser, + onAddComment: () {}, ), - currentUser: currentUser, - onAddComment: () {}, ), - )); + ); final addCommentButton = find.ancestor( of: find.text('Add a comment'), @@ -135,13 +145,15 @@ void main() async { testWidgets( 'View Comments button is visible and enabled if there are answers', (WidgetTester tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollFooter( - poll: poll.copyWith(answersCount: 1), - currentUser: currentUser, - onViewComments: () {}, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollFooter( + poll: poll.copyWith(answersCount: 1), + currentUser: currentUser, + onViewComments: () {}, + ), ), - )); + ); final viewCommentsButton = find.ancestor( of: find.text('View Comments'), @@ -159,15 +171,17 @@ void main() async { testWidgets( 'View Comments button is not visible when there are no answers', (WidgetTester tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollFooter( - poll: poll.copyWith( - answersCount: 0, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollFooter( + poll: poll.copyWith( + answersCount: 0, + ), + currentUser: currentUser, + onViewComments: () {}, ), - currentUser: currentUser, - onViewComments: () {}, ), - )); + ); final viewCommentsButton = find.ancestor( of: find.text('View Comments'), @@ -181,15 +195,17 @@ void main() async { testWidgets( 'Suggest Option button is visible and enabled when allowed', (WidgetTester tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollFooter( - poll: poll.copyWith( - allowUserSuggestedOptions: true, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollFooter( + poll: poll.copyWith( + allowUserSuggestedOptions: true, + ), + currentUser: currentUser, + onSuggestOption: () {}, ), - currentUser: currentUser, - onSuggestOption: () {}, ), - )); + ); final suggestOptionButton = find.ancestor( of: find.text('Suggest an option'), @@ -207,16 +223,18 @@ void main() async { testWidgets( 'Suggest Option button is not visible when poll is closed', (WidgetTester tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollFooter( - poll: poll.copyWith( - isClosed: true, - allowUserSuggestedOptions: true, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollFooter( + poll: poll.copyWith( + isClosed: true, + allowUserSuggestedOptions: true, + ), + currentUser: currentUser, + onSuggestOption: () {}, ), - currentUser: currentUser, - onSuggestOption: () {}, ), - )); + ); final suggestOptionButton = find.ancestor( of: find.text('Suggest an option'), diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/poll_header_test.dart b/packages/stream_chat_flutter/test/src/poll/interactor/poll_header_test.dart index 301dd5d5f5..f09dcede2f 100644 --- a/packages/stream_chat_flutter/test/src/poll/interactor/poll_header_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/interactor/poll_header_test.dart @@ -99,17 +99,19 @@ Widget _wrapWithMaterialApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Container( - color: theme.colorTheme.disabled, - padding: const EdgeInsets.all(16), - child: Center(child: widget), - ), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Container( + color: theme.colorTheme.disabled, + padding: const EdgeInsets.all(16), + child: Center(child: widget), + ), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/poll_suggest_option_dialog_test.dart b/packages/stream_chat_flutter/test/src/poll/interactor/poll_suggest_option_dialog_test.dart index fb66e438df..3cb4b0bd11 100644 --- a/packages/stream_chat_flutter/test/src/poll/interactor/poll_suggest_option_dialog_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/interactor/poll_suggest_option_dialog_test.dart @@ -19,8 +19,7 @@ void main() { goldenTest( '[${brightness.name}] -> PollSuggestOptionDialog with initialOption looks fine', - fileName: - 'poll_suggest_option_dialog_with_initial_option_${brightness.name}', + fileName: 'poll_suggest_option_dialog_with_initial_option_${brightness.name}', constraints: const BoxConstraints.tightFor(width: 600, height: 300), builder: () => _wrapWithMaterialApp( brightness: brightness, @@ -39,17 +38,19 @@ Widget _wrapWithMaterialApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Container( - color: theme.colorTheme.disabled, - padding: const EdgeInsets.all(16), - child: Center(child: widget), - ), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Container( + color: theme.colorTheme.disabled, + padding: const EdgeInsets.all(16), + child: Center(child: widget), + ), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/stream_poll_interactor_test.dart b/packages/stream_chat_flutter/test/src/poll/interactor/stream_poll_interactor_test.dart index acc34896a3..edbdb33d95 100644 --- a/packages/stream_chat_flutter/test/src/poll/interactor/stream_poll_interactor_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/interactor/stream_poll_interactor_test.dart @@ -114,13 +114,15 @@ Widget _wrapWithMaterialApp( data: StreamChatConfigurationData(), child: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center(child: widget), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center(child: widget), + ); + }, + ), ), ), ); diff --git a/packages/stream_chat_flutter/test/src/poll/poll_option_reorderable_list_view_test.dart b/packages/stream_chat_flutter/test/src/poll/poll_option_reorderable_list_view_test.dart index 2609417970..6919559d5c 100644 --- a/packages/stream_chat_flutter/test/src/poll/poll_option_reorderable_list_view_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/poll_option_reorderable_list_view_test.dart @@ -68,18 +68,20 @@ Widget _wrapWithMaterialApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: widget, + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: widget, + ), ), - ), - ); - }), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/poll/poll_question_text_field_test.dart b/packages/stream_chat_flutter/test/src/poll/poll_question_text_field_test.dart index 24928bbc67..2c203c10ae 100644 --- a/packages/stream_chat_flutter/test/src/poll/poll_question_text_field_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/poll_question_text_field_test.dart @@ -55,18 +55,20 @@ Widget _wrapWithMaterialApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: widget, + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: widget, + ), ), - ), - ); - }), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/poll/stream_poll_options_dialog_test.dart b/packages/stream_chat_flutter/test/src/poll/stream_poll_options_dialog_test.dart index 81ebd027ac..0a2de54fec 100644 --- a/packages/stream_chat_flutter/test/src/poll/stream_poll_options_dialog_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/stream_poll_options_dialog_test.dart @@ -94,13 +94,15 @@ Widget _wrapWithMaterialApp( data: StreamChatConfigurationData(), child: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center(child: widget), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center(child: widget), + ); + }, + ), ), ), ); diff --git a/packages/stream_chat_flutter/test/src/poll/stream_poll_results_dialog_test.dart b/packages/stream_chat_flutter/test/src/poll/stream_poll_results_dialog_test.dart index e5b8df69d0..5488228bbe 100644 --- a/packages/stream_chat_flutter/test/src/poll/stream_poll_results_dialog_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/stream_poll_results_dialog_test.dart @@ -114,13 +114,15 @@ Widget _wrapWithMaterialApp( data: StreamChatConfigurationData(), child: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center(child: widget), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center(child: widget), + ); + }, + ), ), ), ); diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_icon_list_test.dart b/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_icon_list_test.dart index f1abc515ba..5971675bf2 100644 --- a/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_icon_list_test.dart +++ b/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_icon_list_test.dart @@ -233,18 +233,20 @@ Widget _wrapWithMaterialApp( debugShowCheckedModeBanner: false, home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.overlay, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: child, + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.overlay, + body: Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: child, + ), ), - ), - ); - }), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_test.dart b/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_test.dart index 72fc26913b..f23f41aaec 100644 --- a/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_test.dart @@ -259,18 +259,20 @@ Widget _wrapWithMaterialApp( debugShowCheckedModeBanner: false, home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.overlay, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: child, + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.overlay, + body: Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: child, + ), ), - ), - ); - }), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_icon_list_test.dart b/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_icon_list_test.dart index c72c145f15..1f6a4ec4cf 100644 --- a/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_icon_list_test.dart +++ b/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_icon_list_test.dart @@ -370,18 +370,20 @@ Widget _wrapWithMaterialApp( debugShowCheckedModeBanner: false, home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.overlay, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: child, + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.overlay, + body: Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: child, + ), ), - ), - ); - }), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_test.dart b/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_test.dart index 3b80f884e4..5828e34e8a 100644 --- a/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_test.dart +++ b/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_test.dart @@ -180,18 +180,20 @@ Widget _wrapWithMaterialApp( debugShowCheckedModeBanner: false, home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.overlay, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: child, + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.overlay, + body: Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: child, + ), ), - ), - ); - }), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/scroll_view/member_scroll_view/stream_member_list_view_test.dart b/packages/stream_chat_flutter/test/src/scroll_view/member_scroll_view/stream_member_list_view_test.dart index b5275a9790..cbf70c5dad 100644 --- a/packages/stream_chat_flutter/test/src/scroll_view/member_scroll_view/stream_member_list_view_test.dart +++ b/packages/stream_chat_flutter/test/src/scroll_view/member_scroll_view/stream_member_list_view_test.dart @@ -16,17 +16,14 @@ void main() { clientState = MockClientState(); when(() => client.state).thenAnswer((_) => clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'testid')); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(OwnUser(id: 'testid'))); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(OwnUser(id: 'testid'))); channel = MockChannel(); - when(() => channel.on(any(), any(), any(), any())) - .thenAnswer((_) => const Stream.empty()); + when(() => channel.on(any(), any(), any(), any())).thenAnswer((_) => const Stream.empty()); channelClientState = MockChannelState(); when(() => channel.client).thenReturn(client); when(() => channel.state).thenReturn(channelClientState); - when(() => channelClientState.membersStream) - .thenAnswer((_) => const Stream.empty()); + when(() => channelClientState.membersStream).thenAnswer((_) => const Stream.empty()); when(() => channelClientState.members).thenReturn([]); }); diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/stream_unread_threads_banner_test.dart b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/stream_unread_threads_banner_test.dart index 897d140aed..4c1a8e7a83 100644 --- a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/stream_unread_threads_banner_test.dart +++ b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/stream_unread_threads_banner_test.dart @@ -23,13 +23,15 @@ Widget _wrapWithMaterialApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center(child: widget), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center(child: widget), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/stream_chat_configuration_test.dart b/packages/stream_chat_flutter/test/src/stream_chat_configuration_test.dart index d9db650ab2..dcd94aba13 100644 --- a/packages/stream_chat_flutter/test/src/stream_chat_configuration_test.dart +++ b/packages/stream_chat_flutter/test/src/stream_chat_configuration_test.dart @@ -9,15 +9,17 @@ void main() { (t) async { final configuration = StreamChatConfigurationData(); late final StreamChatConfigurationData configurationFromProvider; - await t.pumpWidget(StreamChatConfiguration( - data: configuration, - child: Builder( - builder: (context) { - configurationFromProvider = StreamChatConfiguration.of(context); - return const SizedBox(); - }, + await t.pumpWidget( + StreamChatConfiguration( + data: configuration, + child: Builder( + builder: (context) { + configurationFromProvider = StreamChatConfiguration.of(context); + return const SizedBox(); + }, + ), ), - )); + ); expect(configuration, configurationFromProvider); }, @@ -30,15 +32,17 @@ void main() { enforceUniqueReactions: false, ); late final StreamChatConfigurationData configurationFromProvider; - await t.pumpWidget(StreamChatConfiguration( - data: configuration, - child: Builder( - builder: (context) { - configurationFromProvider = StreamChatConfiguration.of(context); - return const SizedBox(); - }, + await t.pumpWidget( + StreamChatConfiguration( + data: configuration, + child: Builder( + builder: (context) { + configurationFromProvider = StreamChatConfiguration.of(context); + return const SizedBox(); + }, + ), ), - )); + ); expect(configuration, configurationFromProvider); }, diff --git a/packages/stream_chat_flutter/test/src/theme/avatar_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/avatar_theme_test.dart index 44f6f85113..bda9511ed9 100644 --- a/packages/stream_chat_flutter/test/src/theme/avatar_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/avatar_theme_test.dart @@ -4,18 +4,16 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() { test('AvatarThemeData copyWith, ==, hashCode basics', () { - expect(const StreamAvatarThemeData(), - const StreamAvatarThemeData().copyWith()); - expect(const StreamAvatarThemeData().hashCode, - const StreamAvatarThemeData().copyWith().hashCode); + expect(const StreamAvatarThemeData(), const StreamAvatarThemeData().copyWith()); + expect(const StreamAvatarThemeData().hashCode, const StreamAvatarThemeData().copyWith().hashCode); }); group('AvatarThemeData lerps correctly', () { test('Lerp completely', () { expect( - const StreamAvatarThemeData() - .lerp(_avatarThemeDataControl1, _avatarThemeDataControl2, 1), - _avatarThemeDataControl2); + const StreamAvatarThemeData().lerp(_avatarThemeDataControl1, _avatarThemeDataControl2, 1), + _avatarThemeDataControl2, + ); }); test('Lerp halfway', () { @@ -34,8 +32,7 @@ void main() { }); test('Merging two AvatarThemeData results in the latter', () { - expect(_avatarThemeDataControl1.merge(_avatarThemeDataControl2), - _avatarThemeDataControl2); + expect(_avatarThemeDataControl1.merge(_avatarThemeDataControl2), _avatarThemeDataControl2); }); } diff --git a/packages/stream_chat_flutter/test/src/theme/channel_header_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/channel_header_theme_test.dart index 1b9706154a..65cd2b5a0f 100644 --- a/packages/stream_chat_flutter/test/src/theme/channel_header_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/channel_header_theme_test.dart @@ -4,25 +4,19 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() { test('ChannelHeaderThemeData copyWith, ==, hashCode basics', () { - expect(const StreamChannelHeaderThemeData(), - const StreamChannelHeaderThemeData().copyWith()); - expect(const StreamChannelHeaderThemeData().hashCode, - const StreamChannelHeaderThemeData().copyWith().hashCode); + expect(const StreamChannelHeaderThemeData(), const StreamChannelHeaderThemeData().copyWith()); + expect(const StreamChannelHeaderThemeData().hashCode, const StreamChannelHeaderThemeData().copyWith().hashCode); }); group('ChannelHeaderThemeData lerps', () { - test( - '''Light ChannelHeaderThemeData lerps completely to dark ChannelHeaderThemeData''', - () { + test('''Light ChannelHeaderThemeData lerps completely to dark ChannelHeaderThemeData''', () { expect( - const StreamChannelHeaderThemeData() - .lerp(_channelThemeControl, _channelThemeControlDark, 1), - _channelThemeControlDark); + const StreamChannelHeaderThemeData().lerp(_channelThemeControl, _channelThemeControlDark, 1), + _channelThemeControlDark, + ); }); - test( - '''Light ChannelHeaderThemeData lerps halfway to dark ChannelHeaderThemeData''', - () { + test('''Light ChannelHeaderThemeData lerps halfway to dark ChannelHeaderThemeData''', () { expect( const StreamChannelHeaderThemeData().lerp( _channelThemeControl, @@ -36,19 +30,16 @@ void main() { ); }); - test( - '''Dark ChannelHeaderThemeData lerps completely to light ChannelHeaderThemeData''', - () { + test('''Dark ChannelHeaderThemeData lerps completely to light ChannelHeaderThemeData''', () { expect( - const StreamChannelHeaderThemeData() - .lerp(_channelThemeControlDark, _channelThemeControl, 1), - _channelThemeControl); + const StreamChannelHeaderThemeData().lerp(_channelThemeControlDark, _channelThemeControl, 1), + _channelThemeControl, + ); }); }); test('Merging dark and light themes results in a dark theme', () { - expect(_channelThemeControl.merge(_channelThemeControlDark), - _channelThemeControlDark); + expect(_channelThemeControl.merge(_channelThemeControlDark), _channelThemeControlDark); }); } @@ -62,11 +53,11 @@ final _channelThemeControl = StreamChannelHeaderThemeData( ), color: const Color(0xff101418), titleStyle: const StreamTextTheme.light().headlineBold.copyWith( - color: const Color(0xffffffff), - ), + color: const Color(0xffffffff), + ), subtitleStyle: const StreamTextTheme.light().footnote.copyWith( - color: const Color(0xff7a7a7a), - ), + color: const Color(0xff7a7a7a), + ), ); final _channelThemeControlMidLerp = StreamChannelHeaderThemeData( @@ -84,8 +75,8 @@ final _channelThemeControlMidLerp = StreamChannelHeaderThemeData( fontSize: 16, ), subtitleStyle: const StreamTextTheme.light().footnote.copyWith( - color: const Color(0xff7a7a7a), - ), + color: const Color(0xff7a7a7a), + ), ); final _channelThemeControlDark = StreamChannelHeaderThemeData( @@ -99,6 +90,6 @@ final _channelThemeControlDark = StreamChannelHeaderThemeData( color: const StreamColorTheme.dark().barsBg, titleStyle: const StreamTextTheme.dark().headlineBold, subtitleStyle: const StreamTextTheme.dark().footnote.copyWith( - color: const Color(0xff7A7A7A), - ), + color: const Color(0xff7A7A7A), + ), ); diff --git a/packages/stream_chat_flutter/test/src/theme/channel_list_header_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/channel_list_header_theme_test.dart index b7bfcbfebd..883fb441be 100644 --- a/packages/stream_chat_flutter/test/src/theme/channel_list_header_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/channel_list_header_theme_test.dart @@ -4,27 +4,26 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() { test('ChannelListHeaderThemeData copyWith, ==, hashCode basics', () { - expect(const StreamChannelListHeaderThemeData(), - const StreamChannelListHeaderThemeData().copyWith()); - expect(const StreamChannelListHeaderThemeData().hashCode, - const StreamChannelListHeaderThemeData().copyWith().hashCode); + expect(const StreamChannelListHeaderThemeData(), const StreamChannelListHeaderThemeData().copyWith()); + expect( + const StreamChannelListHeaderThemeData().hashCode, + const StreamChannelListHeaderThemeData().copyWith().hashCode, + ); }); group('ChannelListHeaderThemeData lerps', () { - test( - '''Light ChannelListHeaderThemeData lerps completely to dark ChannelListHeaderThemeData''', - () { + test('''Light ChannelListHeaderThemeData lerps completely to dark ChannelListHeaderThemeData''', () { expect( - const StreamChannelListHeaderThemeData().lerp( - _channelListHeaderThemeControl, - _channelListHeaderThemeControlDark, - 1), - _channelListHeaderThemeControlDark); + const StreamChannelListHeaderThemeData().lerp( + _channelListHeaderThemeControl, + _channelListHeaderThemeControlDark, + 1, + ), + _channelListHeaderThemeControlDark, + ); }); - test( - '''Light ChannelListHeaderThemeData lerps halfway to dark ChannelListHeaderThemeData''', - () { + test('''Light ChannelListHeaderThemeData lerps halfway to dark ChannelListHeaderThemeData''', () { expect( const StreamChannelListHeaderThemeData().lerp( _channelListHeaderThemeControl, @@ -38,23 +37,23 @@ void main() { ); }); - test( - '''Dark ChannelListHeaderThemeData lerps completely to light ChannelListHeaderThemeData''', - () { + test('''Dark ChannelListHeaderThemeData lerps completely to light ChannelListHeaderThemeData''', () { expect( - const StreamChannelListHeaderThemeData().lerp( - _channelListHeaderThemeControlDark, - _channelListHeaderThemeControl, - 1), - _channelListHeaderThemeControl); + const StreamChannelListHeaderThemeData().lerp( + _channelListHeaderThemeControlDark, + _channelListHeaderThemeControl, + 1, + ), + _channelListHeaderThemeControl, + ); }); }); test('Merging dark and light themes results in a dark theme', () { expect( - _channelListHeaderThemeControl - .merge(_channelListHeaderThemeControlDark), - _channelListHeaderThemeControlDark); + _channelListHeaderThemeControl.merge(_channelListHeaderThemeControlDark), + _channelListHeaderThemeControlDark, + ); }); } diff --git a/packages/stream_chat_flutter/test/src/theme/channel_preview_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/channel_preview_theme_test.dart index 51664dd9f7..b0fd386b2e 100644 --- a/packages/stream_chat_flutter/test/src/theme/channel_preview_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/channel_preview_theme_test.dart @@ -6,25 +6,19 @@ String _dummyFormatter(BuildContext context, DateTime date) => 'formatted'; void main() { test('ChannelPreviewThemeData copyWith, ==, hashCode basics', () { - expect(const StreamChannelPreviewThemeData(), - const StreamChannelPreviewThemeData().copyWith()); - expect(const StreamChannelPreviewThemeData().hashCode, - const StreamChannelPreviewThemeData().copyWith().hashCode); + expect(const StreamChannelPreviewThemeData(), const StreamChannelPreviewThemeData().copyWith()); + expect(const StreamChannelPreviewThemeData().hashCode, const StreamChannelPreviewThemeData().copyWith().hashCode); }); group('ChannelPreviewThemeData lerps', () { - test( - '''Light ChannelPreviewThemeData lerps completely to dark ChannelPreviewThemeData''', - () { + test('''Light ChannelPreviewThemeData lerps completely to dark ChannelPreviewThemeData''', () { expect( - const StreamChannelPreviewThemeData().lerp( - _channelPreviewThemeControl, _channelPreviewThemeControlDark, 1), - _channelPreviewThemeControlDark); + const StreamChannelPreviewThemeData().lerp(_channelPreviewThemeControl, _channelPreviewThemeControlDark, 1), + _channelPreviewThemeControlDark, + ); }); - test( - '''Light ChannelPreviewThemeData lerps halfway to dark ChannelPreviewThemeData''', - () { + test('''Light ChannelPreviewThemeData lerps halfway to dark ChannelPreviewThemeData''', () { expect( const StreamChannelPreviewThemeData().lerp( _channelPreviewThemeControl, @@ -38,19 +32,16 @@ void main() { ); }); - test( - '''Dark ChannelPreviewThemeData lerps completely to light ChannelPreviewThemeData''', - () { + test('''Dark ChannelPreviewThemeData lerps completely to light ChannelPreviewThemeData''', () { expect( - const StreamChannelPreviewThemeData().lerp( - _channelPreviewThemeControlDark, _channelPreviewThemeControl, 1), - _channelPreviewThemeControl); + const StreamChannelPreviewThemeData().lerp(_channelPreviewThemeControlDark, _channelPreviewThemeControl, 1), + _channelPreviewThemeControl, + ); }); }); test('Merging dark and light themes results in a dark theme', () { - expect(_channelPreviewThemeControl.merge(_channelPreviewThemeControlDark), - _channelPreviewThemeControlDark); + expect(_channelPreviewThemeControl.merge(_channelPreviewThemeControlDark), _channelPreviewThemeControlDark); }); } @@ -65,12 +56,12 @@ final _channelPreviewThemeControl = StreamChannelPreviewThemeData( ), titleStyle: const StreamTextTheme.light().bodyBold, subtitleStyle: const StreamTextTheme.light().footnote.copyWith( - color: const Color(0xff7A7A7A), - ), + color: const Color(0xff7A7A7A), + ), lastMessageAtStyle: const StreamTextTheme.light().footnote.copyWith( - // ignore: deprecated_member_use - color: const StreamColorTheme.light().textHighEmphasis.withOpacity(0.5), - ), + // ignore: deprecated_member_use + color: const StreamColorTheme.light().textHighEmphasis.withOpacity(0.5), + ), lastMessageAtFormatter: _dummyFormatter, indicatorIconSize: 16, ); @@ -95,9 +86,9 @@ final _channelPreviewThemeControlMidLerp = StreamChannelPreviewThemeData( fontWeight: FontWeight.w400, ), lastMessageAtStyle: const StreamTextTheme.light().footnote.copyWith( - // ignore: deprecated_member_use - color: const Color(0x807f7f7f).withOpacity(0.5), - ), + // ignore: deprecated_member_use + color: const Color(0x807f7f7f).withOpacity(0.5), + ), lastMessageAtFormatter: _dummyFormatter, indicatorIconSize: 16, ); @@ -113,12 +104,12 @@ final _channelPreviewThemeControlDark = StreamChannelPreviewThemeData( ), titleStyle: const StreamTextTheme.dark().bodyBold, subtitleStyle: const StreamTextTheme.dark().footnote.copyWith( - color: const Color(0xff7A7A7A), - ), + color: const Color(0xff7A7A7A), + ), lastMessageAtStyle: const StreamTextTheme.dark().footnote.copyWith( - // ignore: deprecated_member_use - color: const StreamColorTheme.dark().textHighEmphasis.withOpacity(0.5), - ), + // ignore: deprecated_member_use + color: const StreamColorTheme.dark().textHighEmphasis.withOpacity(0.5), + ), lastMessageAtFormatter: _dummyFormatter, indicatorIconSize: 16, ); diff --git a/packages/stream_chat_flutter/test/src/theme/draft_list_tile_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/draft_list_tile_theme_test.dart index e7f91b0fba..2f9e4c939b 100644 --- a/packages/stream_chat_flutter/test/src/theme/draft_list_tile_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/draft_list_tile_theme_test.dart @@ -5,8 +5,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; String _dummyFormatter(BuildContext context, DateTime date) => 'formatted'; void main() { - testWidgets('StreamDraftListTileTheme merges with ancestor theme', - (tester) async { + testWidgets('StreamDraftListTileTheme merges with ancestor theme', (tester) async { const backgroundColor = Colors.blue; const childBackgroundColor = Colors.red; @@ -164,14 +163,14 @@ void main() { // t = 0.5 should return something in between final lerpedAt05 = data1.lerp(data1, data2, 0.5); - expect(lerpedAt05.backgroundColor, - Color.lerp(Colors.black, Colors.white, 0.5)); + expect(lerpedAt05.backgroundColor, Color.lerp(Colors.black, Colors.white, 0.5)); expect( - lerpedAt05.padding, - EdgeInsetsGeometry.lerp( - const EdgeInsets.all(8), - const EdgeInsets.all(16), - 0.5, - )); + lerpedAt05.padding, + EdgeInsetsGeometry.lerp( + const EdgeInsets.all(8), + const EdgeInsets.all(16), + 0.5, + ), + ); }); } diff --git a/packages/stream_chat_flutter/test/src/theme/gallery_footer_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/gallery_footer_theme_test.dart index c02d77cccc..fb0c5699a0 100644 --- a/packages/stream_chat_flutter/test/src/theme/gallery_footer_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/gallery_footer_theme_test.dart @@ -7,26 +7,18 @@ class MockStreamChatClient extends Mock implements StreamChatClient {} void main() { test('GalleryFooterThemeData copyWith, ==, hashCode basics', () { - expect(const StreamGalleryFooterThemeData(), - const StreamGalleryFooterThemeData().copyWith()); - expect(const StreamGalleryFooterThemeData().hashCode, - const StreamGalleryFooterThemeData().copyWith().hashCode); + expect(const StreamGalleryFooterThemeData(), const StreamGalleryFooterThemeData().copyWith()); + expect(const StreamGalleryFooterThemeData().hashCode, const StreamGalleryFooterThemeData().copyWith().hashCode); }); - test( - '''Light GalleryFooterThemeData lerps completely to dark GalleryFooterThemeData''', - () { + test('''Light GalleryFooterThemeData lerps completely to dark GalleryFooterThemeData''', () { expect( - const StreamGalleryFooterThemeData().lerp( - _galleryFooterThemeDataControl, - _galleryFooterThemeDataControlDark, - 1), - _galleryFooterThemeDataControlDark); + const StreamGalleryFooterThemeData().lerp(_galleryFooterThemeDataControl, _galleryFooterThemeDataControlDark, 1), + _galleryFooterThemeDataControlDark, + ); }); - test( - '''Light GalleryFooterThemeData lerps halfway to dark GalleryFooterThemeData''', - () { + test('''Light GalleryFooterThemeData lerps halfway to dark GalleryFooterThemeData''', () { expect( const StreamGalleryFooterThemeData().lerp( _galleryFooterThemeDataControl, @@ -40,34 +32,25 @@ void main() { ); }); - test( - '''Dark GalleryFooterThemeData lerps completely to light GalleryFooterThemeData''', - () { + test('''Dark GalleryFooterThemeData lerps completely to light GalleryFooterThemeData''', () { expect( - const StreamGalleryFooterThemeData().lerp( - _galleryFooterThemeDataControlDark, - _galleryFooterThemeDataControl, - 1), - _galleryFooterThemeDataControl); + const StreamGalleryFooterThemeData().lerp(_galleryFooterThemeDataControlDark, _galleryFooterThemeDataControl, 1), + _galleryFooterThemeDataControl, + ); }); test('Merging dark and light themes results in a dark theme', () { expect( - _galleryFooterThemeDataControl - .merge(_galleryFooterThemeDataControlDark), - _galleryFooterThemeDataControlDark); + _galleryFooterThemeDataControl.merge(_galleryFooterThemeDataControlDark), + _galleryFooterThemeDataControlDark, + ); }); test('Merging dark and light themes results in a dark theme', () { - expect( - _galleryFooterThemeDataControlDark - .merge(_galleryFooterThemeDataControl), - _galleryFooterThemeDataControl); + expect(_galleryFooterThemeDataControlDark.merge(_galleryFooterThemeDataControl), _galleryFooterThemeDataControl); }); - testWidgets( - 'Passing no GalleryFooterThemeData returns default light theme values', - (WidgetTester tester) async { + testWidgets('Passing no GalleryFooterThemeData returns default light theme values', (WidgetTester tester) async { late BuildContext _context; await tester.pumpWidget( MaterialApp( @@ -85,27 +68,17 @@ void main() { ); final imageFooterTheme = StreamGalleryFooterTheme.of(_context); - expect(imageFooterTheme.backgroundColor, - _galleryFooterThemeDataControl.backgroundColor); - expect(imageFooterTheme.shareIconColor, - _galleryFooterThemeDataControl.shareIconColor); - expect(imageFooterTheme.titleTextStyle, - _galleryFooterThemeDataControl.titleTextStyle); - expect(imageFooterTheme.gridIconButtonColor, - _galleryFooterThemeDataControl.gridIconButtonColor); - expect(imageFooterTheme.bottomSheetBarrierColor, - _galleryFooterThemeDataControl.bottomSheetBarrierColor); - expect(imageFooterTheme.bottomSheetBackgroundColor, - _galleryFooterThemeDataControl.bottomSheetBackgroundColor); - expect(imageFooterTheme.bottomSheetCloseIconColor, - _galleryFooterThemeDataControl.bottomSheetCloseIconColor); - expect(imageFooterTheme.bottomSheetPhotosTextStyle, - _galleryFooterThemeDataControl.bottomSheetPhotosTextStyle); + expect(imageFooterTheme.backgroundColor, _galleryFooterThemeDataControl.backgroundColor); + expect(imageFooterTheme.shareIconColor, _galleryFooterThemeDataControl.shareIconColor); + expect(imageFooterTheme.titleTextStyle, _galleryFooterThemeDataControl.titleTextStyle); + expect(imageFooterTheme.gridIconButtonColor, _galleryFooterThemeDataControl.gridIconButtonColor); + expect(imageFooterTheme.bottomSheetBarrierColor, _galleryFooterThemeDataControl.bottomSheetBarrierColor); + expect(imageFooterTheme.bottomSheetBackgroundColor, _galleryFooterThemeDataControl.bottomSheetBackgroundColor); + expect(imageFooterTheme.bottomSheetCloseIconColor, _galleryFooterThemeDataControl.bottomSheetCloseIconColor); + expect(imageFooterTheme.bottomSheetPhotosTextStyle, _galleryFooterThemeDataControl.bottomSheetPhotosTextStyle); }); - testWidgets( - 'Passing no GalleryFooterThemeData returns default dark theme values', - (WidgetTester tester) async { + testWidgets('Passing no GalleryFooterThemeData returns default dark theme values', (WidgetTester tester) async { late BuildContext _context; await tester.pumpWidget( MaterialApp( @@ -124,22 +97,14 @@ void main() { ); final imageFooterTheme = StreamGalleryFooterTheme.of(_context); - expect(imageFooterTheme.backgroundColor, - _galleryFooterThemeDataControlDark.backgroundColor); - expect(imageFooterTheme.shareIconColor, - _galleryFooterThemeDataControlDark.shareIconColor); - expect(imageFooterTheme.titleTextStyle, - _galleryFooterThemeDataControlDark.titleTextStyle); - expect(imageFooterTheme.gridIconButtonColor, - _galleryFooterThemeDataControlDark.gridIconButtonColor); - expect(imageFooterTheme.bottomSheetBarrierColor, - _galleryFooterThemeDataControlDark.bottomSheetBarrierColor); - expect(imageFooterTheme.bottomSheetBackgroundColor, - _galleryFooterThemeDataControlDark.bottomSheetBackgroundColor); - expect(imageFooterTheme.bottomSheetCloseIconColor, - _galleryFooterThemeDataControlDark.bottomSheetCloseIconColor); - expect(imageFooterTheme.bottomSheetPhotosTextStyle, - _galleryFooterThemeDataControlDark.bottomSheetPhotosTextStyle); + expect(imageFooterTheme.backgroundColor, _galleryFooterThemeDataControlDark.backgroundColor); + expect(imageFooterTheme.shareIconColor, _galleryFooterThemeDataControlDark.shareIconColor); + expect(imageFooterTheme.titleTextStyle, _galleryFooterThemeDataControlDark.titleTextStyle); + expect(imageFooterTheme.gridIconButtonColor, _galleryFooterThemeDataControlDark.gridIconButtonColor); + expect(imageFooterTheme.bottomSheetBarrierColor, _galleryFooterThemeDataControlDark.bottomSheetBarrierColor); + expect(imageFooterTheme.bottomSheetBackgroundColor, _galleryFooterThemeDataControlDark.bottomSheetBackgroundColor); + expect(imageFooterTheme.bottomSheetCloseIconColor, _galleryFooterThemeDataControlDark.bottomSheetCloseIconColor); + expect(imageFooterTheme.bottomSheetPhotosTextStyle, _galleryFooterThemeDataControlDark.bottomSheetPhotosTextStyle); }); } diff --git a/packages/stream_chat_flutter/test/src/theme/gallery_header_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/gallery_header_theme_test.dart index 6c080193ed..f3bd223539 100644 --- a/packages/stream_chat_flutter/test/src/theme/gallery_header_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/gallery_header_theme_test.dart @@ -7,26 +7,18 @@ class MockStreamChatClient extends Mock implements StreamChatClient {} void main() { test('GalleryHeaderThemeData copyWith, ==, hashCode basics', () { - expect(const StreamGalleryHeaderThemeData(), - const StreamGalleryHeaderThemeData().copyWith()); - expect(const StreamGalleryHeaderThemeData().hashCode, - const StreamGalleryHeaderThemeData().copyWith().hashCode); + expect(const StreamGalleryHeaderThemeData(), const StreamGalleryHeaderThemeData().copyWith()); + expect(const StreamGalleryHeaderThemeData().hashCode, const StreamGalleryHeaderThemeData().copyWith().hashCode); }); - test( - '''Light GalleryHeaderThemeData lerps completely to dark GalleryHeaderThemeData''', - () { + test('''Light GalleryHeaderThemeData lerps completely to dark GalleryHeaderThemeData''', () { expect( - const StreamGalleryHeaderThemeData().lerp( - _galleryHeaderThemeDataControl, - _galleryHeaderThemeDataDarkControl, - 1), - _galleryHeaderThemeDataDarkControl); + const StreamGalleryHeaderThemeData().lerp(_galleryHeaderThemeDataControl, _galleryHeaderThemeDataDarkControl, 1), + _galleryHeaderThemeDataDarkControl, + ); }); - test( - '''Light GalleryHeaderThemeData lerps halfway to dark GalleryHeaderThemeData''', - () { + test('''Light GalleryHeaderThemeData lerps halfway to dark GalleryHeaderThemeData''', () { expect( const StreamGalleryHeaderThemeData().lerp( _galleryHeaderThemeDataControl, @@ -40,27 +32,21 @@ void main() { ); }); - test( - '''Dark GalleryHeaderThemeData lerps completely to light GalleryHeaderThemeData''', - () { + test('''Dark GalleryHeaderThemeData lerps completely to light GalleryHeaderThemeData''', () { expect( - const StreamGalleryHeaderThemeData().lerp( - _galleryHeaderThemeDataDarkControl, - _galleryHeaderThemeDataControl, - 1), - _galleryHeaderThemeDataControl); + const StreamGalleryHeaderThemeData().lerp(_galleryHeaderThemeDataDarkControl, _galleryHeaderThemeDataControl, 1), + _galleryHeaderThemeDataControl, + ); }); test('Merging dark and light themes results in a dark theme', () { expect( - _galleryHeaderThemeDataControl - .merge(_galleryHeaderThemeDataDarkControl), - _galleryHeaderThemeDataDarkControl); + _galleryHeaderThemeDataControl.merge(_galleryHeaderThemeDataDarkControl), + _galleryHeaderThemeDataDarkControl, + ); }); - testWidgets( - 'Passing no GalleryHeaderThemeData returns default light theme values', - (WidgetTester tester) async { + testWidgets('Passing no GalleryHeaderThemeData returns default light theme values', (WidgetTester tester) async { late BuildContext _context; await tester.pumpWidget( MaterialApp( @@ -78,23 +64,15 @@ void main() { ); final imageHeaderTheme = StreamGalleryHeaderTheme.of(_context); - expect(imageHeaderTheme.closeButtonColor, - _galleryHeaderThemeDataControl.closeButtonColor); - expect(imageHeaderTheme.backgroundColor, - _galleryHeaderThemeDataControl.backgroundColor); - expect(imageHeaderTheme.iconMenuPointColor, - _galleryHeaderThemeDataControl.iconMenuPointColor); - expect(imageHeaderTheme.titleTextStyle, - _galleryHeaderThemeDataControl.titleTextStyle); - expect(imageHeaderTheme.subtitleTextStyle, - _galleryHeaderThemeDataControl.subtitleTextStyle); - expect(imageHeaderTheme.bottomSheetBarrierColor, - _galleryHeaderThemeDataControl.bottomSheetBarrierColor); + expect(imageHeaderTheme.closeButtonColor, _galleryHeaderThemeDataControl.closeButtonColor); + expect(imageHeaderTheme.backgroundColor, _galleryHeaderThemeDataControl.backgroundColor); + expect(imageHeaderTheme.iconMenuPointColor, _galleryHeaderThemeDataControl.iconMenuPointColor); + expect(imageHeaderTheme.titleTextStyle, _galleryHeaderThemeDataControl.titleTextStyle); + expect(imageHeaderTheme.subtitleTextStyle, _galleryHeaderThemeDataControl.subtitleTextStyle); + expect(imageHeaderTheme.bottomSheetBarrierColor, _galleryHeaderThemeDataControl.bottomSheetBarrierColor); }); - testWidgets( - 'Passing no GalleryHeaderThemeData returns default dark theme values', - (WidgetTester tester) async { + testWidgets('Passing no GalleryHeaderThemeData returns default dark theme values', (WidgetTester tester) async { late BuildContext _context; await tester.pumpWidget( MaterialApp( @@ -113,18 +91,12 @@ void main() { ); final imageHeaderTheme = StreamGalleryHeaderTheme.of(_context); - expect(imageHeaderTheme.closeButtonColor, - _galleryHeaderThemeDataDarkControl.closeButtonColor); - expect(imageHeaderTheme.backgroundColor, - _galleryHeaderThemeDataDarkControl.backgroundColor); - expect(imageHeaderTheme.iconMenuPointColor, - _galleryHeaderThemeDataDarkControl.iconMenuPointColor); - expect(imageHeaderTheme.titleTextStyle, - _galleryHeaderThemeDataDarkControl.titleTextStyle); - expect(imageHeaderTheme.subtitleTextStyle, - _galleryHeaderThemeDataDarkControl.subtitleTextStyle); - expect(imageHeaderTheme.bottomSheetBarrierColor, - _galleryHeaderThemeDataDarkControl.bottomSheetBarrierColor); + expect(imageHeaderTheme.closeButtonColor, _galleryHeaderThemeDataDarkControl.closeButtonColor); + expect(imageHeaderTheme.backgroundColor, _galleryHeaderThemeDataDarkControl.backgroundColor); + expect(imageHeaderTheme.iconMenuPointColor, _galleryHeaderThemeDataDarkControl.iconMenuPointColor); + expect(imageHeaderTheme.titleTextStyle, _galleryHeaderThemeDataDarkControl.titleTextStyle); + expect(imageHeaderTheme.subtitleTextStyle, _galleryHeaderThemeDataDarkControl.subtitleTextStyle); + expect(imageHeaderTheme.bottomSheetBarrierColor, _galleryHeaderThemeDataDarkControl.bottomSheetBarrierColor); }); } @@ -138,13 +110,14 @@ final _galleryHeaderThemeDataControl = StreamGalleryHeaderThemeData( fontWeight: FontWeight.w500, color: Colors.black, ), - subtitleTextStyle: const TextStyle( - fontSize: 12, - color: Colors.black, - fontWeight: FontWeight.w400, - ).copyWith( - color: const Color(0xff7A7A7A), - ), + subtitleTextStyle: + const TextStyle( + fontSize: 12, + color: Colors.black, + fontWeight: FontWeight.w400, + ).copyWith( + color: const Color(0xff7A7A7A), + ), bottomSheetBarrierColor: const Color.fromRGBO(0, 0, 0, 0.2), ); @@ -158,13 +131,14 @@ final _galleryHeaderThemeDataHalfLerpControl = StreamGalleryHeaderThemeData( fontWeight: FontWeight.w500, color: Color(0xff7f7f7f), ), - subtitleTextStyle: const TextStyle( - fontSize: 12, - color: Color(0xff7a7a7a), - fontWeight: FontWeight.w400, - ).copyWith( - color: const Color(0xff7A7A7A), - ), + subtitleTextStyle: + const TextStyle( + fontSize: 12, + color: Color(0xff7a7a7a), + fontWeight: FontWeight.w400, + ).copyWith( + color: const Color(0xff7A7A7A), + ), bottomSheetBarrierColor: const Color(0x4c000000), ); @@ -178,12 +152,13 @@ final _galleryHeaderThemeDataDarkControl = StreamGalleryHeaderThemeData( fontWeight: FontWeight.w500, color: Colors.white, ), - subtitleTextStyle: const TextStyle( - fontSize: 12, - color: Colors.white, - fontWeight: FontWeight.w400, - ).copyWith( - color: const Color(0xff7A7A7A), - ), + subtitleTextStyle: + const TextStyle( + fontSize: 12, + color: Colors.white, + fontWeight: FontWeight.w400, + ).copyWith( + color: const Color(0xff7A7A7A), + ), bottomSheetBarrierColor: const Color.fromRGBO(0, 0, 0, 0.4), ); diff --git a/packages/stream_chat_flutter/test/src/theme/message_input_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/message_input_theme_test.dart index 431a113b0d..96cb7fd979 100644 --- a/packages/stream_chat_flutter/test/src/theme/message_input_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/message_input_theme_test.dart @@ -4,18 +4,16 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() { test('MessageInputThemeData copyWith, ==, hashCode basics', () { - expect(const StreamMessageInputThemeData(), - const StreamMessageInputThemeData().copyWith()); - expect(const StreamMessageInputThemeData().hashCode, - const StreamMessageInputThemeData().copyWith().hashCode); + expect(const StreamMessageInputThemeData(), const StreamMessageInputThemeData().copyWith()); + expect(const StreamMessageInputThemeData().hashCode, const StreamMessageInputThemeData().copyWith().hashCode); }); group('MessageInputThemeData lerps correctly', () { test('Lerp completely from light to dark', () { expect( - const StreamMessageInputThemeData().lerp( - _messageInputThemeControl, _messageInputThemeControlDark, 1), - _messageInputThemeControlDark); + const StreamMessageInputThemeData().lerp(_messageInputThemeControl, _messageInputThemeControlDark, 1), + _messageInputThemeControlDark, + ); }); test('Lerp halfway from light to dark', () { @@ -34,15 +32,14 @@ void main() { test('Lerp completely from dark to light', () { expect( - const StreamMessageInputThemeData().lerp( - _messageInputThemeControlDark, _messageInputThemeControl, 1), - _messageInputThemeControl); + const StreamMessageInputThemeData().lerp(_messageInputThemeControlDark, _messageInputThemeControl, 1), + _messageInputThemeControl, + ); }); }); test('Merging two MessageInputThemeData results in the latter', () { - expect(_messageInputThemeControl.merge(_messageInputThemeControlDark), - _messageInputThemeControlDark); + expect(_messageInputThemeControl.merge(_messageInputThemeControlDark), _messageInputThemeControlDark); }); } diff --git a/packages/stream_chat_flutter/test/src/theme/message_list_view_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/message_list_view_theme_test.dart index 8123029912..b1e8f4842b 100644 --- a/packages/stream_chat_flutter/test/src/theme/message_list_view_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/message_list_view_theme_test.dart @@ -9,26 +9,22 @@ class MockStreamChatClient extends Mock implements StreamChatClient {} void main() { test('MessageListViewThemeData copyWith, ==, hashCode basics', () { - expect(const StreamMessageListViewThemeData(), - const StreamMessageListViewThemeData().copyWith()); - expect(const StreamMessageListViewThemeData().hashCode, - const StreamMessageListViewThemeData().copyWith().hashCode); + expect(const StreamMessageListViewThemeData(), const StreamMessageListViewThemeData().copyWith()); + expect(const StreamMessageListViewThemeData().hashCode, const StreamMessageListViewThemeData().copyWith().hashCode); }); - test( - '''Light MessageListViewThemeData lerps completely to dark MessageListViewThemeData''', - () { + test('''Light MessageListViewThemeData lerps completely to dark MessageListViewThemeData''', () { expect( - const StreamMessageListViewThemeData().lerp( - _messageListViewThemeDataControl, - _messageListViewThemeDataControlDark, - 1), - _messageListViewThemeDataControlDark); + const StreamMessageListViewThemeData().lerp( + _messageListViewThemeDataControl, + _messageListViewThemeDataControlDark, + 1, + ), + _messageListViewThemeDataControlDark, + ); }); - test( - '''Light MessageListViewThemeData lerps halfway to dark MessageListViewThemeData''', - () { + test('''Light MessageListViewThemeData lerps halfway to dark MessageListViewThemeData''', () { expect( const StreamMessageListViewThemeData().lerp( _messageListViewThemeDataControl, @@ -42,27 +38,25 @@ void main() { ); }); - test( - '''Dark MessageListViewThemeData lerps completely to light MessageListViewThemeData''', - () { + test('''Dark MessageListViewThemeData lerps completely to light MessageListViewThemeData''', () { expect( - const StreamMessageListViewThemeData().lerp( - _messageListViewThemeDataControlDark, - _messageListViewThemeDataControl, - 1), - _messageListViewThemeDataControl); + const StreamMessageListViewThemeData().lerp( + _messageListViewThemeDataControlDark, + _messageListViewThemeDataControl, + 1, + ), + _messageListViewThemeDataControl, + ); }); test('Merging dark and light themes results in a dark theme', () { expect( - _messageListViewThemeDataControl - .merge(_messageListViewThemeDataControlDark), - _messageListViewThemeDataControlDark); + _messageListViewThemeDataControl.merge(_messageListViewThemeDataControlDark), + _messageListViewThemeDataControlDark, + ); }); - testWidgets( - 'Passing no MessageListViewThemeData returns default light theme values', - (WidgetTester tester) async { + testWidgets('Passing no MessageListViewThemeData returns default light theme values', (WidgetTester tester) async { late BuildContext _context; await tester.pumpWidget( MaterialApp( @@ -80,13 +74,10 @@ void main() { ); final messageListViewTheme = StreamMessageListViewTheme.of(_context); - expect(messageListViewTheme.backgroundColor, - _messageListViewThemeDataControl.backgroundColor); + expect(messageListViewTheme.backgroundColor, _messageListViewThemeDataControl.backgroundColor); }); - testWidgets( - 'Passing no MessageListViewThemeData returns default dark theme values', - (WidgetTester tester) async { + testWidgets('Passing no MessageListViewThemeData returns default dark theme values', (WidgetTester tester) async { late BuildContext _context; await tester.pumpWidget( MaterialApp( @@ -105,20 +96,18 @@ void main() { ); final messageListViewTheme = StreamMessageListViewTheme.of(_context); - expect(messageListViewTheme.backgroundColor, - _messageListViewThemeDataControlDark.backgroundColor); + expect(messageListViewTheme.backgroundColor, _messageListViewThemeDataControlDark.backgroundColor); }); - testWidgets( - 'Pass backgroundImage to MessageListViewThemeData return backgroundImage', - (WidgetTester tester) async { + testWidgets('Pass backgroundImage to MessageListViewThemeData return backgroundImage', (WidgetTester tester) async { late BuildContext _context; await tester.pumpWidget( MaterialApp( builder: (context, child) => StreamChat( client: MockStreamChatClient(), - streamChatThemeData: StreamChatThemeData.light() - .copyWith(messageListViewTheme: _messageListViewThemeDataImage), + streamChatThemeData: StreamChatThemeData.light().copyWith( + messageListViewTheme: _messageListViewThemeDataImage, + ), child: child, ), home: Builder( @@ -136,8 +125,7 @@ void main() { ); final messageListViewTheme = StreamMessageListViewTheme.of(_context); - expect(messageListViewTheme.backgroundImage, - _messageListViewThemeDataImage.backgroundImage); + expect(messageListViewTheme.backgroundImage, _messageListViewThemeDataImage.backgroundImage); }); } diff --git a/packages/stream_chat_flutter/test/src/theme/message_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/message_theme_test.dart index a7cb68df2f..d8759b2be3 100644 --- a/packages/stream_chat_flutter/test/src/theme/message_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/message_theme_test.dart @@ -6,48 +6,43 @@ String _dummyFormatter(BuildContext context, DateTime date) => 'formatted'; void main() { test('MessageThemeData copyWith, ==, hashCode basics', () { - expect(const StreamMessageThemeData(), - const StreamMessageThemeData().copyWith()); - expect(const StreamMessageThemeData().hashCode, - const StreamMessageThemeData().copyWith().hashCode); + expect(const StreamMessageThemeData(), const StreamMessageThemeData().copyWith()); + expect(const StreamMessageThemeData().hashCode, const StreamMessageThemeData().copyWith().hashCode); }); group('MessageThemeData lerps', () { - test('''Light MessageThemeData lerps completely to dark MessageThemeData''', - () { + test('''Light MessageThemeData lerps completely to dark MessageThemeData''', () { expect( - const StreamMessageThemeData() - .lerp(_messageThemeControl, _messageThemeControlDark, 1), - _messageThemeControlDark); + const StreamMessageThemeData().lerp(_messageThemeControl, _messageThemeControlDark, 1), + _messageThemeControlDark, + ); }); - test('''Dark MessageThemeData lerps completely to light MessageThemeData''', - () { + test('''Dark MessageThemeData lerps completely to light MessageThemeData''', () { expect( - const StreamMessageThemeData() - .lerp(_messageThemeControlDark, _messageThemeControl, 1), - _messageThemeControl); + const StreamMessageThemeData().lerp(_messageThemeControlDark, _messageThemeControl, 1), + _messageThemeControl, + ); }); }); test('Merging dark and light themes results in a dark theme', () { - expect(_messageThemeControl.merge(_messageThemeControlDark), - _messageThemeControlDark); + expect(_messageThemeControl.merge(_messageThemeControlDark), _messageThemeControlDark); }); } final _messageThemeControl = StreamMessageThemeData( messageAuthorStyle: const StreamTextTheme.light().footnote.copyWith( - color: const StreamColorTheme.light().textLowEmphasis, - ), + color: const StreamColorTheme.light().textLowEmphasis, + ), messageTextStyle: const StreamTextTheme.light().body, createdAtStyle: const StreamTextTheme.light().footnote.copyWith( - color: const StreamColorTheme.light().textLowEmphasis, - ), + color: const StreamColorTheme.light().textLowEmphasis, + ), createdAtFormatter: _dummyFormatter, repliesStyle: const StreamTextTheme.light().footnoteBold.copyWith( - color: const StreamColorTheme.light().accentPrimary, - ), + color: const StreamColorTheme.light().accentPrimary, + ), messageBackgroundColor: const StreamColorTheme.light().disabled, reactionsBackgroundColor: const StreamColorTheme.light().barsBg, reactionsBorderColor: const StreamColorTheme.light().borders, @@ -68,16 +63,16 @@ final _messageThemeControl = StreamMessageThemeData( final _messageThemeControlDark = StreamMessageThemeData( messageAuthorStyle: const StreamTextTheme.dark().footnote.copyWith( - color: const StreamColorTheme.dark().textLowEmphasis, - ), + color: const StreamColorTheme.dark().textLowEmphasis, + ), messageTextStyle: const StreamTextTheme.dark().body, createdAtStyle: const StreamTextTheme.dark().footnote.copyWith( - color: const StreamColorTheme.dark().textLowEmphasis, - ), + color: const StreamColorTheme.dark().textLowEmphasis, + ), createdAtFormatter: _dummyFormatter, repliesStyle: const StreamTextTheme.dark().footnoteBold.copyWith( - color: const StreamColorTheme.dark().accentPrimary, - ), + color: const StreamColorTheme.dark().accentPrimary, + ), messageBackgroundColor: const StreamColorTheme.dark().disabled, reactionsBackgroundColor: const StreamColorTheme.dark().barsBg, reactionsBorderColor: const StreamColorTheme.dark().borders, diff --git a/packages/stream_chat_flutter/test/src/theme/thread_list_tile_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/thread_list_tile_theme_test.dart index dd857151f6..d4c38dece2 100644 --- a/packages/stream_chat_flutter/test/src/theme/thread_list_tile_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/thread_list_tile_theme_test.dart @@ -5,8 +5,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; String _dummyFormatter(BuildContext context, DateTime date) => 'formatted'; void main() { - testWidgets('StreamThreadListTileTheme merges with ancestor theme', - (tester) async { + testWidgets('StreamThreadListTileTheme merges with ancestor theme', (tester) async { const backgroundColor = Colors.blue; const childBackgroundColor = Colors.red; @@ -116,12 +115,9 @@ void main() { expect(copied.padding, newPadding); // Unchanged properties should remain the same expect(copied.threadChannelNameStyle, original.threadChannelNameStyle); - expect( - copied.threadReplyToMessageStyle, original.threadReplyToMessageStyle); - expect(copied.threadLatestReplyTimestampStyle, - original.threadLatestReplyTimestampStyle); - expect(copied.threadLatestReplyTimestampFormatter, - original.threadLatestReplyTimestampFormatter); + expect(copied.threadReplyToMessageStyle, original.threadReplyToMessageStyle); + expect(copied.threadLatestReplyTimestampStyle, original.threadLatestReplyTimestampStyle); + expect(copied.threadLatestReplyTimestampFormatter, original.threadLatestReplyTimestampFormatter); }); test('StreamThreadListTileThemeData merge', () { @@ -147,12 +143,9 @@ void main() { expect(merged.padding, other.padding); // Null properties in 'other' should not override 'original' expect(merged.threadChannelNameStyle, original.threadChannelNameStyle); - expect( - merged.threadReplyToMessageStyle, original.threadReplyToMessageStyle); - expect(merged.threadLatestReplyTimestampStyle, - original.threadLatestReplyTimestampStyle); - expect(merged.threadLatestReplyTimestampFormatter, - original.threadLatestReplyTimestampFormatter); + expect(merged.threadReplyToMessageStyle, original.threadReplyToMessageStyle); + expect(merged.threadLatestReplyTimestampStyle, original.threadLatestReplyTimestampStyle); + expect(merged.threadLatestReplyTimestampFormatter, original.threadLatestReplyTimestampFormatter); // Merging with null should return original final mergedWithNull = original.merge(null); @@ -176,29 +169,26 @@ void main() { final lerpedAt0 = data1.lerp(data1, data2, 0); expect(lerpedAt0.backgroundColor, data1.backgroundColor); expect(lerpedAt0.padding, data1.padding); - expect(lerpedAt0.threadLatestReplyTimestampFormatter, - data1.threadLatestReplyTimestampFormatter); + expect(lerpedAt0.threadLatestReplyTimestampFormatter, data1.threadLatestReplyTimestampFormatter); // t = 1 should return data2 final lerpedAt1 = data1.lerp(data1, data2, 1); expect(lerpedAt1.backgroundColor, data2.backgroundColor); expect(lerpedAt1.padding, data2.padding); - expect(lerpedAt1.threadLatestReplyTimestampFormatter, - data2.threadLatestReplyTimestampFormatter); + expect(lerpedAt1.threadLatestReplyTimestampFormatter, data2.threadLatestReplyTimestampFormatter); // t = 0.5 should return something in between final lerpedAt05 = data1.lerp(data1, data2, 0.5); - expect(lerpedAt05.backgroundColor, - Color.lerp(Colors.black, Colors.white, 0.5)); + expect(lerpedAt05.backgroundColor, Color.lerp(Colors.black, Colors.white, 0.5)); expect( - lerpedAt05.padding, - EdgeInsetsGeometry.lerp( - const EdgeInsets.all(8), - const EdgeInsets.all(16), - 0.5, - )); + lerpedAt05.padding, + EdgeInsetsGeometry.lerp( + const EdgeInsets.all(8), + const EdgeInsets.all(16), + 0.5, + ), + ); // For t < 0.5, should use data1's formatter - expect(lerpedAt05.threadLatestReplyTimestampFormatter, - data1.threadLatestReplyTimestampFormatter); + expect(lerpedAt05.threadLatestReplyTimestampFormatter, data1.threadLatestReplyTimestampFormatter); }); } diff --git a/packages/stream_chat_flutter/test/src/utils/extension_test.dart b/packages/stream_chat_flutter/test/src/utils/extension_test.dart index badab2e9d9..77953c1bd3 100644 --- a/packages/stream_chat_flutter/test/src/utils/extension_test.dart +++ b/packages/stream_chat_flutter/test/src/utils/extension_test.dart @@ -325,8 +325,7 @@ void main() { ); }); - test('should handle both userId and userName with special characters', - () { + test('should handle both userId and userName with special characters', () { final user = User(id: 'user[123]', name: 'Test (X)'); final message = Message( diff --git a/packages/stream_chat_flutter/test/test_utils/data_generator.dart b/packages/stream_chat_flutter/test/test_utils/data_generator.dart index 5759136ba6..94f32285e3 100644 --- a/packages/stream_chat_flutter/test/test_utils/data_generator.dart +++ b/packages/stream_chat_flutter/test/test_utils/data_generator.dart @@ -8,9 +8,10 @@ List generateConversation( int unreadCount = 0, }) { assert( - users == null || noOfUsers == null, - 'Only one of users or noOfUsers ' - 'should be provided'); + users == null || noOfUsers == null, + 'Only one of users or noOfUsers ' + 'should be provided', + ); assert(count > 0, 'Count should be greater than 0'); assert(count > unreadCount, 'Count should be greater than unreadCount'); @@ -38,8 +39,7 @@ List generateConversation( id: faker.datatype.uuid(), text: faker.lorem.sentence(), user: user, - createdAt: - DateTime.now().subtract(Duration(minutes: i + count - unreadCount)), + createdAt: DateTime.now().subtract(Duration(minutes: i + count - unreadCount)), ), ); } diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index 0a523f81ac..40392a8c19 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -19,11 +19,7 @@ Future main() async { '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo''', ); - runApp( - StreamExample( - client: client, - ), - ); + runApp(StreamExample(client: client)); } /// Example application using Stream Chat core widgets. @@ -37,10 +33,7 @@ class StreamExample extends StatelessWidget { /// /// If you'd prefer using pre-made UI widgets for your app, please see our /// other package, `stream_chat_flutter`. - const StreamExample({ - Key? key, - required this.client, - }) : super(key: key); + const StreamExample({Key? key, required this.client}) : super(key: key); /// Instance of Stream Client. /// Stream's [StreamChatClient] can be used to connect to our servers and @@ -50,13 +43,10 @@ class StreamExample extends StatelessWidget { @override Widget build(BuildContext context) => MaterialApp( - title: 'Stream Chat Core Example', - home: HomeScreen(), - builder: (context, child) => StreamChatCore( - client: client, - child: child!, - ), - ); + title: 'Stream Chat Core Example', + home: HomeScreen(), + builder: (context, child) => StreamChatCore(client: client, child: child!), + ); } /// Basic layout displaying a list of [Channel]s the user is a part of. @@ -80,12 +70,7 @@ class _HomeScreenState extends State { client: StreamChatCore.of(context).client, filter: Filter.and([ Filter.equal('type', 'messaging'), - Filter.in_( - 'members', - [ - StreamChatCore.of(context).currentUser!.id, - ], - ), + Filter.in_('members', [StreamChatCore.of(context).currentUser!.id]), ]), ); @@ -103,91 +88,89 @@ class _HomeScreenState extends State { @override Widget build(BuildContext context) => Scaffold( - appBar: AppBar( - title: const Text('Channels'), - ), - body: PagedValueListenableBuilder( - valueListenable: channelListController, - builder: (context, value, child) { - return value.when( - (channels, nextPageKey, error) => LazyLoadScrollView( - onEndOfPage: () async { - if (nextPageKey != null) { - channelListController.loadMore(nextPageKey); + appBar: AppBar(title: const Text('Channels')), + body: PagedValueListenableBuilder( + valueListenable: channelListController, + builder: (context, value, child) { + return value.when( + (channels, nextPageKey, error) => LazyLoadScrollView( + onEndOfPage: () async { + if (nextPageKey != null) { + channelListController.loadMore(nextPageKey); + } + }, + child: ListView.builder( + /// We're using the channels length when there are no more + /// pages to load and there are no errors with pagination. + /// In case we need to show a loading indicator or and error + /// tile we're increasing the count by 1. + itemCount: (nextPageKey != null || error != null) + ? channels.length + 1 + : channels.length, + itemBuilder: (BuildContext context, int index) { + if (index == channels.length) { + if (error != null) { + return TextButton( + onPressed: () { + channelListController.retry(); + }, + child: Text(error.message), + ); } - }, - child: ListView.builder( - /// We're using the channels length when there are no more - /// pages to load and there are no errors with pagination. - /// In case we need to show a loading indicator or and error - /// tile we're increasing the count by 1. - itemCount: (nextPageKey != null || error != null) - ? channels.length + 1 - : channels.length, - itemBuilder: (BuildContext context, int index) { - if (index == channels.length) { - if (error != null) { - return TextButton( - onPressed: () { - channelListController.retry(); - }, - child: Text(error.message), - ); - } - return CircularProgressIndicator(); - } + return CircularProgressIndicator(); + } - final _item = channels[index]; - return ListTile( - title: Text(_item.name ?? ''), - subtitle: StreamBuilder( - stream: _item.state!.lastMessageStream, - initialData: _item.state!.lastMessage, - builder: (context, snapshot) { - if (snapshot.hasData) { - return Text(snapshot.data!.text!); - } + final _item = channels[index]; + return ListTile( + title: Text(_item.name ?? ''), + subtitle: StreamBuilder( + stream: _item.state!.lastMessageStream, + initialData: _item.state!.lastMessage, + builder: (context, snapshot) { + if (snapshot.hasData) { + return Text(snapshot.data!.text!); + } - return const SizedBox(); - }, + return const SizedBox(); + }, + ), + onTap: () { + /// Display a list of messages when the user taps on + /// an item. We can use [StreamChannel] to wrap our + /// [MessageScreen] screen with the selected channel. + /// + /// This allows us to use a built-in inherited widget + /// for accessing our `channel` later on. + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: _item, + child: const MessageScreen(), + ), ), - onTap: () { - /// Display a list of messages when the user taps on - /// an item. We can use [StreamChannel] to wrap our - /// [MessageScreen] screen with the selected channel. - /// - /// This allows us to use a built-in inherited widget - /// for accessing our `channel` later on. - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: _item, - child: const MessageScreen(), - ), - ), - ); - }, ); }, - ), - ), - loading: () => const Center( - child: SizedBox( - height: 100, - width: 100, - child: CircularProgressIndicator(), - ), - ), - error: (e) => Center( - child: Text( - 'Oh no, something went wrong. ' - 'Please check your config. $e', - ), - ), - ); - }, - ), - ); + ); + }, + ), + ), + loading: () => const Center( + child: SizedBox( + height: 100, + width: 100, + child: CircularProgressIndicator(), + ), + ), + error: (e) => Center( + child: Text( + 'Oh no, something went wrong. ' + 'Please check your config. $e', + ), + ), + ); + }, + ), + ); } /// A list of messages sent in the current channel. @@ -259,9 +242,8 @@ class _MessageScreenState extends State { }, child: MessageListCore( messageListController: messageListController, - emptyBuilder: (BuildContext context) => const Center( - child: Text('Nothing here yet'), - ), + emptyBuilder: (BuildContext context) => + const Center(child: Text('Nothing here yet')), loadingBuilder: (BuildContext context) => const Center( child: SizedBox( height: 100, @@ -269,44 +251,43 @@ class _MessageScreenState extends State { child: CircularProgressIndicator(), ), ), - messageListBuilder: ( - BuildContext context, - List messages, - ) => - ListView.builder( - controller: _scrollController, - itemCount: messages.length, - reverse: true, - itemBuilder: (BuildContext context, int index) { - final item = messages[index]; - final client = StreamChatCore.of(context).client; - if (item.user!.id == client.uid) { - return Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.all(8), - child: Text(item.text!), + messageListBuilder: + (BuildContext context, List messages) => + ListView.builder( + controller: _scrollController, + itemCount: messages.length, + reverse: true, + itemBuilder: (BuildContext context, int index) { + final item = messages[index]; + final client = StreamChatCore.of(context).client; + if (item.user!.id == client.uid) { + return Align( + alignment: Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.all(8), + child: Text(item.text!), + ), + ); + } else { + return Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8), + child: Text(item.text!), + ), + ); + } + }, ), - ); - } else { - return Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.all(8), - child: Text(item.text!), - ), - ); - } - }, - ), errorBuilder: (BuildContext context, error) { print(error.toString()); return const Center( child: SizedBox( height: 100, width: 100, - child: - Text('Oh no, an error occured. Please see logs.'), + child: Text( + 'Oh no, an error occured. Please see logs.', + ), ), ); }, @@ -344,10 +325,7 @@ class _MessageScreenState extends State { child: const Padding( padding: EdgeInsets.all(8), child: Center( - child: Icon( - Icons.send, - color: Colors.white, - ), + child: Icon(Icons.send, color: Colors.white), ), ), ), diff --git a/packages/stream_chat_flutter_core/example/pubspec.yaml b/packages/stream_chat_flutter_core/example/pubspec.yaml index c2821a10ae..95fdf094f7 100644 --- a/packages/stream_chat_flutter_core/example/pubspec.yaml +++ b/packages/stream_chat_flutter_core/example/pubspec.yaml @@ -16,8 +16,8 @@ version: 1.0.0+1 # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: cupertino_icons: ^1.0.3 diff --git a/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart b/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart index 6305224b61..22344c81ef 100644 --- a/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart +++ b/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart @@ -40,8 +40,7 @@ class BetterStreamBuilder extends StatefulWidget { _BetterStreamBuilderState createState() => _BetterStreamBuilderState(); } -class _BetterStreamBuilderState - extends State> { +class _BetterStreamBuilderState extends State> { T? _lastEvent; StreamSubscription? _subscription; Object? _lastError; @@ -102,8 +101,7 @@ class _BetterStreamBuilderState void _onEvent(T? event) { _lastError = null; - final isEqual = - widget.comparator?.call(_lastEvent, event) ?? event == _lastEvent; + final isEqual = widget.comparator?.call(_lastEvent, event) ?? event == _lastEvent; if (!isEqual) { _lastEvent = event; if (mounted) { diff --git a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart index d56d5f239a..1495a94c9b 100644 --- a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart @@ -53,11 +53,10 @@ class _LazyLoadScrollViewState extends State { double _scrollPosition = 0; @override - Widget build(BuildContext context) => - NotificationListener( - onNotification: _onNotification, - child: widget.child, - ); + Widget build(BuildContext context) => NotificationListener( + onNotification: _onNotification, + child: widget.child, + ); bool _onNotification(ScrollNotification notification) { if (notification is ScrollStartNotification) { @@ -78,8 +77,7 @@ class _LazyLoadScrollViewState extends State { final minScrollExtent = notification.metrics.minScrollExtent; final scrollOffset = widget.scrollOffset; - if (pixels > (minScrollExtent + scrollOffset) && - pixels < (maxScrollExtent - scrollOffset)) { + if (pixels > (minScrollExtent + scrollOffset) && pixels < (maxScrollExtent - scrollOffset)) { if (widget.onInBetweenOfPage != null) { widget.onInBetweenOfPage!(); return !widget.allowNotificationBubbling; diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 44b3c2619c..7a9b3d6c0e 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -9,12 +9,11 @@ import 'package:stream_chat_flutter_core/src/stream_channel.dart'; import 'package:stream_chat_flutter_core/src/typedef.dart'; /// Default filter for the message list -bool Function(Message) defaultMessageFilter(String currentUserId) => - (Message m) { - final isMyMessage = m.user?.id == currentUserId; - if (m.shadowed && !isMyMessage) return false; - return true; - }; +bool Function(Message) defaultMessageFilter(String currentUserId) => (Message m) { + final isMyMessage = m.user?.id == currentUserId; + if (m.shadowed && !isMyMessage) return false; + return true; +}; /// [MessageListCore] is a simplified class that allows fetching a list of /// messages while exposing UI builders. @@ -131,8 +130,8 @@ class MessageListCoreState extends State { Widget build(BuildContext context) { final messagesStream = _isThreadConversation ? _streamChannel!.channel.state?.threadsStream - .where((threads) => threads.containsKey(widget.parentMessage!.id)) - .map((threads) => threads[widget.parentMessage!.id]) + .where((threads) => threads.containsKey(widget.parentMessage!.id)) + .map((threads) => threads[widget.parentMessage!.id]) : _streamChannel!.channel.state?.messagesStream; final initialData = _isThreadConversation diff --git a/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart b/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart index ae730c06b8..f485b75c5a 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; /// A function that takes a [BuildContext] and returns a [TextStyle]. -typedef TextStyleBuilder = TextStyle? Function( - BuildContext context, - String text, -); +typedef TextStyleBuilder = + TextStyle? Function( + BuildContext context, + String text, + ); /// Controller for the [StreamTextField] widget. class MessageTextFieldController extends TextEditingController { diff --git a/packages/stream_chat_flutter_core/lib/src/paged_value_notifier.dart b/packages/stream_chat_flutter_core/lib/src/paged_value_notifier.dart index 83643a64d0..38bfa98756 100644 --- a/packages/stream_chat_flutter_core/lib/src/paged_value_notifier.dart +++ b/packages/stream_chat_flutter_core/lib/src/paged_value_notifier.dart @@ -8,8 +8,7 @@ part 'paged_value_notifier.freezed.dart'; const defaultInitialPagedLimitMultiplier = 3; /// Value listenable for paged data. -typedef PagedValueListenableBuilder - = ValueListenableBuilder>; +typedef PagedValueListenableBuilder = ValueListenableBuilder>; /// A [PagedValueNotifier] that uses a [PagedListenable] to load data. /// @@ -19,8 +18,7 @@ typedef PagedValueListenableBuilder /// /// [PagedValueNotifier] is a [ValueNotifier] that emits a [PagedValue] /// whenever the data is loaded or an error occurs. -abstract class PagedValueNotifier - extends ValueNotifier> { +abstract class PagedValueNotifier extends ValueNotifier> { /// Creates a [PagedValueNotifier] PagedValueNotifier(this._initialValue) : super(_initialValue); @@ -148,17 +146,18 @@ extension PagedValuePatternMatching on PagedValue { List items, Key? nextPageKey, StreamChatError? error, - ) success, { + ) + success, { required TResult Function() loading, required TResult Function(StreamChatError error) error, }) { final pagedValue = this; return switch (pagedValue) { Success() => success( - pagedValue.items, - pagedValue.nextPageKey, - pagedValue.error, - ), + pagedValue.items, + pagedValue.nextPageKey, + pagedValue.error, + ), Loading() => loading(), Error() => error(pagedValue.error), }; @@ -171,17 +170,18 @@ extension PagedValuePatternMatching on PagedValue { List items, Key? nextPageKey, StreamChatError? error, - )? success, { + )? + success, { TResult? Function()? loading, TResult? Function(StreamChatError error)? error, }) { final pagedValue = this; return switch (pagedValue) { Success() => success?.call( - pagedValue.items, - pagedValue.nextPageKey, - pagedValue.error, - ), + pagedValue.items, + pagedValue.nextPageKey, + pagedValue.error, + ), Loading() => loading?.call(), Error() => error?.call(pagedValue.error), }; @@ -194,7 +194,8 @@ extension PagedValuePatternMatching on PagedValue { List items, Key? nextPageKey, StreamChatError? error, - )? success, { + )? + success, { TResult Function()? loading, TResult Function(StreamChatError error)? error, required TResult orElse(), @@ -202,10 +203,10 @@ extension PagedValuePatternMatching on PagedValue { final pagedValue = this; final result = switch (pagedValue) { Success() => success?.call( - pagedValue.items, - pagedValue.nextPageKey, - pagedValue.error, - ), + pagedValue.items, + pagedValue.nextPageKey, + pagedValue.error, + ), Loading() => loading?.call(), Error() => error?.call(pagedValue.error), }; diff --git a/packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart b/packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart index a078f471c7..ea5fe13b0f 100644 --- a/packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart @@ -5,18 +5,20 @@ import 'package:stream_chat_flutter_core/src/paged_value_notifier.dart'; /// Signature for a function that creates a widget for a given index, e.g., in a /// [PagedValueListView] and [PagedValueGridView]. -typedef PagedValueScrollViewIndexedWidgetBuilder = Widget Function( - BuildContext context, - List values, - int index, -); +typedef PagedValueScrollViewIndexedWidgetBuilder = + Widget Function( + BuildContext context, + List values, + int index, + ); /// Signature for the item builder that creates the children of the /// [PagedValueListView] and [PagedValueGridView]. -typedef PagedValueScrollViewLoadMoreErrorBuilder = Widget Function( - BuildContext context, - StreamChatError error, -); +typedef PagedValueScrollViewLoadMoreErrorBuilder = + Widget Function( + BuildContext context, + StreamChatError error, + ); /// A [ListView] that loads more pages when the user scrolls to the end of the /// list. @@ -260,8 +262,7 @@ class PagedValueListView extends StatefulWidget { final Clip clipBehavior; @override - State> createState() => - _PagedValueListViewState(); + State> createState() => _PagedValueListViewState(); } class _PagedValueListViewState extends State> { @@ -288,65 +289,62 @@ class _PagedValueListViewState extends State> { @override Widget build(BuildContext context) => PagedValueListenableBuilder( - valueListenable: _controller, - builder: (context, value, _) => value.when( - (items, nextPageKey, error) { - if (items.isEmpty) { - return widget.emptyBuilder(context); - } - - return ListView.separated( - scrollDirection: widget.scrollDirection, - padding: widget.padding, - physics: widget.physics, - reverse: widget.reverse, - controller: widget.scrollController, - primary: widget.primary, - shrinkWrap: widget.shrinkWrap, - addAutomaticKeepAlives: widget.addAutomaticKeepAlives, - addRepaintBoundaries: widget.addRepaintBoundaries, - addSemanticIndexes: widget.addSemanticIndexes, - keyboardDismissBehavior: widget.keyboardDismissBehavior, - restorationId: widget.restorationId, - dragStartBehavior: widget.dragStartBehavior, - cacheExtent: widget.cacheExtent, - clipBehavior: widget.clipBehavior, - itemCount: value.itemCount, - separatorBuilder: (context, index) => - widget.separatorBuilder(context, items, index), - itemBuilder: (context, index) { - if (!_hasRequestedNextPage) { - final newPageRequestTriggerIndex = - items.length - widget.loadMoreTriggerIndex; - final isBuildingTriggerIndexItem = - index == newPageRequestTriggerIndex; - if (nextPageKey != null && isBuildingTriggerIndexItem) { - // Schedules the request for the end of this frame. - WidgetsBinding.instance.addPostFrameCallback((_) async { - if (error == null) { - await _controller.loadMore(nextPageKey); - } - _hasRequestedNextPage = false; - }); - _hasRequestedNextPage = true; + valueListenable: _controller, + builder: (context, value, _) => value.when( + (items, nextPageKey, error) { + if (items.isEmpty) { + return widget.emptyBuilder(context); + } + + return ListView.separated( + scrollDirection: widget.scrollDirection, + padding: widget.padding, + physics: widget.physics, + reverse: widget.reverse, + controller: widget.scrollController, + primary: widget.primary, + shrinkWrap: widget.shrinkWrap, + addAutomaticKeepAlives: widget.addAutomaticKeepAlives, + addRepaintBoundaries: widget.addRepaintBoundaries, + addSemanticIndexes: widget.addSemanticIndexes, + keyboardDismissBehavior: widget.keyboardDismissBehavior, + restorationId: widget.restorationId, + dragStartBehavior: widget.dragStartBehavior, + cacheExtent: widget.cacheExtent, + clipBehavior: widget.clipBehavior, + itemCount: value.itemCount, + separatorBuilder: (context, index) => widget.separatorBuilder(context, items, index), + itemBuilder: (context, index) { + if (!_hasRequestedNextPage) { + final newPageRequestTriggerIndex = items.length - widget.loadMoreTriggerIndex; + final isBuildingTriggerIndexItem = index == newPageRequestTriggerIndex; + if (nextPageKey != null && isBuildingTriggerIndexItem) { + // Schedules the request for the end of this frame. + WidgetsBinding.instance.addPostFrameCallback((_) async { + if (error == null) { + await _controller.loadMore(nextPageKey); } - } + _hasRequestedNextPage = false; + }); + _hasRequestedNextPage = true; + } + } - if (index == items.length) { - if (error != null) { - return widget.loadMoreErrorBuilder(context, error); - } - return widget.loadMoreIndicatorBuilder(context); - } + if (index == items.length) { + if (error != null) { + return widget.loadMoreErrorBuilder(context, error); + } + return widget.loadMoreIndicatorBuilder(context); + } - return widget.itemBuilder(context, items, index); - }, - ); + return widget.itemBuilder(context, items, index); }, - loading: () => widget.loadingBuilder(context), - error: (error) => widget.errorBuilder(context, error), - ), - ); + ); + }, + loading: () => widget.loadingBuilder(context), + error: (error) => widget.errorBuilder(context, error), + ), + ); } /// A [GridView] that loads more pages when the user scrolls to the end of the @@ -616,8 +614,7 @@ class PagedValueGridView extends StatefulWidget { final Clip clipBehavior; @override - State> createState() => - _PagedValueGridViewState(); + State> createState() => _PagedValueGridViewState(); } class _PagedValueGridViewState extends State> { @@ -644,63 +641,61 @@ class _PagedValueGridViewState extends State> { @override Widget build(BuildContext context) => PagedValueListenableBuilder( - valueListenable: _controller, - builder: (context, value, _) => value.when( - (items, nextPageKey, error) { - if (items.isEmpty) { - return widget.emptyBuilder(context); - } - - return GridView.builder( - scrollDirection: widget.scrollDirection, - reverse: widget.reverse, - controller: widget.scrollController, - primary: widget.primary, - physics: widget.physics, - shrinkWrap: widget.shrinkWrap, - padding: widget.padding, - addAutomaticKeepAlives: widget.addAutomaticKeepAlives, - addRepaintBoundaries: widget.addRepaintBoundaries, - addSemanticIndexes: widget.addSemanticIndexes, - cacheExtent: widget.cacheExtent, - semanticChildCount: widget.semanticChildCount, - dragStartBehavior: widget.dragStartBehavior, - keyboardDismissBehavior: widget.keyboardDismissBehavior, - restorationId: widget.restorationId, - clipBehavior: widget.clipBehavior, - itemCount: value.itemCount, - gridDelegate: widget.gridDelegate, - itemBuilder: (context, index) { - if (!_hasRequestedNextPage) { - final newPageRequestTriggerIndex = - items.length - widget.loadMoreTriggerIndex; - final isBuildingTriggerIndexItem = - index == newPageRequestTriggerIndex; - if (nextPageKey != null && isBuildingTriggerIndexItem) { - // Schedules the request for the end of this frame. - WidgetsBinding.instance.addPostFrameCallback((_) async { - if (error == null) { - await _controller.loadMore(nextPageKey); - } - _hasRequestedNextPage = false; - }); - _hasRequestedNextPage = true; + valueListenable: _controller, + builder: (context, value, _) => value.when( + (items, nextPageKey, error) { + if (items.isEmpty) { + return widget.emptyBuilder(context); + } + + return GridView.builder( + scrollDirection: widget.scrollDirection, + reverse: widget.reverse, + controller: widget.scrollController, + primary: widget.primary, + physics: widget.physics, + shrinkWrap: widget.shrinkWrap, + padding: widget.padding, + addAutomaticKeepAlives: widget.addAutomaticKeepAlives, + addRepaintBoundaries: widget.addRepaintBoundaries, + addSemanticIndexes: widget.addSemanticIndexes, + cacheExtent: widget.cacheExtent, + semanticChildCount: widget.semanticChildCount, + dragStartBehavior: widget.dragStartBehavior, + keyboardDismissBehavior: widget.keyboardDismissBehavior, + restorationId: widget.restorationId, + clipBehavior: widget.clipBehavior, + itemCount: value.itemCount, + gridDelegate: widget.gridDelegate, + itemBuilder: (context, index) { + if (!_hasRequestedNextPage) { + final newPageRequestTriggerIndex = items.length - widget.loadMoreTriggerIndex; + final isBuildingTriggerIndexItem = index == newPageRequestTriggerIndex; + if (nextPageKey != null && isBuildingTriggerIndexItem) { + // Schedules the request for the end of this frame. + WidgetsBinding.instance.addPostFrameCallback((_) async { + if (error == null) { + await _controller.loadMore(nextPageKey); } - } + _hasRequestedNextPage = false; + }); + _hasRequestedNextPage = true; + } + } - if (index == items.length) { - if (error != null) { - return widget.loadMoreErrorBuilder(context, error); - } - return widget.loadMoreIndicatorBuilder(context); - } + if (index == items.length) { + if (error != null) { + return widget.loadMoreErrorBuilder(context, error); + } + return widget.loadMoreIndicatorBuilder(context); + } - return widget.itemBuilder(context, items, index); - }, - ); + return widget.itemBuilder(context, items, index); }, - loading: () => widget.loadingBuilder(context), - error: (error) => widget.errorBuilder(context, error), - ), - ); + ); + }, + loading: () => widget.loadingBuilder(context), + error: (error) => widget.errorBuilder(context, error), + ), + ); } diff --git a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart index 77ef984bc9..ee6edd0c2b 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -17,11 +17,12 @@ enum QueryDirection { /// Signature used by [StreamChannel.errorBuilder] to create a replacement /// widget for an error that occurs while asynchronously building the channel. // TODO: Remove once ErrorBuilder supports passing stacktrace. -typedef ErrorWidgetBuilder = Widget Function( - BuildContext context, - Object error, - StackTrace? stackTrace, -); +typedef ErrorWidgetBuilder = + Widget Function( + BuildContext context, + Object error, + StackTrace? stackTrace, + ); Color _getDefaultBackgroundColor(BuildContext context) { final brightness = Theme.of(context).brightness; @@ -95,8 +96,7 @@ class StreamChannel extends StatefulWidget { color: backgroundColor, child: Center( child: switch (exception) { - DioException(type: DioExceptionType.badResponse) => - Text(exception.message ?? 'Bad response'), + DioException(type: DioExceptionType.badResponse) => Text(exception.message ?? 'Bad response'), DioException() => const Text('Check your connection and retry'), _ => Text(exception.toString()), }, @@ -180,8 +180,7 @@ class StreamChannelState extends State { String? get initialMessageId => widget.initialMessageId; /// Current channel state stream - Stream? get channelStateStream => - widget.channel.state?.channelStateStream; + Stream? get channelStateStream => widget.channel.state?.channelStateStream; final _queryTopMessagesController = BehaviorSubject.seeded(false); final _queryBottomMessagesController = BehaviorSubject.seeded(false); @@ -427,31 +426,30 @@ class StreamChannelState extends State { String? messageId, { int limit = 30, bool preferOffline = false, - }) => - _queryAtMessage( - messageId: messageId, - limit: limit, - preferOffline: preferOffline, - ); + }) => _queryAtMessage( + messageId: messageId, + limit: limit, + preferOffline: preferOffline, + ); /// Loads channel at specific message Future loadChannelAtTimestamp( DateTime timestamp, { int limit = 30, bool preferOffline = false, - }) => - _queryAtTimestamp( - timestamp: timestamp, - limit: limit, - preferOffline: preferOffline, - ); + }) => _queryAtTimestamp( + timestamp: timestamp, + limit: limit, + preferOffline: preferOffline, + ); // If we are jumping to a message we can determine if we loaded the oldest // page or the newest page, depending on where the aroundMessageId is located. ({ bool endOfPrependReached, bool endOfAppendReached, - }) _inferBoundariesFromAnchorId( + }) + _inferBoundariesFromAnchorId( String anchorId, List loadedMessages, ) { @@ -539,7 +537,8 @@ class StreamChannelState extends State { ({ bool endOfPrependReached, bool endOfAppendReached, - }) _inferBoundariesFromAnchorTimestamp( + }) + _inferBoundariesFromAnchorTimestamp( DateTime anchorTimestamp, List loadedMessages, ) { @@ -564,9 +563,11 @@ class StreamChannelState extends State { DateTime anchorTimestamp, List loadedMessages, ) { - final messageTimestamps = loadedMessages.map((it) { - return it.createdAt.millisecondsSinceEpoch; - }).toList(growable: false); + final messageTimestamps = loadedMessages + .map((it) { + return it.createdAt.millisecondsSinceEpoch; + }) + .toList(growable: false); return messageTimestamps.lowerBoundBy( anchorTimestamp.millisecondsSinceEpoch, @@ -820,8 +821,7 @@ class StreamChannelState extends State { @override void didUpdateWidget(StreamChannel oldWidget) { super.didUpdateWidget(oldWidget); - if (oldWidget.channel.cid != widget.channel.cid || - oldWidget.initialMessageId != widget.initialMessageId) { + if (oldWidget.channel.cid != widget.channel.cid || oldWidget.initialMessageId != widget.initialMessageId) { // Re-initialize channel if the channel CID or initial message ID changes. _channelInitFuture = [_maybeInitChannel(), channel.initialized].wait; } diff --git a/packages/stream_chat_flutter_core/lib/src/stream_channel_list_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_channel_list_controller.dart index db2ff2c4c6..bd671a0bcc 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel_list_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel_list_controller.dart @@ -55,8 +55,8 @@ class StreamChannelListController extends PagedValueNotifier { this.limit = defaultChannelPagedLimit, this.messageLimit, this.memberLimit, - }) : _eventHandler = eventHandler ?? StreamChannelListEventHandler(), - super(const PagedValue.loading()); + }) : _eventHandler = eventHandler ?? StreamChannelListEventHandler(), + super(const PagedValue.loading()); /// Creates a [StreamChannelListController] from the passed [value]. StreamChannelListController.fromValue( @@ -113,14 +113,14 @@ class StreamChannelListController extends PagedValueNotifier { super.value = switch (channelStateSort) { null => newValue, final channelSort => newValue.maybeMap( - orElse: () => newValue, - (success) => success.copyWith( - items: success.items.sortedByCompare( - (it) => it.state!.channelState, - channelSort.compare, - ), + orElse: () => newValue, + (success) => success.copyWith( + items: success.items.sortedByCompare( + (it) => it.state!.channelState, + channelSort.compare, ), ), + ), }; } @@ -269,8 +269,7 @@ class StreamChannelListController extends PagedValueNotifier { _eventHandler.onNotificationMessageNew(event, this); } else if (eventType == EventType.notificationRemovedFromChannel) { _eventHandler.onNotificationRemovedFromChannel(event, this); - } else if (eventType == 'user.presence.changed' || - eventType == EventType.userUpdated) { + } else if (eventType == 'user.presence.changed' || eventType == EventType.userUpdated) { _eventHandler.onUserPresenceChanged(event, this); } else if (eventType == EventType.memberUpdated) { _eventHandler.onMemberUpdated(event, this); diff --git a/packages/stream_chat_flutter_core/lib/src/stream_channel_list_event_handler.dart b/packages/stream_chat_flutter_core/lib/src/stream_channel_list_event_handler.dart index b59d16e67c..59a21f13c4 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel_list_event_handler.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel_list_event_handler.dart @@ -171,8 +171,7 @@ mixin class StreamChannelListEventHandler { StreamChannelListController controller, ) { final channels = [...controller.currentItems]; - final updatedChannels = - channels.where((it) => it.cid != event.channel?.cid); + final updatedChannels = channels.where((it) => it.cid != event.channel?.cid); final listChanged = channels.length != updatedChannels.length; if (!listChanged) return; diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index 252ffee341..44d77f156b 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -137,8 +137,7 @@ class StreamChatCore extends StatefulWidget { } /// State class associated with [StreamChatCore]. -class StreamChatCoreState extends State - with WidgetsBindingObserver { +class StreamChatCoreState extends State with WidgetsBindingObserver { /// The current user User? get currentUser => client.state.currentUser; @@ -168,15 +167,13 @@ class StreamChatCoreState extends State case PlatformType.macOS: final info = await DeviceInfoPlugin().macOsInfo; - osVersion = [info.majorVersion, info.minorVersion, info.patchVersion] - .join('.'); + osVersion = [info.majorVersion, info.minorVersion, info.patchVersion].join('.'); deviceModel = info.model; break; case PlatformType.windows: final info = await DeviceInfoPlugin().windowsInfo; - osVersion = [info.majorVersion, info.minorVersion, info.buildNumber] - .join('.'); + osVersion = [info.majorVersion, info.minorVersion, info.buildNumber].join('.'); deviceModel = null; break; diff --git a/packages/stream_chat_flutter_core/lib/src/stream_draft_list_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_draft_list_controller.dart index 8845ec93f1..cebe6c965a 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_draft_list_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_draft_list_controller.dart @@ -34,10 +34,10 @@ class StreamDraftListController extends PagedValueNotifier { this.filter, this.sort = defaultDraftListSort, this.limit = defaultDraftPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort, - _eventHandler = eventHandler ?? StreamDraftListEventHandler(), - super(const PagedValue.loading()); + }) : _activeFilter = filter, + _activeSort = sort, + _eventHandler = eventHandler ?? StreamDraftListEventHandler(), + super(const PagedValue.loading()); /// Creates a [StreamThreadListController] from the passed [value]. StreamDraftListController.fromValue( @@ -47,9 +47,9 @@ class StreamDraftListController extends PagedValueNotifier { this.filter, this.sort = defaultDraftListSort, this.limit = defaultDraftPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort, - _eventHandler = eventHandler ?? StreamDraftListEventHandler(); + }) : _activeFilter = filter, + _activeSort = sort, + _eventHandler = eventHandler ?? StreamDraftListEventHandler(); /// The Stream client used to perform the queries. final StreamChatClient client; @@ -94,11 +94,11 @@ class StreamDraftListController extends PagedValueNotifier { super.value = switch (_activeSort) { null => newValue, final draftSort => newValue.maybeMap( - orElse: () => newValue, - (success) => success.copyWith( - items: success.items.sorted(draftSort.compare), - ), + orElse: () => newValue, + (success) => success.copyWith( + items: success.items.sorted(draftSort.compare), ), + ), }; } diff --git a/packages/stream_chat_flutter_core/lib/src/stream_member_list_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_member_list_controller.dart index 16185685d9..0bc07a4c43 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_member_list_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_member_list_controller.dart @@ -36,9 +36,9 @@ class StreamMemberListController extends PagedValueNotifier { this.filter, this.sort = defaultMemberListSort, this.limit = defaultMemberPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort, - super(const PagedValue.loading()); + }) : _activeFilter = filter, + _activeSort = sort, + super(const PagedValue.loading()); /// Creates a [StreamMemberListController] from the passed [value]. StreamMemberListController.fromValue( @@ -47,8 +47,8 @@ class StreamMemberListController extends PagedValueNotifier { this.filter, this.sort = defaultMemberListSort, this.limit = defaultMemberPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort; + }) : _activeFilter = filter, + _activeSort = sort; /// The client to use for the channels list. final Channel channel; @@ -97,11 +97,11 @@ class StreamMemberListController extends PagedValueNotifier { super.value = switch (_activeSort) { null => newValue, final memberSort => newValue.maybeMap( - orElse: () => newValue, - (success) => success.copyWith( - items: success.items.sorted(memberSort.compare), - ), + orElse: () => newValue, + (success) => success.copyWith( + items: success.items.sorted(memberSort.compare), ), + ), }; } diff --git a/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart index 478fdacce7..35cbc4915a 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart @@ -23,42 +23,39 @@ class StreamMessageInputController extends ValueNotifier { factory StreamMessageInputController({ Message? message, Map? textPatternStyle, - }) => - StreamMessageInputController._( - initialMessage: message ?? Message(), - textPatternStyle: textPatternStyle, - ); + }) => StreamMessageInputController._( + initialMessage: message ?? Message(), + textPatternStyle: textPatternStyle, + ); /// Creates a controller for an editable text field from an initial [text]. factory StreamMessageInputController.fromText( String? text, { Map? textPatternStyle, - }) => - StreamMessageInputController._( - initialMessage: Message(text: text), - textPatternStyle: textPatternStyle, - ); + }) => StreamMessageInputController._( + initialMessage: Message(text: text), + textPatternStyle: textPatternStyle, + ); /// Creates a controller for an editable text field from initial /// [attachments]. factory StreamMessageInputController.fromAttachments( List attachments, { Map? textPatternStyle, - }) => - StreamMessageInputController._( - initialMessage: Message(attachments: attachments), - textPatternStyle: textPatternStyle, - ); + }) => StreamMessageInputController._( + initialMessage: Message(attachments: attachments), + textPatternStyle: textPatternStyle, + ); StreamMessageInputController._({ required Message initialMessage, Map? textPatternStyle, - }) : _initialMessage = initialMessage, - _textFieldController = MessageTextFieldController.fromValue( - _textEditingValueFromMessage(initialMessage), - textPatternStyle: textPatternStyle, - ), - super(initialMessage) { + }) : _initialMessage = initialMessage, + _textFieldController = MessageTextFieldController.fromValue( + _textEditingValueFromMessage(initialMessage), + textPatternStyle: textPatternStyle, + ), + super(initialMessage) { _textFieldController.addListener(_textFieldListener); } @@ -347,14 +344,12 @@ class StreamMessageInputController extends ValueNotifier { /// the property will restore [StreamMessageInputController.message] /// to the value it had when the restoration data it is getting restored from /// was collected. -class StreamRestorableMessageInputController - extends RestorableChangeNotifier { +class StreamRestorableMessageInputController extends RestorableChangeNotifier { /// Creates a [StreamRestorableMessageInputController]. /// /// This constructor creates a default [Message] when no `message` argument /// is supplied. - StreamRestorableMessageInputController({Message? message}) - : _initialValue = message ?? Message(); + StreamRestorableMessageInputController({Message? message}) : _initialValue = message ?? Message(); /// Creates a [StreamRestorableMessageInputController] from an initial /// [text] value. @@ -364,8 +359,7 @@ class StreamRestorableMessageInputController final Message _initialValue; @override - StreamMessageInputController createDefaultValue() => - StreamMessageInputController(message: _initialValue); + StreamMessageInputController createDefaultValue() => StreamMessageInputController(message: _initialValue); @override StreamMessageInputController fromPrimitives(Object? data) { diff --git a/packages/stream_chat_flutter_core/lib/src/stream_message_reminder_list_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_message_reminder_list_controller.dart index a2fa4f156e..242bd9c4b1 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_message_reminder_list_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_message_reminder_list_controller.dart @@ -29,8 +29,7 @@ const _kDefaultBackendPaginationLimit = 30; /// This controller is typically used in conjunction with UI components /// to display and interact with a list of message reminders. /// {@endtemplate} -class StreamMessageReminderListController - extends PagedValueNotifier { +class StreamMessageReminderListController extends PagedValueNotifier { /// {@macro streamMessageReminderListController} StreamMessageReminderListController({ required this.client, @@ -38,10 +37,10 @@ class StreamMessageReminderListController this.filter, this.sort = defaultMessageReminderListSort, this.limit = defaultMessageReminderPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort, - _eventHandler = eventHandler ?? StreamMessageReminderListEventHandler(), - super(const PagedValue.loading()); + }) : _activeFilter = filter, + _activeSort = sort, + _eventHandler = eventHandler ?? StreamMessageReminderListEventHandler(), + super(const PagedValue.loading()); /// Creates a [StreamMessageReminderListController] from the passed [value]. StreamMessageReminderListController.fromValue( @@ -51,9 +50,9 @@ class StreamMessageReminderListController this.filter, this.sort = defaultMessageReminderListSort, this.limit = defaultMessageReminderPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort, - _eventHandler = eventHandler ?? StreamMessageReminderListEventHandler(); + }) : _activeFilter = filter, + _activeSort = sort, + _eventHandler = eventHandler ?? StreamMessageReminderListEventHandler(); /// The Stream client used to perform the queries. final StreamChatClient client; @@ -98,11 +97,11 @@ class StreamMessageReminderListController super.value = switch (_activeSort) { null => newValue, final reminderSort => newValue.maybeMap( - orElse: () => newValue, - (success) => success.copyWith( - items: success.items.sorted(reminderSort.compare), - ), + orElse: () => newValue, + (success) => success.copyWith( + items: success.items.sorted(reminderSort.compare), ), + ), }; } diff --git a/packages/stream_chat_flutter_core/lib/src/stream_message_search_list_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_message_search_list_controller.dart index 5f8b369c70..3666f7d0f4 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_message_search_list_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_message_search_list_controller.dart @@ -15,8 +15,7 @@ const _kDefaultBackendPaginationLimit = 30; /// * Load initial data. /// * Load more data using [loadMore]. /// * Replace the previously loaded users. -class StreamMessageSearchListController - extends PagedValueNotifier { +class StreamMessageSearchListController extends PagedValueNotifier { /// Creates a Stream user list controller. /// /// * `client` is the Stream chat client to use for the channels list. @@ -36,19 +35,19 @@ class StreamMessageSearchListController this.searchQuery, this.sort, this.limit = defaultMessageSearchPagedLimit, - }) : assert( - messageFilter != null || searchQuery != null, - 'Either messageFilter or searchQuery must be provided', - ), - assert( - messageFilter == null || searchQuery == null, - 'Only one of messageFilter or searchQuery can be provided', - ), - _activeFilter = filter, - _activeMessageFilter = messageFilter, - _activeSearchQuery = searchQuery, - _activeSort = sort, - super(const PagedValue.loading()); + }) : assert( + messageFilter != null || searchQuery != null, + 'Either messageFilter or searchQuery must be provided', + ), + assert( + messageFilter == null || searchQuery == null, + 'Only one of messageFilter or searchQuery can be provided', + ), + _activeFilter = filter, + _activeMessageFilter = messageFilter, + _activeSearchQuery = searchQuery, + _activeSort = sort, + super(const PagedValue.loading()); /// Creates a [StreamUserListController] from the passed [value]. StreamMessageSearchListController.fromValue( @@ -59,18 +58,18 @@ class StreamMessageSearchListController this.searchQuery, this.sort, this.limit = defaultMessageSearchPagedLimit, - }) : assert( - messageFilter != null || searchQuery != null, - 'Either messageFilter or searchQuery must be provided', - ), - assert( - messageFilter == null || searchQuery == null, - 'Only one of messageFilter or searchQuery can be provided', - ), - _activeFilter = filter, - _activeMessageFilter = messageFilter, - _activeSearchQuery = searchQuery, - _activeSort = sort; + }) : assert( + messageFilter != null || searchQuery != null, + 'Either messageFilter or searchQuery must be provided', + ), + assert( + messageFilter == null || searchQuery == null, + 'Only one of messageFilter or searchQuery can be provided', + ), + _activeFilter = filter, + _activeMessageFilter = messageFilter, + _activeSearchQuery = searchQuery, + _activeSort = sort; /// The client to use for the channels list. final StreamChatClient client; diff --git a/packages/stream_chat_flutter_core/lib/src/stream_poll_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_poll_controller.dart index 749fb77e0e..d63f7c3569 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_poll_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_poll_controller.dart @@ -53,11 +53,14 @@ class StreamPollController extends ValueNotifier { factory StreamPollController({ Poll? poll, PollConfig? config, - }) => - StreamPollController._( - config ?? const PollConfig(), - poll ?? Poll(name: '', options: const [PollOption(text: '')]), - ); + }) => StreamPollController._( + config ?? const PollConfig(), + poll ?? + Poll( + name: '', + options: const [PollOption(text: '')], + ), + ); StreamPollController._(this.config, super.poll) : _initialValue = poll; @@ -84,7 +87,7 @@ class StreamPollController extends ValueNotifier { // Remove the id from the new added options. return option.copyWith(id: null); - }) + }), ], ); } @@ -122,8 +125,7 @@ class StreamPollController extends ValueNotifier { final name = value.name; final (:min, :max) = nameRange; - if (min != null && name.length < min || - max != null && name.length > max) { + if (min != null && name.length < min || max != null && name.length > max) { invalidErrors.add( PollValidationError.nameRange(name, range: nameRange), ); @@ -147,8 +149,7 @@ class StreamPollController extends ValueNotifier { final nonEmptyOptions = [...options.where((it) => it.text.isNotEmpty)]; final (:min, :max) = optionsRange; - if (min != null && nonEmptyOptions.length < min || - max != null && nonEmptyOptions.length > max) { + if (min != null && nonEmptyOptions.length < min || max != null && nonEmptyOptions.length > max) { invalidErrors.add( PollValidationError.optionsRange(options, range: optionsRange), ); @@ -161,8 +162,7 @@ class StreamPollController extends ValueNotifier { if (config.allowedVotesRange case final allowedVotesRange?) { final (:min, :max) = allowedVotesRange; - if (min != null && maxVotesAllowed < min || - max != null && maxVotesAllowed > max) { + if (min != null && maxVotesAllowed < min || max != null && maxVotesAllowed > max) { invalidErrors.add( PollValidationError.maxVotesAllowed( maxVotesAllowed, @@ -292,19 +292,15 @@ extension PollValidationErrorPatternMatching on PollValidationError { TResult when({ required TResult Function(List options) duplicateOptions, required TResult Function(String name, Range range) nameRange, - required TResult Function(List options, Range range) - optionsRange, - required TResult Function(int maxVotesAllowed, Range range) - maxVotesAllowed, + required TResult Function(List options, Range range) optionsRange, + required TResult Function(int maxVotesAllowed, Range range) maxVotesAllowed, }) { final error = this; return switch (error) { _PollValidationErrorDuplicateOptions() => duplicateOptions(error.options), _PollValidationErrorNameRange() => nameRange(error.name, error.range), - _PollValidationErrorOptionsRange() => - optionsRange(error.options, error.range), - _PollValidationErrorMaxVotesAllowed() => - maxVotesAllowed(error.maxVotesAllowed, error.range), + _PollValidationErrorOptionsRange() => optionsRange(error.options, error.range), + _PollValidationErrorMaxVotesAllowed() => maxVotesAllowed(error.maxVotesAllowed, error.range), }; } @@ -318,14 +314,10 @@ extension PollValidationErrorPatternMatching on PollValidationError { }) { final error = this; return switch (error) { - _PollValidationErrorDuplicateOptions() => - duplicateOptions?.call(error.options), - _PollValidationErrorNameRange() => - nameRange?.call(error.name, error.range), - _PollValidationErrorOptionsRange() => - optionsRange?.call(error.options, error.range), - _PollValidationErrorMaxVotesAllowed() => - maxVotesAllowed?.call(error.maxVotesAllowed, error.range), + _PollValidationErrorDuplicateOptions() => duplicateOptions?.call(error.options), + _PollValidationErrorNameRange() => nameRange?.call(error.name, error.range), + _PollValidationErrorOptionsRange() => optionsRange?.call(error.options, error.range), + _PollValidationErrorMaxVotesAllowed() => maxVotesAllowed?.call(error.maxVotesAllowed, error.range), }; } @@ -340,14 +332,10 @@ extension PollValidationErrorPatternMatching on PollValidationError { }) { final error = this; final result = switch (error) { - _PollValidationErrorDuplicateOptions() => - duplicateOptions?.call(error.options), - _PollValidationErrorNameRange() => - nameRange?.call(error.name, error.range), - _PollValidationErrorOptionsRange() => - optionsRange?.call(error.options, error.range), - _PollValidationErrorMaxVotesAllowed() => - maxVotesAllowed?.call(error.maxVotesAllowed, error.range), + _PollValidationErrorDuplicateOptions() => duplicateOptions?.call(error.options), + _PollValidationErrorNameRange() => nameRange?.call(error.name, error.range), + _PollValidationErrorOptionsRange() => optionsRange?.call(error.options, error.range), + _PollValidationErrorMaxVotesAllowed() => maxVotesAllowed?.call(error.maxVotesAllowed, error.range), }; return result ?? orElse(); @@ -356,13 +344,10 @@ extension PollValidationErrorPatternMatching on PollValidationError { /// @nodoc @optionalTypeArgs TResult map({ - required TResult Function(_PollValidationErrorDuplicateOptions value) - duplicateOptions, + required TResult Function(_PollValidationErrorDuplicateOptions value) duplicateOptions, required TResult Function(_PollValidationErrorNameRange value) nameRange, - required TResult Function(_PollValidationErrorOptionsRange value) - optionsRange, - required TResult Function(_PollValidationErrorMaxVotesAllowed value) - maxVotesAllowed, + required TResult Function(_PollValidationErrorOptionsRange value) optionsRange, + required TResult Function(_PollValidationErrorMaxVotesAllowed value) maxVotesAllowed, }) { final error = this; return switch (error) { @@ -376,12 +361,10 @@ extension PollValidationErrorPatternMatching on PollValidationError { /// @nodoc @optionalTypeArgs TResult? mapOrNull({ - TResult? Function(_PollValidationErrorDuplicateOptions value)? - duplicateOptions, + TResult? Function(_PollValidationErrorDuplicateOptions value)? duplicateOptions, TResult? Function(_PollValidationErrorNameRange value)? nameRange, TResult? Function(_PollValidationErrorOptionsRange value)? optionsRange, - TResult? Function(_PollValidationErrorMaxVotesAllowed value)? - maxVotesAllowed, + TResult? Function(_PollValidationErrorMaxVotesAllowed value)? maxVotesAllowed, }) { final error = this; return switch (error) { @@ -395,12 +378,10 @@ extension PollValidationErrorPatternMatching on PollValidationError { /// @nodoc @optionalTypeArgs TResult maybeMap({ - TResult Function(_PollValidationErrorDuplicateOptions value)? - duplicateOptions, + TResult Function(_PollValidationErrorDuplicateOptions value)? duplicateOptions, TResult Function(_PollValidationErrorNameRange value)? nameRange, TResult Function(_PollValidationErrorOptionsRange value)? optionsRange, - TResult Function(_PollValidationErrorMaxVotesAllowed value)? - maxVotesAllowed, + TResult Function(_PollValidationErrorMaxVotesAllowed value)? maxVotesAllowed, required TResult orElse(), }) { final error = this; diff --git a/packages/stream_chat_flutter_core/lib/src/stream_poll_vote_list_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_poll_vote_list_controller.dart index 3b29d687dc..a55d48fbd5 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_poll_vote_list_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_poll_vote_list_controller.dart @@ -21,8 +21,7 @@ const _kDefaultBackendPaginationLimit = 30; /// * Load initial data. /// * Load more data using [loadMore]. /// * Replace the previously loaded poll votes. -class StreamPollVoteListController - extends PagedValueNotifier { +class StreamPollVoteListController extends PagedValueNotifier { /// Creates a Stream poll vote list controller. /// * `channel` is the Stream chat channel to use for the poll votes list. /// * `pollId` is the poll id to use for the poll votes list. @@ -36,10 +35,10 @@ class StreamPollVoteListController this.filter, this.sort = defaultPollVoteListSort, this.limit = defaultPollVotePagedLimit, - }) : _eventHandler = eventHandler ?? StreamPollVoteEventHandler(), - _activeFilter = filter, - _activeSort = sort, - super(const PagedValue.loading()); + }) : _eventHandler = eventHandler ?? StreamPollVoteEventHandler(), + _activeFilter = filter, + _activeSort = sort, + super(const PagedValue.loading()); /// Creates a [StreamPollVoteListController] from the passed [value]. StreamPollVoteListController.fromValue( @@ -50,9 +49,9 @@ class StreamPollVoteListController this.filter, this.sort = defaultPollVoteListSort, this.limit = defaultPollVotePagedLimit, - }) : _eventHandler = eventHandler ?? StreamPollVoteEventHandler(), - _activeFilter = filter, - _activeSort = sort; + }) : _eventHandler = eventHandler ?? StreamPollVoteEventHandler(), + _activeFilter = filter, + _activeSort = sort; /// The channel to use for the poll votes list. final Channel channel; @@ -106,11 +105,11 @@ class StreamPollVoteListController super.value = switch (_activeSort) { null => newValue, final pollVoteSort => newValue.maybeMap( - orElse: () => newValue, - (success) => success.copyWith( - items: success.items.sorted(pollVoteSort.compare), - ), + orElse: () => newValue, + (success) => success.copyWith( + items: success.items.sorted(pollVoteSort.compare), ), + ), }; } @@ -216,13 +215,11 @@ class StreamPollVoteListController if (eventListener?.call(event) ?? false) return; final eventType = event.type; - if (eventType == EventType.pollVoteCasted || - eventType == EventType.pollAnswerCasted) { + if (eventType == EventType.pollVoteCasted || eventType == EventType.pollAnswerCasted) { _eventHandler.onPollVoteCasted(event, this); } else if (eventType == EventType.pollVoteChanged) { _eventHandler.onPollVoteChanged(event, this); - } else if (eventType == EventType.pollVoteRemoved || - eventType == EventType.pollAnswerRemoved) { + } else if (eventType == EventType.pollVoteRemoved || eventType == EventType.pollAnswerRemoved) { _eventHandler.onPollVoteRemoved(event, this); } }); diff --git a/packages/stream_chat_flutter_core/lib/src/stream_thread_list_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_thread_list_controller.dart index fe30762ed7..691c4e7f2b 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_thread_list_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_thread_list_controller.dart @@ -29,11 +29,11 @@ class StreamThreadListController extends PagedValueNotifier { this.sort, this.options = const ThreadOptions(), this.limit = defaultThreadsPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort, - _activeOptions = options, - _eventHandler = eventHandler ?? StreamThreadListEventHandler(), - super(const PagedValue.loading()); + }) : _activeFilter = filter, + _activeSort = sort, + _activeOptions = options, + _eventHandler = eventHandler ?? StreamThreadListEventHandler(), + super(const PagedValue.loading()); /// Creates a [StreamThreadListController] from the passed [value]. StreamThreadListController.fromValue( @@ -44,10 +44,10 @@ class StreamThreadListController extends PagedValueNotifier { this.sort, this.options = const ThreadOptions(), this.limit = defaultThreadsPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort, - _activeOptions = options, - _eventHandler = eventHandler ?? StreamThreadListEventHandler(); + }) : _activeFilter = filter, + _activeSort = sort, + _activeOptions = options, + _eventHandler = eventHandler ?? StreamThreadListEventHandler(); /// The Stream client used to perform the queries. final StreamChatClient client; @@ -129,11 +129,11 @@ class StreamThreadListController extends PagedValueNotifier { super.value = switch (_activeSort) { null => newValue, final threadSort => newValue.maybeMap( - orElse: () => newValue, - (success) => success.copyWith( - items: success.items.sorted(threadSort.compare), - ), + orElse: () => newValue, + (success) => success.copyWith( + items: success.items.sorted(threadSort.compare), ), + ), }; } @@ -338,11 +338,9 @@ class StreamThreadListController extends PagedValueNotifier { final handlerFunc = switch (event.type) { EventType.threadUpdated => _eventHandler.onThreadUpdated, EventType.connectionRecovered => _eventHandler.onConnectionRecovered, - EventType.notificationThreadMessageNew => - _eventHandler.onNotificationThreadMessageNew, + EventType.notificationThreadMessageNew => _eventHandler.onNotificationThreadMessageNew, EventType.messageRead => _eventHandler.onMessageRead, - EventType.notificationMarkUnread => - _eventHandler.onNotificationMarkUnread, + EventType.notificationMarkUnread => _eventHandler.onNotificationMarkUnread, EventType.channelDeleted => _eventHandler.onChannelDeleted, EventType.channelTruncated => _eventHandler.onChannelTruncated, EventType.messageNew => _eventHandler.onMessageNew, diff --git a/packages/stream_chat_flutter_core/lib/src/stream_user_list_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_user_list_controller.dart index 9dc25635eb..fa22bc49a1 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_user_list_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_user_list_controller.dart @@ -40,9 +40,9 @@ class StreamUserListController extends PagedValueNotifier { this.sort = defaultUserListSort, this.presence = true, this.limit = defaultUserPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort, - super(const PagedValue.loading()); + }) : _activeFilter = filter, + _activeSort = sort, + super(const PagedValue.loading()); /// Creates a [StreamUserListController] from the passed [value]. StreamUserListController.fromValue( @@ -52,8 +52,8 @@ class StreamUserListController extends PagedValueNotifier { this.sort = defaultUserListSort, this.presence = true, this.limit = defaultUserPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort; + }) : _activeFilter = filter, + _activeSort = sort; /// The client to use for the channels list. final StreamChatClient client; @@ -105,11 +105,11 @@ class StreamUserListController extends PagedValueNotifier { super.value = switch (_activeSort) { null => newValue, final userSort => newValue.maybeMap( - orElse: () => newValue, - (success) => success.copyWith( - items: success.items.sorted(userSort.compare), - ), + orElse: () => newValue, + (success) => success.copyWith( + items: success.items.sorted(userSort.compare), ), + ), }; } diff --git a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart index 444362bbb7..92e1e92751 100644 --- a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart +++ b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart @@ -8,11 +8,7 @@ export 'src/lazy_load_scroll_view.dart'; export 'src/message_list_core.dart' hide MessageListCoreState; export 'src/message_text_field_controller.dart'; export 'src/paged_value_notifier.dart' - show - PagedValueListenableBuilder, - PagedValue, - PagedValueNotifier, - PagedValuePatternMatching; + show PagedValueListenableBuilder, PagedValue, PagedValueNotifier, PagedValuePatternMatching; export 'src/paged_value_scroll_view.dart'; export 'src/stream_channel.dart'; export 'src/stream_channel_list_controller.dart'; diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index e0029c4ac7..dc3124e751 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -18,8 +18,8 @@ issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: collection: ^1.17.2 diff --git a/packages/stream_chat_flutter_core/test/message_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_list_core_test.dart index d49a31e8f5..2398ae33b7 100644 --- a/packages/stream_chat_flutter_core/test/message_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_list_core_test.dart @@ -94,8 +94,7 @@ void main() { final mockChannel = MockChannel(); when(() => mockChannel.state.unreadCount).thenReturn(0); when(() => mockChannel.state.isUpToDate).thenReturn(true); - when(() => mockChannel.state.messagesStream) - .thenAnswer((_) => Stream.value([])); + when(() => mockChannel.state.messagesStream).thenAnswer((_) => Stream.value([])); when(() => mockChannel.state.messages).thenReturn([]); await tester.pumpWidget( @@ -131,8 +130,7 @@ void main() { when(() => mockChannel.state.isUpToDate).thenReturn(true); when(() => mockChannel.state.unreadCount).thenReturn(0); - when(() => mockChannel.state.messagesStream) - .thenAnswer((_) => Stream.value([])); + when(() => mockChannel.state.messagesStream).thenAnswer((_) => Stream.value([])); when(() => mockChannel.state.messages).thenReturn([]); await tester.pumpWidget( @@ -173,8 +171,7 @@ void main() { when(() => mockChannel.state.unreadCount).thenReturn(0); final messages = _generateMessages(); when(() => mockChannel.state.messages).thenReturn(messages); - when(() => mockChannel.state.messagesStream) - .thenAnswer((_) => Stream.value(messages)); + when(() => mockChannel.state.messagesStream).thenAnswer((_) => Stream.value(messages)); when(() => mockChannel.state.messages).thenReturn(messages); await tester.pumpWidget( @@ -193,13 +190,15 @@ void main() { await coreState.paginateData(); - verify(() => mockChannel.query( - messagesPagination: any( - named: 'messagesPagination', - that: wrapMatcher((it) => it.limit == paginationLimit), - ), - preferOffline: any(named: 'preferOffline'), - )).called(1); + verify( + () => mockChannel.query( + messagesPagination: any( + named: 'messagesPagination', + that: wrapMatcher((it) => it.limit == paginationLimit), + ), + preferOffline: any(named: 'preferOffline'), + ), + ).called(1); }, ); @@ -223,8 +222,7 @@ void main() { when(() => mockChannel.state.isUpToDate).thenReturn(true); const error = 'Error! Error! Error!'; - when(() => mockChannel.state.messagesStream) - .thenAnswer((_) => Stream.error(error)); + when(() => mockChannel.state.messagesStream).thenAnswer((_) => Stream.error(error)); when(() => mockChannel.state.messages).thenReturn([]); when(() => mockChannel.state.unreadCount).thenReturn(0); @@ -254,8 +252,7 @@ void main() { key: messageListCoreKey, messageListBuilder: (_, __) => const Offstage(), loadingBuilder: (BuildContext context) => const Offstage(), - emptyBuilder: (BuildContext context) => - const Offstage(key: emptyWidgetKey), + emptyBuilder: (BuildContext context) => const Offstage(key: emptyWidgetKey), errorBuilder: (BuildContext context, Object error) => const Offstage(), ); @@ -264,8 +261,7 @@ void main() { when(() => mockChannel.state.isUpToDate).thenReturn(true); const messages = []; - when(() => mockChannel.state.messagesStream) - .thenAnswer((_) => Stream.value(messages)); + when(() => mockChannel.state.messagesStream).thenAnswer((_) => Stream.value(messages)); when(() => mockChannel.state.messages).thenReturn(messages); when(() => mockChannel.state.unreadCount).thenReturn(0); @@ -302,19 +298,20 @@ void main() { final mockChannel = MockChannel(); when(() => mockChannel.state.isUpToDate).thenReturn(false); - when(() => mockChannel.query( - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - membersPagination: any(named: 'membersPagination'), - messagesPagination: any(named: 'messagesPagination'), - preferOffline: any(named: 'preferOffline'), - watchersPagination: any(named: 'watchersPagination'), - )).thenAnswer((_) async => const ChannelState()); + when( + () => mockChannel.query( + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + membersPagination: any(named: 'membersPagination'), + messagesPagination: any(named: 'messagesPagination'), + preferOffline: any(named: 'preferOffline'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).thenAnswer((_) async => const ChannelState()); const messages = []; - when(() => mockChannel.state.messagesStream) - .thenAnswer((_) => Stream.value(messages)); + when(() => mockChannel.state.messagesStream).thenAnswer((_) => Stream.value(messages)); when(() => mockChannel.state.messages).thenReturn(messages); when(() => mockChannel.state.unreadCount).thenReturn(0); @@ -358,8 +355,7 @@ void main() { when(() => mockChannel.state.isUpToDate).thenReturn(true); final messages = _generateMessages(); - when(() => mockChannel.state.messagesStream) - .thenAnswer((_) => Stream.value(messages)); + when(() => mockChannel.state.messagesStream).thenAnswer((_) => Stream.value(messages)); when(() => mockChannel.state.messages).thenReturn(messages); when(() => mockChannel.state.unreadCount).thenReturn(0); @@ -408,8 +404,7 @@ void main() { final threads = {parentMessage.id: messages}; when(() => mockChannel.state.threads).thenReturn(threads); - when(() => mockChannel.state.threadsStream) - .thenAnswer((_) => Stream.value(threads)); + when(() => mockChannel.state.threadsStream).thenAnswer((_) => Stream.value(threads)); when(() => mockChannel.state.unreadCount).thenReturn(0); when( diff --git a/packages/stream_chat_flutter_core/test/mocks.dart b/packages/stream_chat_flutter_core/test/mocks.dart index 7235202250..cc705f9838 100644 --- a/packages/stream_chat_flutter_core/test/mocks.dart +++ b/packages/stream_chat_flutter_core/test/mocks.dart @@ -22,11 +22,11 @@ class MockClientState extends Mock implements ClientState { @override OwnUser get currentUser => _currentUser ??= OwnUser( - id: 'testUserId', - role: 'admin', - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - ); + id: 'testUserId', + role: 'admin', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + ); } class NonInitializedMockChannel extends Mock implements Channel { diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart index 8f56ce1c1d..80c95ad425 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -145,8 +145,7 @@ void main() { when( mockClient.openConnection, ).thenAnswer((_) async => OwnUser(id: 'test-user')); - when(() => mockClient.wsConnectionStatus) - .thenReturn(ConnectionStatus.connected); + when(() => mockClient.wsConnectionStatus).thenReturn(ConnectionStatus.connected); }); tearDown(() { @@ -202,8 +201,7 @@ void main() { ).thenReturn(ConnectionStatus.disconnected); // Act - bring app to foreground - tester.binding - .handleAppLifecycleStateChanged(AppLifecycleState.resumed); + tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed); await tester.pumpAndSettle(); // Assert @@ -251,8 +249,7 @@ void main() { ); // Act - tester.binding - .handleAppLifecycleStateChanged(AppLifecycleState.paused); + tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.paused); await tester.pumpAndSettle(); // Wait for timer to expire diff --git a/packages/stream_chat_flutter_core/test/stream_draft_list_controller_test.dart b/packages/stream_chat_flutter_core/test/stream_draft_list_controller_test.dart index ff07096332..87aa10cfa8 100644 --- a/packages/stream_chat_flutter_core/test/stream_draft_list_controller_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_draft_list_controller_test.dart @@ -36,9 +36,7 @@ List generateDrafts({ final baseId = startId ?? 123; return List.generate(count, (index) { - final text = texts != null && index < texts.length - ? texts[index] - : 'Draft ${index + 1}'; + final text = texts != null && index < texts.length ? texts[index] : 'Draft ${index + 1}'; return generateDraft( channelCid: 'messaging:${baseId + index}', @@ -86,22 +84,26 @@ void main() { ..drafts = drafts ..next = ''; - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => response); + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); final controller = StreamDraftListController(client: client); await controller.doInitialLoad(); await pumpEventQueue(); - verify(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).called(1); + verify( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).called(1); expect(controller.value, isA>()); expect(controller.value.asSuccess.items, equals(drafts)); @@ -109,11 +111,13 @@ void main() { test('handles API exceptions by transitioning to error state', () async { final exception = Exception('API unavailable'); - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenThrow(exception); + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenThrow(exception); final controller = StreamDraftListController(client: client); @@ -142,11 +146,13 @@ void main() { ..drafts = additionalDrafts ..next = ''; - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => response); + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); final controller = StreamDraftListController.fromValue( PagedValue( @@ -171,9 +177,9 @@ void main() { for (final draft in mergedDrafts) { expect( - controller.value.asSuccess.items.any((d) => - d.channelCid == draft.channelCid && - d.message.text == draft.message.text), + controller.value.asSuccess.items.any( + (d) => d.channelCid == draft.channelCid && d.message.text == draft.message.text, + ), isTrue, ); } @@ -181,17 +187,18 @@ void main() { expect(controller.value.asSuccess.nextPageKey, isNull); }); - test('loadMore preserves existing items when API throws exception', - () async { + test('loadMore preserves existing items when API throws exception', () async { const nextKey = 'next_page_token'; final existingDrafts = generateDrafts(); final exception = Exception('Network error'); - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenThrow(exception); + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenThrow(exception); final controller = StreamDraftListController.fromValue( PagedValue( @@ -331,9 +338,7 @@ void main() { equals(allDrafts.length - 1), ); - final remainingDraftTexts = [ - ...controller.value.asSuccess.items.map((d) => d.message.text) - ]; + final remainingDraftTexts = [...controller.value.asSuccess.items.map((d) => d.message.text)]; expect(remainingDraftTexts, contains('Thread Draft 2')); expect(remainingDraftTexts, isNot(contains('Thread Draft 1'))); @@ -393,11 +398,13 @@ void main() { ..drafts = drafts ..next = ''; - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => queryResponse); + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => queryResponse); final controller = StreamDraftListController.fromValue( PagedValue(items: drafts), @@ -432,11 +439,13 @@ void main() { ..drafts = drafts ..next = ''; - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => queryResponse); + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => queryResponse); final controller = StreamDraftListController.fromValue( PagedValue(items: drafts), @@ -473,11 +482,13 @@ void main() { final drafts = generateDrafts(); var queryCallCount = 0; - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async { + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async { queryCallCount++; return QueryDraftsResponse() ..drafts = drafts @@ -512,11 +523,13 @@ void main() { ..drafts = drafts ..next = ''; - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => queryResponse); + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => queryResponse); final controller = StreamDraftListController.fromValue( PagedValue(items: drafts), @@ -569,11 +582,13 @@ void main() { ..drafts = drafts ..next = ''; - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => response); + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); final controller = StreamDraftListController.fromValue( PagedValue(items: drafts), @@ -599,11 +614,13 @@ void main() { final apiCalls = >[]; - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((invocation) async { + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((invocation) async { apiCalls.add({ 'filter': invocation.namedArguments[const Symbol('filter')], 'sort': invocation.namedArguments[const Symbol('sort')], @@ -647,18 +664,22 @@ void main() { final apiCalls = >[]; - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((invocation) { + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((invocation) { apiCalls.add({ 'filter': invocation.namedArguments[const Symbol('filter')], 'sort': invocation.namedArguments[const Symbol('sort')], }); - return Future.value(QueryDraftsResponse() - ..drafts = drafts - ..next = ''); + return Future.value( + QueryDraftsResponse() + ..drafts = drafts + ..next = '', + ); }); final controller = StreamDraftListController( @@ -688,8 +709,7 @@ void main() { group('Disposal', () { test('dispose cancels subscriptions without errors', () { - final controller = StreamDraftListController(client: client) - ..doInitialLoad(); + final controller = StreamDraftListController(client: client)..doInitialLoad(); expect(controller.dispose, returnsNormally); }); diff --git a/packages/stream_chat_flutter_core/test/stream_message_input_controller_test.dart b/packages/stream_chat_flutter_core/test/stream_message_input_controller_test.dart index ba1eadfa9f..f2159360fa 100644 --- a/packages/stream_chat_flutter_core/test/stream_message_input_controller_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_message_input_controller_test.dart @@ -241,12 +241,10 @@ void main() { }); test('setOGAttachment replaces existing OG attachment', () { - final oldOGAttachment = - Attachment(ogScrapeUrl: 'https://old.example.com'); + final oldOGAttachment = Attachment(ogScrapeUrl: 'https://old.example.com'); controller.addAttachment(oldOGAttachment); - final newOGAttachment = - Attachment(ogScrapeUrl: 'https://new.example.com'); + final newOGAttachment = Attachment(ogScrapeUrl: 'https://new.example.com'); controller.setOGAttachment(newOGAttachment); expect(controller.attachments.length, 1); @@ -501,8 +499,7 @@ class _RestorableWidget extends StatefulWidget { State<_RestorableWidget> createState() => _RestorableWidgetState(); } -class _RestorableWidgetState extends State<_RestorableWidget> - with RestorationMixin { +class _RestorableWidgetState extends State<_RestorableWidget> with RestorationMixin { final controller = StreamRestorableMessageInputController(); @override diff --git a/packages/stream_chat_flutter_core/test/stream_message_reminder_list_controller_test.dart b/packages/stream_chat_flutter_core/test/stream_message_reminder_list_controller_test.dart index 86d6767df1..cbbd5924b6 100644 --- a/packages/stream_chat_flutter_core/test/stream_message_reminder_list_controller_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_message_reminder_list_controller_test.dart @@ -48,15 +48,9 @@ List generateMessageReminders({ final channelCid = channelCids != null && index < channelCids.length ? channelCids[index] : 'messaging:${baseId + index}'; - final messageId = messageIds != null && index < messageIds.length - ? messageIds[index] - : 'message_${baseId + index}'; - final userId = userIds != null && index < userIds.length - ? userIds[index] - : 'user_${baseId + index}'; - final text = texts != null && index < texts.length - ? texts[index] - : 'Reminder ${index + 1}'; + final messageId = messageIds != null && index < messageIds.length ? messageIds[index] : 'message_${baseId + index}'; + final userId = userIds != null && index < userIds.length ? userIds[index] : 'user_${baseId + index}'; + final text = texts != null && index < texts.length ? texts[index] : 'Reminder ${index + 1}'; return generateMessageReminder( channelCid: channelCid, @@ -110,22 +104,26 @@ void main() { ..reminders = reminders ..next = null; - when(() => client.queryReminders( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => response); + when( + () => client.queryReminders( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); final controller = StreamMessageReminderListController(client: client); await controller.doInitialLoad(); await pumpEventQueue(); - verify(() => client.queryReminders( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).called(1); + verify( + () => client.queryReminders( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).called(1); expect(controller.value, isA>()); expect(controller.value.asSuccess.items, equals(reminders)); @@ -133,11 +131,13 @@ void main() { test('handles StreamChatError exceptions properly', () async { const chatError = StreamChatError('Network error'); - when(() => client.queryReminders( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenThrow(chatError); + when( + () => client.queryReminders( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenThrow(chatError); final controller = StreamMessageReminderListController(client: client); @@ -166,11 +166,13 @@ void main() { ..reminders = additionalReminders ..next = null; - when(() => client.queryReminders( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => response); + when( + () => client.queryReminders( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); final controller = StreamMessageReminderListController.fromValue( PagedValue( @@ -198,11 +200,13 @@ void main() { final existingReminders = generateMessageReminders(); const chatError = StreamChatError('Network error'); - when(() => client.queryReminders( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenThrow(chatError); + when( + () => client.queryReminders( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenThrow(chatError); final controller = StreamMessageReminderListController.fromValue( PagedValue( @@ -275,9 +279,7 @@ void main() { ); }); - test( - 'deleteReminder removes reminder and returns true when reminder exists', - () { + test('deleteReminder removes reminder and returns true when reminder exists', () { final reminders = generateMessageReminders(); final controller = StreamMessageReminderListController.fromValue( PagedValue(items: reminders), @@ -292,9 +294,9 @@ void main() { equals(reminders.length - 1), ); expect( - controller.value.asSuccess.items.any((r) => - r.messageId == reminders[0].messageId && - r.userId == reminders[0].userId), + controller.value.asSuccess.items.any( + (r) => r.messageId == reminders[0].messageId && r.userId == reminders[0].userId, + ), isFalse, ); }); @@ -438,9 +440,7 @@ void main() { expect( controller.value.asSuccess.items.any( - (r) => - r.messageId == initialReminders[0].messageId && - r.userId == initialReminders[0].userId, + (r) => r.messageId == initialReminders[0].messageId && r.userId == initialReminders[0].userId, ), isFalse, ); diff --git a/packages/stream_chat_flutter_core/test/stream_poll_controller_test.dart b/packages/stream_chat_flutter_core/test/stream_poll_controller_test.dart index ed5282eb63..408b4c9c29 100644 --- a/packages/stream_chat_flutter_core/test/stream_poll_controller_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_poll_controller_test.dart @@ -11,10 +11,13 @@ void main() { }); test('Initialization with Custom Poll and Config', () { - final poll = Poll(name: 'Initial Poll', options: const [ - PollOption(text: 'Option 1'), - PollOption(text: 'Option 2'), - ]); + final poll = Poll( + name: 'Initial Poll', + options: const [ + PollOption(text: 'Option 1'), + PollOption(text: 'Option 2'), + ], + ); const config = PollConfig(nameRange: (min: 2, max: 50)); final pollController = StreamPollController(poll: poll, config: config); @@ -101,10 +104,7 @@ void main() { final errors = pollController.validateGranularly(); expect(errors.isEmpty, isFalse); - final containsNameRangeError = errors - .map((e) => e.mapOrNull(nameRange: (e) => e)) - .nonNulls - .isNotEmpty; + final containsNameRangeError = errors.map((e) => e.mapOrNull(nameRange: (e) => e)).nonNulls.isNotEmpty; expect(containsNameRangeError, isTrue); }); @@ -117,10 +117,7 @@ void main() { final errors = pollController.validateGranularly(); expect(errors.isEmpty, isFalse); - final containsDuplicateOptions = errors - .map((e) => e.mapOrNull(duplicateOptions: (e) => e)) - .nonNulls - .isNotEmpty; + final containsDuplicateOptions = errors.map((e) => e.mapOrNull(duplicateOptions: (e) => e)).nonNulls.isNotEmpty; expect(containsDuplicateOptions, isTrue); }); @@ -130,10 +127,7 @@ void main() { final errors = pollController.validateGranularly(); expect(errors.isEmpty, isFalse); - final containsOptionsRangeError = errors - .map((e) => e.mapOrNull(optionsRange: (e) => e)) - .nonNulls - .isNotEmpty; + final containsOptionsRangeError = errors.map((e) => e.mapOrNull(optionsRange: (e) => e)).nonNulls.isNotEmpty; expect(containsOptionsRangeError, isTrue); }); @@ -238,10 +232,7 @@ void main() { )..question = 'A' * 200; final errors = pollController.validateGranularly(); - final containsNameRangeError = errors - .map((e) => e.mapOrNull(nameRange: (e) => e)) - .nonNulls - .isNotEmpty; + final containsNameRangeError = errors.map((e) => e.mapOrNull(nameRange: (e) => e)).nonNulls.isNotEmpty; expect(containsNameRangeError, isFalse); }); @@ -256,10 +247,7 @@ void main() { } final errors = pollController.validateGranularly(); - final containsOptionsRangeError = errors - .map((e) => e.mapOrNull(optionsRange: (e) => e)) - .nonNulls - .isNotEmpty; + final containsOptionsRangeError = errors.map((e) => e.mapOrNull(optionsRange: (e) => e)).nonNulls.isNotEmpty; expect(containsOptionsRangeError, isFalse); }); diff --git a/packages/stream_chat_flutter_core/test/stream_thread_list_event_handler_test.dart b/packages/stream_chat_flutter_core/test/stream_thread_list_event_handler_test.dart index 2dd163d6df..f4608b16f1 100644 --- a/packages/stream_chat_flutter_core/test/stream_thread_list_event_handler_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_thread_list_event_handler_test.dart @@ -5,8 +5,7 @@ import 'package:stream_chat_flutter_core/src/stream_thread_list_controller.dart' import 'package:stream_chat_flutter_core/src/stream_thread_list_event_handler.dart'; // Mock classes -class MockStreamThreadListController extends Mock - implements StreamThreadListController {} +class MockStreamThreadListController extends Mock implements StreamThreadListController {} class MockEvent extends Mock implements Event {} @@ -83,8 +82,7 @@ void main() { () { when(() => mockEvent.message).thenReturn(mockMessage); when(() => mockMessage.parentId).thenReturn('parent-id'); - when(() => mockController.getThread( - parentMessageId: any(named: 'parentMessageId'))).thenReturn(null); + when(() => mockController.getThread(parentMessageId: any(named: 'parentMessageId'))).thenReturn(null); handler.onNotificationThreadMessageNew(mockEvent, mockController); verify(() => mockController.addUnseenThreadId('parent-id')); @@ -108,12 +106,10 @@ void main() { when(() => mockMessage.parentId).thenReturn(null); when(() => mockEvent.message).thenReturn(mockMessage); when(() => mockEvent.hardDelete).thenReturn(true); - when(() => mockController.deleteThread(parentMessageId: 'message-id')) - .thenReturn(true); + when(() => mockController.deleteThread(parentMessageId: 'message-id')).thenReturn(true); handler.onMessageDeleted(mockEvent, mockController); - verify( - () => mockController.deleteThread(parentMessageId: 'message-id')); + verify(() => mockController.deleteThread(parentMessageId: 'message-id')); verifyNever(() => mockController.deleteReply(any())); }, ); @@ -178,27 +174,23 @@ void main() { when(() => mockEvent.cid).thenReturn('channel-cid'); handler.onChannelDeleted(mockEvent, mockController); - verify(() => - mockController.deleteThreadByChannelCid(channelCid: 'channel-cid')); + verify(() => mockController.deleteThreadByChannelCid(channelCid: 'channel-cid')); }); test('onChannelTruncated deletes threads by channel cid', () { when(() => mockEvent.cid).thenReturn('channel-cid'); handler.onChannelTruncated(mockEvent, mockController); - verify(() => - mockController.deleteThreadByChannelCid(channelCid: 'channel-cid')); + verify(() => mockController.deleteThreadByChannelCid(channelCid: 'channel-cid')); }); test('onMessageRead marks thread as read', () { - when(() => mockThread.copyWith(read: any(named: 'read'))) - .thenReturn(mockThread); + when(() => mockThread.copyWith(read: any(named: 'read'))).thenReturn(mockThread); when(() => mockThread.parentMessageId).thenReturn('parent-id'); when(() => mockEvent.thread).thenReturn(mockThread); when(() => mockEvent.user).thenReturn(mockUser); when(() => mockEvent.createdAt).thenReturn(DateTime.now()); - when(() => mockController.getThread(parentMessageId: 'parent-id')) - .thenReturn(mockThread); + when(() => mockController.getThread(parentMessageId: 'parent-id')).thenReturn(mockThread); when(() => mockController.updateThread(mockThread)).thenReturn(true); handler.onMessageRead(mockEvent, mockController); @@ -208,14 +200,12 @@ void main() { }); test('onNotificationMarkUnread marks thread as unread', () { - when(() => mockThread.copyWith(read: any(named: 'read'))) - .thenReturn(mockThread); + when(() => mockThread.copyWith(read: any(named: 'read'))).thenReturn(mockThread); when(() => mockThread.parentMessageId).thenReturn('parent-id'); when(() => mockEvent.thread).thenReturn(mockThread); when(() => mockEvent.user).thenReturn(mockUser); when(() => mockEvent.createdAt).thenReturn(DateTime.now()); - when(() => mockController.getThread(parentMessageId: 'parent-id')) - .thenReturn(mockThread); + when(() => mockController.getThread(parentMessageId: 'parent-id')).thenReturn(mockThread); when(() => mockController.updateThread(mockThread)).thenReturn(true); handler.onNotificationMarkUnread(mockEvent, mockController); diff --git a/packages/stream_chat_localizations/example/lib/add_new_lang.dart b/packages/stream_chat_localizations/example/lib/add_new_lang.dart index c9592d4b71..423aac059d 100644 --- a/packages/stream_chat_localizations/example/lib/add_new_lang.dart +++ b/packages/stream_chat_localizations/example/lib/add_new_lang.dart @@ -3,16 +3,14 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_localizations/stream_chat_localizations.dart'; -class _NnStreamChatLocalizationsDelegate - extends LocalizationsDelegate { +class _NnStreamChatLocalizationsDelegate extends LocalizationsDelegate { const _NnStreamChatLocalizationsDelegate(); @override bool isSupported(Locale locale) => locale.languageCode == 'nn'; @override - Future load(Locale locale) => - SynchronousFuture(const NnStreamChatLocalizations()); + Future load(Locale locale) => SynchronousFuture(const NnStreamChatLocalizations()); @override bool shouldReload(_NnStreamChatLocalizationsDelegate old) => false; @@ -73,8 +71,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Uploading $remaining/$total ...'; + }) => 'Uploading $remaining/$total ...'; @override String pinnedByUserText({ @@ -87,8 +84,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { } @override - String get sendMessagePermissionError => - "You don't have permission to send messages"; + String get sendMessagePermissionError => "You don't have permission to send messages"; @override String get emptyMessagesText => 'There are no messages currently'; @@ -161,8 +157,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { 'The file is too large to upload. The file size limit is $limitInMB MB.'; @override - String get couldNotReadBytesFromFileError => - 'Could not read bytes from file.'; + String get couldNotReadBytesFromFileError => 'Could not read bytes from file.'; @override String get addAFileLabel => 'Add a file'; @@ -217,8 +212,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get flagMessageSuccessfulLabel => 'Message flagged'; @override - String get flagMessageSuccessfulText => - 'The message has been reported to a moderator.'; + String get flagMessageSuccessfulText => 'The message has been reported to a moderator.'; @override String get deleteLabel => 'DELETE'; @@ -227,12 +221,10 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get deleteMessageLabel => 'Delete Message'; @override - String get deleteMessageQuestion => - 'Are you sure you want to permanently delete this\nmessage?'; + String get deleteMessageQuestion => 'Are you sure you want to permanently delete this\nmessage?'; @override - String get operationCouldNotBeCompletedText => - "The operation couldn't be completed."; + String get operationCouldNotBeCompletedText => "The operation couldn't be completed."; @override String get replyLabel => 'Reply'; @@ -302,8 +294,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get letsStartChattingLabel => 'Let’s start chatting!'; @override - String get sendingFirstMessageLabel => - 'How about sending your first message to a friend?'; + String get sendingFirstMessageLabel => 'How about sending your first message to a friend?'; @override String get startAChatLabel => 'Start a chat'; @@ -315,8 +306,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get deleteConversationLabel => 'Delete Conversation'; @override - String get deleteConversationQuestion => - 'Are you sure you want to delete this conversation?'; + String get deleteConversationQuestion => 'Are you sure you want to delete this conversation?'; @override String get streamChatLabel => 'Stream Chat'; @@ -355,8 +345,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get leaveConversationLabel => 'Leave conversation'; @override - String get leaveConversationQuestion => - 'Are you sure you want to leave this conversation?'; + String get leaveConversationQuestion => 'Are you sure you want to leave this conversation?'; @override String get showInChatLabel => 'Show in Chat'; @@ -392,8 +381,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '$currentPage of $totalPages'; + }) => '$currentPage of $totalPages'; @override String get fileText => 'File'; @@ -402,8 +390,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get replyToMessageLabel => 'Reply to Message'; @override - String attachmentLimitExceedError(int limit) => - 'Attachment limit exceeded, limit: $limit'; + String attachmentLimitExceedError(int limit) => 'Attachment limit exceeded, limit: $limit'; @override String get slowModeOnLabel => 'Slow mode ON'; @@ -457,8 +444,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { } @override - String get linkDisabledDetails => - 'Sending links is not allowed in this conversation.'; + String get linkDisabledDetails => 'Sending links is not allowed in this conversation.'; @override String get linkDisabledError => 'Links are disabled'; @@ -578,15 +564,13 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get enterYourCommentLabel => 'Enter your comment'; @override - String get endVoteConfirmationText => - 'Are you sure you want to end the poll?'; + String get endVoteConfirmationText => 'Are you sure you want to end the poll?'; @override String get deletePollOptionLabel => 'Delete Option'; @override - String get deletePollOptionQuestion => - 'Are you sure you want to delete this option?'; + String get deletePollOptionQuestion => 'Are you sure you want to delete this option?'; @override String get createLabel => 'Create'; @@ -630,10 +614,10 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 votes', - 1 => '1 vote', - _ => '$count votes', - }; + null || < 1 => '0 votes', + 1 => '1 vote', + _ => '$count votes', + }; @override String get noPollVotesLabel => 'There are no poll votes currently'; @@ -660,8 +644,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get sendAnywayLabel => 'Send Anyway'; @override - String get moderatedMessageBlockedText => - 'Message was blocked by moderation policies'; + String get moderatedMessageBlockedText => 'Message was blocked by moderation policies'; @override String get moderationReviewModalTitle => 'Are you sure?'; diff --git a/packages/stream_chat_localizations/example/lib/main.dart b/packages/stream_chat_localizations/example/lib/main.dart index 22abb65fab..0d1b4fbd23 100644 --- a/packages/stream_chat_localizations/example/lib/main.dart +++ b/packages/stream_chat_localizations/example/lib/main.dart @@ -64,32 +64,32 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) => MaterialApp( - theme: ThemeData.light(), - darkTheme: ThemeData.dark(), - // Add all the supported locales - supportedLocales: const [ - Locale('en'), - Locale('hi'), - Locale('fr'), - Locale('it'), - Locale('es'), - Locale('ja'), - Locale('ko'), - Locale('pt'), - ], - // Add GlobalStreamChatLocalizations.delegates - localizationsDelegates: GlobalStreamChatLocalizations.delegates, - // Programatically set the locale (this is a global change) - locale: const Locale('fr'), - builder: (context, widget) => StreamChat( - client: client, - child: widget, - ), - home: StreamChannel( - channel: channel, - child: const ChannelPage(), - ), - ); + theme: ThemeData.light(), + darkTheme: ThemeData.dark(), + // Add all the supported locales + supportedLocales: const [ + Locale('en'), + Locale('hi'), + Locale('fr'), + Locale('it'), + Locale('es'), + Locale('ja'), + Locale('ko'), + Locale('pt'), + ], + // Add GlobalStreamChatLocalizations.delegates + localizationsDelegates: GlobalStreamChatLocalizations.delegates, + // Programatically set the locale (this is a global change) + locale: const Locale('fr'), + builder: (context, widget) => StreamChat( + client: client, + child: widget, + ), + home: StreamChannel( + channel: channel, + child: const ChannelPage(), + ), + ); } /// A list of messages sent in the current channel. diff --git a/packages/stream_chat_localizations/example/lib/override_lang.dart b/packages/stream_chat_localizations/example/lib/override_lang.dart index 1dc05f9100..9cd76c9849 100644 --- a/packages/stream_chat_localizations/example/lib/override_lang.dart +++ b/packages/stream_chat_localizations/example/lib/override_lang.dart @@ -3,16 +3,14 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_localizations/stream_chat_localizations.dart'; -class _CustomStreamChatLocalizationsDelegate - extends LocalizationsDelegate { +class _CustomStreamChatLocalizationsDelegate extends LocalizationsDelegate { const _CustomStreamChatLocalizationsDelegate(); @override bool isSupported(Locale locale) => locale.languageCode == 'en'; @override - Future load(Locale locale) => - SynchronousFuture(CustomStreamChatLocalizationsEn()); + Future load(Locale locale) => SynchronousFuture(CustomStreamChatLocalizationsEn()); @override bool shouldReload(_CustomStreamChatLocalizationsDelegate old) => false; @@ -89,34 +87,34 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) => MaterialApp( - theme: ThemeData.light(), - darkTheme: ThemeData.dark(), - // Add all the supported locales - supportedLocales: const [ - Locale('en'), - Locale('hi'), - Locale('fr'), - Locale('it'), - Locale('es'), - Locale('ja'), - Locale('ko'), - Locale('pt'), - ], - // Add overridden "CustomStreamChatLocalizationsEn.delegate" along with - // "GlobalStreamChatLocalizations.delegates" - localizationsDelegates: const [ - CustomStreamChatLocalizationsEn.delegate, - ...GlobalStreamChatLocalizations.delegates, - ], - builder: (context, widget) => StreamChat( - client: client, - child: widget, - ), - home: StreamChannel( - channel: channel, - child: const ChannelPage(), - ), - ); + theme: ThemeData.light(), + darkTheme: ThemeData.dark(), + // Add all the supported locales + supportedLocales: const [ + Locale('en'), + Locale('hi'), + Locale('fr'), + Locale('it'), + Locale('es'), + Locale('ja'), + Locale('ko'), + Locale('pt'), + ], + // Add overridden "CustomStreamChatLocalizationsEn.delegate" along with + // "GlobalStreamChatLocalizations.delegates" + localizationsDelegates: const [ + CustomStreamChatLocalizationsEn.delegate, + ...GlobalStreamChatLocalizations.delegates, + ], + builder: (context, widget) => StreamChat( + client: client, + child: widget, + ), + home: StreamChannel( + channel: channel, + child: const ChannelPage(), + ), + ); } /// A list of messages sent in the current channel. diff --git a/packages/stream_chat_localizations/example/pubspec.yaml b/packages/stream_chat_localizations/example/pubspec.yaml index 4c821fd4ac..73442357ee 100644 --- a/packages/stream_chat_localizations/example/pubspec.yaml +++ b/packages/stream_chat_localizations/example/pubspec.yaml @@ -17,8 +17,8 @@ version: 1.0.0+1 # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: cupertino_icons: ^1.0.3 diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart index 62248df818..956b906cb4 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart @@ -112,8 +112,7 @@ GlobalStreamChatLocalizations? getStreamChatTranslation(Locale locale) { /// ) /// ``` /// -abstract class GlobalStreamChatLocalizations - implements StreamChatLocalizations { +abstract class GlobalStreamChatLocalizations implements StreamChatLocalizations { /// Initializes an object that defines the StreamChat widget's localized /// strings for the given `localeName`. const GlobalStreamChatLocalizations({ @@ -129,8 +128,7 @@ abstract class GlobalStreamChatLocalizations /// [GlobalStreamChatLocalizations.delegates] as the value of /// [MaterialApp.localizationsDelegates] to include the localizations for both /// the flutter and stream chat widget libraries. - static const LocalizationsDelegate delegate = - _StreamChatLocalizationsDelegate(); + static const LocalizationsDelegate delegate = _StreamChatLocalizationsDelegate(); /// A value for [MaterialApp.localizationsDelegates] that's typically used by /// internationalized apps. @@ -160,16 +158,13 @@ abstract class GlobalStreamChatLocalizations ]; } -class _StreamChatLocalizationsDelegate - extends LocalizationsDelegate { +class _StreamChatLocalizationsDelegate extends LocalizationsDelegate { const _StreamChatLocalizationsDelegate(); @override - bool isSupported(Locale locale) => - kStreamChatSupportedLanguages.contains(locale.languageCode); + bool isSupported(Locale locale) => kStreamChatSupportedLanguages.contains(locale.languageCode); - static final _loadedTranslations = - >{}; + static final _loadedTranslations = >{}; @override Future load(Locale locale) { @@ -186,6 +181,7 @@ class _StreamChatLocalizationsDelegate bool shouldReload(_StreamChatLocalizationsDelegate old) => false; @override - String toString() => 'GlobalStreamChatLocalizations.delegate(' + String toString() => + 'GlobalStreamChatLocalizations.delegate(' '${kStreamChatSupportedLanguages.length} locales)'; } diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart index dbab4c9b59..0f8159999d 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart @@ -51,8 +51,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Transferència en curs $remaining/$total ...'; + }) => 'Transferència en curs $remaining/$total ...'; @override String pinnedByUserText({ @@ -65,8 +64,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { } @override - String get sendMessagePermissionError => - 'No tens permís per enviar missatges'; + String get sendMessagePermissionError => 'No tens permís per enviar missatges'; @override String get emptyMessagesText => 'Actualment no hi ha missatges'; @@ -75,8 +73,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get genericErrorText => 'Hi ha hagut un problema'; @override - String get loadingMessagesError => - 'Hi ha hagut un error mentre carregava el missatge'; + String get loadingMessagesError => 'Hi ha hagut un error mentre carregava el missatge'; @override String resultCountText(int count) => '$count resultats'; @@ -115,8 +112,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get reconnectingLabel => 'Reconnectant...'; @override - String get alsoSendAsDirectMessageLabel => - 'Enviar també com a missatge directe'; + String get alsoSendAsDirectMessageLabel => 'Enviar també com a missatge directe'; @override String get addACommentOrSendLabel => 'Afegir un comentari o enviar'; @@ -142,8 +138,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { 'La mida màxima del fitxer és de $limitInMB MB.'; @override - String get couldNotReadBytesFromFileError => - "No s'han pogut llegir els bytes del fitxer."; + String get couldNotReadBytesFromFileError => "No s'han pogut llegir els bytes del fitxer."; @override String get addAFileLabel => 'Afegeix un fitxer'; @@ -196,8 +191,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get flagMessageSuccessfulLabel => 'Missatge reportat'; @override - String get flagMessageSuccessfulText => - 'Aquest missatge ha estat reportat a un moderador'; + String get flagMessageSuccessfulText => 'Aquest missatge ha estat reportat a un moderador'; @override String get deleteLabel => 'ESBORRA'; @@ -206,12 +200,10 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get deleteMessageLabel => 'Esborra el missatge'; @override - String get deleteMessageQuestion => - 'Estàs segur que vols esborrar aquest missatge de forma permanent?'; + String get deleteMessageQuestion => 'Estàs segur que vols esborrar aquest missatge de forma permanent?'; @override - String get operationCouldNotBeCompletedText => - "L'operació no s'ha pogut completar"; + String get operationCouldNotBeCompletedText => "L'operació no s'ha pogut completar"; @override String get replyLabel => 'Respondre'; @@ -281,8 +273,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get letsStartChattingLabel => 'Comencem a parlar!'; @override - String get sendingFirstMessageLabel => - 'Què et sembla enviar el teu primer missatge?'; + String get sendingFirstMessageLabel => 'Què et sembla enviar el teu primer missatge?'; @override String get startAChatLabel => 'Inicia una conversa'; @@ -294,8 +285,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get deleteConversationLabel => 'Esborra la conversa'; @override - String get deleteConversationQuestion => - 'Estàs segur que vols esborrar aquesta conversa?'; + String get deleteConversationQuestion => 'Estàs segur que vols esborrar aquesta conversa?'; @override String get streamChatLabel => 'Stream Chat'; @@ -334,8 +324,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get leaveConversationLabel => 'Surt de la conversa'; @override - String get leaveConversationQuestion => - "Estàs segur que vols sortir d'aquesta conversa?"; + String get leaveConversationQuestion => "Estàs segur que vols sortir d'aquesta conversa?"; @override String get showInChatLabel => 'Mostra al xat'; @@ -371,8 +360,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} de $totalPages'; + }) => '${currentPage + 1} de $totalPages'; @override String get fileText => 'Fitxer'; @@ -381,8 +369,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get replyToMessageLabel => 'Respondre al missatge'; @override - String attachmentLimitExceedError(int limit) => - 'No és possible afegir més de $limit fitxers adjunts'; + String attachmentLimitExceedError(int limit) => 'No és possible afegir més de $limit fitxers adjunts'; @override String get viewLibrary => 'Veure llibreria'; @@ -439,8 +426,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { } @override - String get linkDisabledDetails => - 'No es permet enviar enllaços a aquesta conversa'; + String get linkDisabledDetails => 'No es permet enviar enllaços a aquesta conversa'; @override String get linkDisabledError => 'Els enllaços estan deshabilitats'; @@ -449,8 +435,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String unreadMessagesSeparatorText() => 'Missatges nous'; @override - String get enableFileAccessMessage => - "Habilita l'accés als fitxers per poder compartir-los amb amics"; + String get enableFileAccessMessage => "Habilita l'accés als fitxers per poder compartir-los amb amics"; @override String get allowFileAccessMessage => "Permet l'accés als fitxers"; @@ -559,15 +544,13 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get enterYourCommentLabel => 'Introdueix el teu comentari'; @override - String get endVoteConfirmationText => - 'Estàs segur que vols finalitzar la votació?'; + String get endVoteConfirmationText => 'Estàs segur que vols finalitzar la votació?'; @override String get deletePollOptionLabel => 'Eliminar opció'; @override - String get deletePollOptionQuestion => - 'Estàs segur que vols eliminar aquesta opció?'; + String get deletePollOptionQuestion => 'Estàs segur que vols eliminar aquesta opció?'; @override String get createLabel => 'Crear'; @@ -611,10 +594,10 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 vots', - 1 => '1 vot', - _ => '$count vots', - }; + null || < 1 => '0 vots', + 1 => '1 vot', + _ => '$count vots', + }; @override String get noPollVotesLabel => 'No hi ha vots en aquest moment'; @@ -635,15 +618,13 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get slideToCancelLabel => 'Llisca per cancel·lar'; @override - String get holdToRecordLabel => - 'Mantén premut per gravar, deixa anar per enviar'; + String get holdToRecordLabel => 'Mantén premut per gravar, deixa anar per enviar'; @override String get sendAnywayLabel => 'Enviar igualment'; @override - String get moderatedMessageBlockedText => - 'Missatge bloquejat per les polítiques de moderació'; + String get moderatedMessageBlockedText => 'Missatge bloquejat per les polítiques de moderació'; @override String get moderationReviewModalTitle => 'Estàs segur?'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart index ba18de1f32..bd0212a612 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart @@ -51,8 +51,7 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Hochladen $remaining/$total ...'; + }) => 'Hochladen $remaining/$total ...'; @override String pinnedByUserText({ @@ -186,8 +185,7 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String get flagMessageSuccessfulLabel => 'Nachricht gemeldet'; @override - String get flagMessageSuccessfulText => - 'Die Nachricht wurde an einen Moderator weitergeleitet.'; + String get flagMessageSuccessfulText => 'Die Nachricht wurde an einen Moderator weitergeleitet.'; @override String get deleteLabel => 'LÖSCHEN'; @@ -196,12 +194,10 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String get deleteMessageLabel => 'Nachricht löschen'; @override - String get deleteMessageQuestion => - 'Sind Sie sicher, dass Sie diese Nachricht endgültig löschen wollen?'; + String get deleteMessageQuestion => 'Sind Sie sicher, dass Sie diese Nachricht endgültig löschen wollen?'; @override - String get operationCouldNotBeCompletedText => - 'Die Operation konnte nicht abgeschlossen werden.'; + String get operationCouldNotBeCompletedText => 'Die Operation konnte nicht abgeschlossen werden.'; @override String get replyLabel => 'Antwort'; @@ -271,7 +267,8 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String get letsStartChattingLabel => 'Lass uns anfangen zu chatten!'; @override - String get sendingFirstMessageLabel => 'Wie wäre es, wenn Sie Ihre erste ' + String get sendingFirstMessageLabel => + 'Wie wäre es, wenn Sie Ihre erste ' 'Nachricht an einen Freund senden würden?'; @override @@ -284,8 +281,7 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String get deleteConversationLabel => 'Unterhaltung löschen'; @override - String get deleteConversationQuestion => - 'Sind Sie sicher, dass Sie diese Unterhaltung löschen wollen?'; + String get deleteConversationQuestion => 'Sind Sie sicher, dass Sie diese Unterhaltung löschen wollen?'; @override String get streamChatLabel => 'Stream Chat'; @@ -323,8 +319,7 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String get leaveConversationLabel => 'Unterhaltung verlassen'; @override - String get leaveConversationQuestion => - 'Sind Sie sicher, dass Sie diese Unterhaltung verlassen wollen?'; + String get leaveConversationQuestion => 'Sind Sie sicher, dass Sie diese Unterhaltung verlassen wollen?'; @override String get showInChatLabel => 'Im Chat anzeigen'; @@ -360,8 +355,7 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} von $totalPages'; + }) => '${currentPage + 1} von $totalPages'; @override String get fileText => 'Datei'; @@ -370,26 +364,22 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String get replyToMessageLabel => 'Auf Nachricht antworten'; @override - String attachmentLimitExceedError(int limit) => - 'Dateigröße überschritten, Grenze: $limit'; + String attachmentLimitExceedError(int limit) => 'Dateigröße überschritten, Grenze: $limit'; @override String get slowModeOnLabel => 'Langsamer Modus: EIN'; @override - String get linkDisabledDetails => - 'Das Senden von Links ist in dieser Konversation nicht erlaubt.'; + String get linkDisabledDetails => 'Das Senden von Links ist in dieser Konversation nicht erlaubt.'; @override String get linkDisabledError => 'Verknüpfungen sind deaktiviert'; @override - String get sendMessagePermissionError => - 'Sie sind nicht berechtigt Nachrichten zu senden'; + String get sendMessagePermissionError => 'Sie sind nicht berechtigt Nachrichten zu senden'; @override - String get couldNotReadBytesFromFileError => - 'Kan bytes niet uit bestand lezen.'; + String get couldNotReadBytesFromFileError => 'Kan bytes niet uit bestand lezen.'; @override String get downloadLabel => 'Downloaden'; @@ -552,15 +542,13 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String get enterYourCommentLabel => 'Geben Sie Ihren Kommentar ein'; @override - String get endVoteConfirmationText => - 'Sind Sie sicher, dass Sie die Abstimmung beenden möchten?'; + String get endVoteConfirmationText => 'Sind Sie sicher, dass Sie die Abstimmung beenden möchten?'; @override String get deletePollOptionLabel => 'Option löschen'; @override - String get deletePollOptionQuestion => - 'Sind Sie sicher, dass Sie diese Option löschen möchten?'; + String get deletePollOptionQuestion => 'Sind Sie sicher, dass Sie diese Option löschen möchten?'; @override String get createLabel => 'Erstellen'; @@ -604,10 +592,10 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 Stimmen', - 1 => '1 Stimme', - _ => '$count Stimmen', - }; + null || < 1 => '0 Stimmen', + 1 => '1 Stimme', + _ => '$count Stimmen', + }; @override String get noPollVotesLabel => 'Derzeit keine Umfrage-Stimmen'; @@ -634,8 +622,7 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String get sendAnywayLabel => 'Trotzdem senden'; @override - String get moderatedMessageBlockedText => - 'Nachricht wurde durch Moderationsrichtlinien blockiert'; + String get moderatedMessageBlockedText => 'Nachricht wurde durch Moderationsrichtlinien blockiert'; @override String get moderationReviewModalTitle => 'Bist du sicher?'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart index b392b0ea4a..6c5c76f0ee 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart @@ -51,8 +51,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Uploading $remaining/$total ...'; + }) => 'Uploading $remaining/$total ...'; @override String pinnedByUserText({ @@ -65,8 +64,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { } @override - String get sendMessagePermissionError => - "You don't have permission to send messages"; + String get sendMessagePermissionError => "You don't have permission to send messages"; @override String get emptyMessagesText => 'There are no messages currently'; @@ -139,8 +137,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { 'The file is too large to upload. The file size limit is $limitInMB MB.'; @override - String get couldNotReadBytesFromFileError => - 'Could not read bytes from file.'; + String get couldNotReadBytesFromFileError => 'Could not read bytes from file.'; @override String get addAFileLabel => 'Add a file'; @@ -193,8 +190,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get flagMessageSuccessfulLabel => 'Message flagged'; @override - String get flagMessageSuccessfulText => - 'The message has been reported to a moderator.'; + String get flagMessageSuccessfulText => 'The message has been reported to a moderator.'; @override String get deleteLabel => 'DELETE'; @@ -203,12 +199,10 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get deleteMessageLabel => 'Delete Message'; @override - String get deleteMessageQuestion => - 'Are you sure you want to permanently delete this message?'; + String get deleteMessageQuestion => 'Are you sure you want to permanently delete this message?'; @override - String get operationCouldNotBeCompletedText => - "The operation couldn't be completed."; + String get operationCouldNotBeCompletedText => "The operation couldn't be completed."; @override String get replyLabel => 'Reply'; @@ -278,8 +272,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get letsStartChattingLabel => 'Let’s start chatting!'; @override - String get sendingFirstMessageLabel => - 'How about sending your first message to a friend?'; + String get sendingFirstMessageLabel => 'How about sending your first message to a friend?'; @override String get startAChatLabel => 'Start a chat'; @@ -291,8 +284,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get deleteConversationLabel => 'Delete Conversation'; @override - String get deleteConversationQuestion => - 'Are you sure you want to delete this conversation?'; + String get deleteConversationQuestion => 'Are you sure you want to delete this conversation?'; @override String get streamChatLabel => 'Stream Chat'; @@ -331,8 +323,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get leaveConversationLabel => 'Leave conversation'; @override - String get leaveConversationQuestion => - 'Are you sure you want to leave this conversation?'; + String get leaveConversationQuestion => 'Are you sure you want to leave this conversation?'; @override String get showInChatLabel => 'Show in Chat'; @@ -368,8 +359,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} of $totalPages'; + }) => '${currentPage + 1} of $totalPages'; @override String get fileText => 'File'; @@ -378,8 +368,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get replyToMessageLabel => 'Reply to Message'; @override - String attachmentLimitExceedError(int limit) => - 'Attachment limit exceeded, limit: $limit'; + String attachmentLimitExceedError(int limit) => 'Attachment limit exceeded, limit: $limit'; @override String get slowModeOnLabel => 'Slow mode ON'; @@ -433,8 +422,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { } @override - String get linkDisabledDetails => - 'Sending links is not allowed in this conversation.'; + String get linkDisabledDetails => 'Sending links is not allowed in this conversation.'; @override String get linkDisabledError => 'Links are disabled'; @@ -446,8 +434,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String unreadMessagesSeparatorText() => 'New messages'; @override - String get enableFileAccessMessage => - 'Please enable access to files so you can share them with friends.'; + String get enableFileAccessMessage => 'Please enable access to files so you can share them with friends.'; @override String get allowFileAccessMessage => 'Allow access to files'; @@ -555,15 +542,13 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get enterYourCommentLabel => 'Enter your comment'; @override - String get endVoteConfirmationText => - 'Are you sure you want to end the vote?'; + String get endVoteConfirmationText => 'Are you sure you want to end the vote?'; @override String get deletePollOptionLabel => 'Delete Option'; @override - String get deletePollOptionQuestion => - 'Are you sure you want to delete this option?'; + String get deletePollOptionQuestion => 'Are you sure you want to delete this option?'; @override String get createLabel => 'Create'; @@ -607,10 +592,10 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 votes', - 1 => '1 vote', - _ => '$count votes', - }; + null || < 1 => '0 votes', + 1 => '1 vote', + _ => '$count votes', + }; @override String get noPollVotesLabel => 'There are no poll votes currently'; @@ -637,8 +622,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get sendAnywayLabel => 'Send Anyway'; @override - String get moderatedMessageBlockedText => - 'Message was blocked by moderation policies'; + String get moderatedMessageBlockedText => 'Message was blocked by moderation policies'; @override String get moderationReviewModalTitle => 'Are you sure?'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart index d81e4271cd..13d9f430bb 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart @@ -45,15 +45,13 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get onlyVisibleToYouText => 'Sólo visible para usted'; @override - String threadReplyCountText(int count) => - '$count respuestas al hilo de discusión'; + String threadReplyCountText(int count) => '$count respuestas al hilo de discusión'; @override String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Transferencia en curso $remaining/$total ...'; + }) => 'Transferencia en curso $remaining/$total ...'; @override String pinnedByUserText({ @@ -66,8 +64,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { } @override - String get sendMessagePermissionError => - 'No tienes permiso para enviar mensajes'; + String get sendMessagePermissionError => 'No tienes permiso para enviar mensajes'; @override String get emptyMessagesText => 'Actualmente no hay mensajes'; @@ -76,8 +73,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get genericErrorText => 'Hubo un problema'; @override - String get loadingMessagesError => - 'Hubo un error mientras se cargaba el mensaje'; + String get loadingMessagesError => 'Hubo un error mientras se cargaba el mensaje'; @override String resultCountText(int count) => '$count resultados'; @@ -116,8 +112,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get reconnectingLabel => 'Reconectando...'; @override - String get alsoSendAsDirectMessageLabel => - 'Enviar también como mensaje directo'; + String get alsoSendAsDirectMessageLabel => 'Enviar también como mensaje directo'; @override String get addACommentOrSendLabel => 'Añadir un comentario o enviar'; @@ -143,8 +138,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { 'El límite de tamaño de los archivos es de $limitInMB MB.'; @override - String get couldNotReadBytesFromFileError => - 'No se pudieron leer los bytes del archivo.'; + String get couldNotReadBytesFromFileError => 'No se pudieron leer los bytes del archivo.'; @override String get addAFileLabel => 'Añadir un archivo'; @@ -197,8 +191,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get flagMessageSuccessfulLabel => 'Mensaje reportado'; @override - String get flagMessageSuccessfulText => - 'Este mensaje ha sido reportado a un moderador.'; + String get flagMessageSuccessfulText => 'Este mensaje ha sido reportado a un moderador.'; @override String get deleteLabel => 'BORRAR'; @@ -207,12 +200,10 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get deleteMessageLabel => 'Borrar el mensaje'; @override - String get deleteMessageQuestion => - '¿Estás seguro de que quieres borrar este mensaje de forma permanente?'; + String get deleteMessageQuestion => '¿Estás seguro de que quieres borrar este mensaje de forma permanente?'; @override - String get operationCouldNotBeCompletedText => - 'La operación no pudo completarse.'; + String get operationCouldNotBeCompletedText => 'La operación no pudo completarse.'; @override String get replyLabel => 'Responder'; @@ -282,8 +273,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get letsStartChattingLabel => '¡Empecemos a charlar!'; @override - String get sendingFirstMessageLabel => - '¿Qué le parece enviar su primer mensaje a un amigo?'; + String get sendingFirstMessageLabel => '¿Qué le parece enviar su primer mensaje a un amigo?'; @override String get startAChatLabel => 'Iniciar una conversación'; @@ -295,8 +285,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get deleteConversationLabel => 'Borrar la conversación'; @override - String get deleteConversationQuestion => - '¿Estás seguro de que quieres borrar esta conversación?'; + String get deleteConversationQuestion => '¿Estás seguro de que quieres borrar esta conversación?'; @override String get streamChatLabel => 'Stream Chat'; @@ -335,8 +324,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get leaveConversationLabel => 'Salir de la conversación'; @override - String get leaveConversationQuestion => - '¿Estás seguro de que quiere salir de esta conversación?'; + String get leaveConversationQuestion => '¿Estás seguro de que quiere salir de esta conversación?'; @override String get showInChatLabel => 'Mostrar en el chat'; @@ -372,8 +360,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} de $totalPages'; + }) => '${currentPage + 1} de $totalPages'; @override String get fileText => 'Archivo'; @@ -382,7 +369,8 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get replyToMessageLabel => 'Responder al Mensaje'; @override - String attachmentLimitExceedError(int limit) => ''' + String attachmentLimitExceedError(int limit) => + ''' No es posible añadir más de $limit archivos adjuntos '''; @@ -441,8 +429,7 @@ No es posible añadir más de $limit archivos adjuntos } @override - String get linkDisabledDetails => - 'No se permite enviar enlaces en esta conversación.'; + String get linkDisabledDetails => 'No se permite enviar enlaces en esta conversación.'; @override String get linkDisabledError => 'Los enlaces están deshabilitados'; @@ -451,8 +438,7 @@ No es posible añadir más de $limit archivos adjuntos String unreadMessagesSeparatorText() => 'Nuevos mensajes'; @override - String get enableFileAccessMessage => - 'Habilite el acceso a los archivos para poder compartirlos con amigos.'; + String get enableFileAccessMessage => 'Habilite el acceso a los archivos para poder compartirlos con amigos.'; @override String get allowFileAccessMessage => 'Permitir el acceso a los archivos'; @@ -560,15 +546,13 @@ No es posible añadir más de $limit archivos adjuntos String get enterYourCommentLabel => 'Ingresa tu comentario'; @override - String get endVoteConfirmationText => - '¿Estás seguro de que quieres finalizar la votación?'; + String get endVoteConfirmationText => '¿Estás seguro de que quieres finalizar la votación?'; @override String get deletePollOptionLabel => 'Eliminar opción'; @override - String get deletePollOptionQuestion => - '¿Estás seguro de que quieres eliminar esta opción?'; + String get deletePollOptionQuestion => '¿Estás seguro de que quieres eliminar esta opción?'; @override String get createLabel => 'Crear'; @@ -612,17 +596,16 @@ No es posible añadir más de $limit archivos adjuntos @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 votos', - 1 => '1 voto', - _ => '$count votos', - }; + null || < 1 => '0 votos', + 1 => '1 voto', + _ => '$count votos', + }; @override String get noPollVotesLabel => 'No hay votos en la encuesta actualmente'; @override - String get loadingPollVotesError => - 'Error al cargar los votos de la encuesta'; + String get loadingPollVotesError => 'Error al cargar los votos de la encuesta'; @override String get repliedToLabel => 'respondido a:'; @@ -637,15 +620,13 @@ No es posible añadir más de $limit archivos adjuntos String get slideToCancelLabel => 'Desliza para cancelar'; @override - String get holdToRecordLabel => - 'Mantén pulsado para grabar, suelta para enviar'; + String get holdToRecordLabel => 'Mantén pulsado para grabar, suelta para enviar'; @override String get sendAnywayLabel => 'Enviar de todos modos'; @override - String get moderatedMessageBlockedText => - 'Mensaje bloqueado por políticas de moderación'; + String get moderatedMessageBlockedText => 'Mensaje bloqueado por políticas de moderación'; @override String get moderationReviewModalTitle => '¿Estás seguro?'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart index a685fe77ae..44ec8509e3 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart @@ -45,15 +45,13 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get onlyVisibleToYouText => 'Seulement visible par vous'; @override - String threadReplyCountText(int count) => - '$count Réponses au fil de discussion'; + String threadReplyCountText(int count) => '$count Réponses au fil de discussion'; @override String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Transfert en cours $remaining/$total ...'; + }) => 'Transfert en cours $remaining/$total ...'; @override String pinnedByUserText({ @@ -66,8 +64,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { } @override - String get sendMessagePermissionError => - "Vous n'êtes pas autorisé à envoyer des messages"; + String get sendMessagePermissionError => "Vous n'êtes pas autorisé à envoyer des messages"; @override String get emptyMessagesText => "Il n'y a pas de messages actuellement"; @@ -115,8 +112,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get reconnectingLabel => 'Reconnexion...'; @override - String get alsoSendAsDirectMessageLabel => - 'Envoyer aussi comme message direct'; + String get alsoSendAsDirectMessageLabel => 'Envoyer aussi comme message direct'; @override String get addACommentOrSendLabel => 'Ajouter un commentaire ou envoyer'; @@ -142,8 +138,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { 'La taille limite du fichier est de $limitInMB Mo.'; @override - String get couldNotReadBytesFromFileError => - 'Impossible de lire les octets du fichier.'; + String get couldNotReadBytesFromFileError => 'Impossible de lire les octets du fichier.'; @override String get addAFileLabel => 'Ajouter un fichier'; @@ -196,8 +191,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get flagMessageSuccessfulLabel => 'Message signalé'; @override - String get flagMessageSuccessfulText => - 'Ce message a été signalé à un modérateur.'; + String get flagMessageSuccessfulText => 'Ce message a été signalé à un modérateur.'; @override String get deleteLabel => 'SUPPRIMER'; @@ -206,12 +200,10 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get deleteMessageLabel => 'Supprimer le message'; @override - String get deleteMessageQuestion => - 'Êtes-vous sûr de vouloir supprimer définitivement ce message ?'; + String get deleteMessageQuestion => 'Êtes-vous sûr de vouloir supprimer définitivement ce message ?'; @override - String get operationCouldNotBeCompletedText => - "L'opération n'a pas pu être terminée."; + String get operationCouldNotBeCompletedText => "L'opération n'a pas pu être terminée."; @override String get replyLabel => 'Répondre'; @@ -281,8 +273,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get letsStartChattingLabel => 'Commençons à discuter !'; @override - String get sendingFirstMessageLabel => - "Que diriez-vous d'envoyer votre premier message à un ami ?"; + String get sendingFirstMessageLabel => "Que diriez-vous d'envoyer votre premier message à un ami ?"; @override String get startAChatLabel => 'Commencer une discussion'; @@ -294,8 +285,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get deleteConversationLabel => 'Supprimer la conversation'; @override - String get deleteConversationQuestion => - 'Vous êtes sûr de vouloir supprimer cette conversation ?'; + String get deleteConversationQuestion => 'Vous êtes sûr de vouloir supprimer cette conversation ?'; @override String get streamChatLabel => 'Stream Chat'; @@ -334,8 +324,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get leaveConversationLabel => 'Quitter la conversation'; @override - String get leaveConversationQuestion => - 'Etes-vous sûr de vouloir quitter cette conversation ?'; + String get leaveConversationQuestion => 'Etes-vous sûr de vouloir quitter cette conversation ?'; @override String get showInChatLabel => 'Montrer dans la Discussion'; @@ -371,8 +360,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} de $totalPages'; + }) => '${currentPage + 1} de $totalPages'; @override String get fileText => 'Fichier'; @@ -381,7 +369,8 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get replyToMessageLabel => 'Répondre au Message'; @override - String attachmentLimitExceedError(int limit) => ''' + String attachmentLimitExceedError(int limit) => + ''' Limite de pièces jointes dépassée : il n'est pas possible d'ajouter plus de $limit pièces jointes '''; @@ -440,8 +429,7 @@ Limite de pièces jointes dépassée : il n'est pas possible d'ajouter plus de $ } @override - String get linkDisabledDetails => - "L'envoi de liens n'est pas autorisé dans cette conversation."; + String get linkDisabledDetails => "L'envoi de liens n'est pas autorisé dans cette conversation."; @override String get linkDisabledError => 'Les liens sont désactivés'; @@ -518,8 +506,7 @@ Limite de pièces jointes dépassée : il n'est pas possible d'ajouter plus de $ String get multipleAnswersLabel => 'Réponses multiples'; @override - String get maximumVotesPerPersonLabel => - 'Nombre maximum de votes par personne'; + String get maximumVotesPerPersonLabel => 'Nombre maximum de votes par personne'; @override String? maxVotesPerPersonValidationError(int votes, Range range) { @@ -561,15 +548,13 @@ Limite de pièces jointes dépassée : il n'est pas possible d'ajouter plus de $ String get enterYourCommentLabel => 'Entrez votre commentaire'; @override - String get endVoteConfirmationText => - 'Êtes-vous sûr de vouloir terminer le vote?'; + String get endVoteConfirmationText => 'Êtes-vous sûr de vouloir terminer le vote?'; @override String get deletePollOptionLabel => "Supprimer l'option"; @override - String get deletePollOptionQuestion => - 'Êtes-vous sûr de vouloir supprimer cette option ?'; + String get deletePollOptionQuestion => 'Êtes-vous sûr de vouloir supprimer cette option ?'; @override String get createLabel => 'Créer'; @@ -613,18 +598,16 @@ Limite de pièces jointes dépassée : il n'est pas possible d'ajouter plus de $ @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 vote', - 1 => '1 vote', - _ => '$count votes', - }; + null || < 1 => '0 vote', + 1 => '1 vote', + _ => '$count votes', + }; @override - String get noPollVotesLabel => - "Il n'y a pas de votes de sondage actuellement"; + String get noPollVotesLabel => "Il n'y a pas de votes de sondage actuellement"; @override - String get loadingPollVotesError => - 'Erreur de chargement des votes du sondage'; + String get loadingPollVotesError => 'Erreur de chargement des votes du sondage'; @override String get repliedToLabel => 'répondu à:'; @@ -639,15 +622,13 @@ Limite de pièces jointes dépassée : il n'est pas possible d'ajouter plus de $ String get slideToCancelLabel => 'Glissez pour annuler'; @override - String get holdToRecordLabel => - 'Maintenez pour enregistrer, relâchez pour envoyer'; + String get holdToRecordLabel => 'Maintenez pour enregistrer, relâchez pour envoyer'; @override String get sendAnywayLabel => 'Envoyer quand même'; @override - String get moderatedMessageBlockedText => - 'Message bloqué par les politiques de modération'; + String get moderatedMessageBlockedText => 'Message bloqué par les politiques de modération'; @override String get moderationReviewModalTitle => 'Êtes-vous sûr ?'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart index 1bea4031ee..331c273e55 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart @@ -51,8 +51,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'अपलोडिंग $remaining/$total ...'; + }) => 'अपलोडिंग $remaining/$total ...'; @override String pinnedByUserText({ @@ -178,8 +177,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get flagMessageLabel => 'फ्लैग संदेश'; @override - String get flagMessageQuestion => - 'क्या आप आगे की जांच के लिए इस संदेश की एक प्रति मॉडरेटर को भेजना चाहते हैं?'; + String get flagMessageQuestion => 'क्या आप आगे की जांच के लिए इस संदेश की एक प्रति मॉडरेटर को भेजना चाहते हैं?'; @override String get flagLabel => 'फ्लैग'; @@ -191,8 +189,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get flagMessageSuccessfulLabel => 'संदेश फ्लैग हो गया'; @override - String get flagMessageSuccessfulText => - 'संदेश की रिपोर्ट एक मॉडरेटर को कर दी गई है।'; + String get flagMessageSuccessfulText => 'संदेश की रिपोर्ट एक मॉडरेटर को कर दी गई है।'; @override String get deleteLabel => 'हटाएँ'; @@ -201,12 +198,10 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get deleteMessageLabel => 'संदेश हटाएं'; @override - String get deleteMessageQuestion => - 'क्या आप वाकई इस संदेश को स्थायी रूप से हटाना चाहते हैं?'; + String get deleteMessageQuestion => 'क्या आप वाकई इस संदेश को स्थायी रूप से हटाना चाहते हैं?'; @override - String get operationCouldNotBeCompletedText => - 'कार्रवाई पूरी नहीं की जा सकी.'; + String get operationCouldNotBeCompletedText => 'कार्रवाई पूरी नहीं की जा सकी.'; @override String get replyLabel => 'जवाब दें'; @@ -276,8 +271,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get letsStartChattingLabel => 'चलो चैट करना शुरू करें!'; @override - String get sendingFirstMessageLabel => - 'किसी मित्र को अपना पहला संदेश भेजने के बारे में क्या विचार है?'; + String get sendingFirstMessageLabel => 'किसी मित्र को अपना पहला संदेश भेजने के बारे में क्या विचार है?'; @override String get startAChatLabel => 'चैट शुरू करें'; @@ -289,8 +283,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get deleteConversationLabel => 'वार्तालाप हटाए'; @override - String get deleteConversationQuestion => - 'क्या आप वाकई इस वार्तालाप को हटाना चाहते हैं?'; + String get deleteConversationQuestion => 'क्या आप वाकई इस वार्तालाप को हटाना चाहते हैं?'; @override String get streamChatLabel => 'स्ट्रीम चैट'; @@ -329,8 +322,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get leaveConversationLabel => 'वार्तालाप छोड़े'; @override - String get leaveConversationQuestion => - 'क्या आप वाकई इस बातचीत को छोड़ना चाहते हैं?'; + String get leaveConversationQuestion => 'क्या आप वाकई इस बातचीत को छोड़ना चाहते हैं?'; @override String get showInChatLabel => 'चैट में दिखाएं'; @@ -366,8 +358,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} ऑफ़ $totalPages'; + }) => '${currentPage + 1} ऑफ़ $totalPages'; @override String get fileText => 'फ़ाइल'; @@ -376,7 +367,8 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get replyToMessageLabel => 'संदेश का जवाब'; @override - String attachmentLimitExceedError(int limit) => ''' + String attachmentLimitExceedError(int limit) => + ''' अटैचमेंट लिमिट: $limit अटैचमेंट से अधिक जोड़ना संभव नहीं है '''; @@ -435,8 +427,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { } @override - String get linkDisabledDetails => - 'इस बातचीत में लिंक भेजने की अनुमति नहीं है.'; + String get linkDisabledDetails => 'इस बातचीत में लिंक भेजने की अनुमति नहीं है.'; @override String get linkDisabledError => 'लिंक भेजना प्रतिबंधित'; @@ -445,8 +436,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String unreadMessagesSeparatorText() => 'नए संदेश।'; @override - String get enableFileAccessMessage => - 'कृपया फ़ाइलों तक पहुंच सक्षम करें ताकि आप उन्हें मित्रों के साथ साझा कर सकें।'; + String get enableFileAccessMessage => 'कृपया फ़ाइलों तक पहुंच सक्षम करें ताकि आप उन्हें मित्रों के साथ साझा कर सकें।'; @override String get allowFileAccessMessage => 'फाइलों तक पहुंच की अनुमति दें'; @@ -548,15 +538,13 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get enterYourCommentLabel => 'अपनी टिप्पणी दर्ज करें'; @override - String get endVoteConfirmationText => - 'क्या आप वाकई मतदान समाप्त करना चाहते हैं?'; + String get endVoteConfirmationText => 'क्या आप वाकई मतदान समाप्त करना चाहते हैं?'; @override String get deletePollOptionLabel => 'विकल्प हटाएं'; @override - String get deletePollOptionQuestion => - 'क्या आप वाकई इस विकल्प को हटाना चाहते हैं?'; + String get deletePollOptionQuestion => 'क्या आप वाकई इस विकल्प को हटाना चाहते हैं?'; @override String get endLabel => 'समाप्त'; @@ -632,15 +620,13 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get slideToCancelLabel => 'रद्द करने के लिए स्लाइड करें'; @override - String get holdToRecordLabel => - 'रिकॉर्ड करने के लिए दबाए रखें, भेजने के लिए छोड़ें'; + String get holdToRecordLabel => 'रिकॉर्ड करने के लिए दबाए रखें, भेजने के लिए छोड़ें'; @override String get sendAnywayLabel => 'फिर भी भेजें'; @override - String get moderatedMessageBlockedText => - 'मॉडरेशन नीतियों द्वारा संदेश अवरुद्ध किया गया'; + String get moderatedMessageBlockedText => 'मॉडरेशन नीतियों द्वारा संदेश अवरुद्ध किया गया'; @override String get moderationReviewModalTitle => 'क्या आप निश्चित हैं?'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart index 39d56c44c1..d6a318dc23 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart @@ -56,8 +56,7 @@ class StreamChatLocalizationsIt extends GlobalStreamChatLocalizations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Caricamento $remaining/$total ...'; + }) => 'Caricamento $remaining/$total ...'; @override String pinnedByUserText({ @@ -70,8 +69,7 @@ class StreamChatLocalizationsIt extends GlobalStreamChatLocalizations { } @override - String get sendMessagePermissionError => - "Non hai l'autorizzazione per inviare messaggi"; + String get sendMessagePermissionError => "Non hai l'autorizzazione per inviare messaggi"; @override String get emptyMessagesText => "Non c'é nessun messaggio al momento"; @@ -80,8 +78,7 @@ class StreamChatLocalizationsIt extends GlobalStreamChatLocalizations { String get genericErrorText => 'Qualcosa è andato storto'; @override - String get loadingMessagesError => - 'Errore durante il caricamento dei messaggi'; + String get loadingMessagesError => 'Errore durante il caricamento dei messaggi'; @override String resultCountText(int count) => '$count risultati'; @@ -120,8 +117,7 @@ class StreamChatLocalizationsIt extends GlobalStreamChatLocalizations { String get reconnectingLabel => 'Riconnessione in corso...'; @override - String get alsoSendAsDirectMessageLabel => - 'Manda anche come messaggio diretto'; + String get alsoSendAsDirectMessageLabel => 'Manda anche come messaggio diretto'; @override String get addACommentOrSendLabel => 'Aggiungi un commento o invia'; @@ -146,8 +142,7 @@ class StreamChatLocalizationsIt extends GlobalStreamChatLocalizations { Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; @override - String get couldNotReadBytesFromFileError => - 'Impossibile leggere i byte dal file.'; + String get couldNotReadBytesFromFileError => 'Impossibile leggere i byte dal file.'; @override String get addAFileLabel => 'Aggiungi un file'; @@ -187,8 +182,7 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get flagMessageLabel => 'Segnala messaggio'; @override - String get flagMessageQuestion => - 'Vuoi mandare una copia di questo messaggio ad un moderatore?'; + String get flagMessageQuestion => 'Vuoi mandare una copia di questo messaggio ad un moderatore?'; @override String get flagLabel => 'SEGNALA'; @@ -200,8 +194,7 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get flagMessageSuccessfulLabel => 'Messaggio segnalato'; @override - String get flagMessageSuccessfulText => - 'Questo messaggio è stato segnalato ad un moderatore.'; + String get flagMessageSuccessfulText => 'Questo messaggio è stato segnalato ad un moderatore.'; @override String get deleteLabel => 'CANCELLA'; @@ -210,12 +203,10 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get deleteMessageLabel => 'Cancella messaggio'; @override - String get deleteMessageQuestion => - 'Sei sicuro di voler definitivamente cancellare questo messaggio?'; + String get deleteMessageQuestion => 'Sei sicuro di voler definitivamente cancellare questo messaggio?'; @override - String get operationCouldNotBeCompletedText => - 'Non è stato possibile completare questa operazione.'; + String get operationCouldNotBeCompletedText => 'Non è stato possibile completare questa operazione.'; @override String get replyLabel => 'Rispondi'; @@ -285,8 +276,7 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get letsStartChattingLabel => 'Inizia una conversazione!'; @override - String get sendingFirstMessageLabel => - 'Che ne dici di mandare il tuo primo messaggio ad un amico?'; + String get sendingFirstMessageLabel => 'Che ne dici di mandare il tuo primo messaggio ad un amico?'; @override String get startAChatLabel => 'Inizia una conversazione'; @@ -298,8 +288,7 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get deleteConversationLabel => 'Elimina conversazione'; @override - String get deleteConversationQuestion => - 'Sei sicuro di voler eliminare questa conversazione?'; + String get deleteConversationQuestion => 'Sei sicuro di voler eliminare questa conversazione?'; @override String get streamChatLabel => 'Stream Chat'; @@ -338,8 +327,7 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get leaveConversationLabel => 'Esci dalla conversazione'; @override - String get leaveConversationQuestion => - 'Sei sicuro di voler lasciare questa conversazione?'; + String get leaveConversationQuestion => 'Sei sicuro di voler lasciare questa conversazione?'; @override String get showInChatLabel => 'Mostra nella chat'; @@ -375,8 +363,7 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} di $totalPages'; + }) => '${currentPage + 1} di $totalPages'; @override String get fileText => 'file'; @@ -385,7 +372,8 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get replyToMessageLabel => 'Rispondi al messaggio'; @override - String attachmentLimitExceedError(int limit) => ''' + String attachmentLimitExceedError(int limit) => + ''' Attenzione: il limite massimo di $limit file è stato superato. '''; @@ -444,8 +432,7 @@ Attenzione: il limite massimo di $limit file è stato superato. } @override - String get linkDisabledDetails => - 'Non è permesso condividere link in questa convesazione.'; + String get linkDisabledDetails => 'Non è permesso condividere link in questa convesazione.'; @override String get linkDisabledError => 'I links sono disattivati'; @@ -564,15 +551,13 @@ Attenzione: il limite massimo di $limit file è stato superato. String get enterYourCommentLabel => 'Inserisci il tuo commento'; @override - String get endVoteConfirmationText => - 'Sei sicuro di voler terminare il voto?'; + String get endVoteConfirmationText => 'Sei sicuro di voler terminare il voto?'; @override String get deletePollOptionLabel => "Elimina l'opzione"; @override - String get deletePollOptionQuestion => - 'Sei sicuro di voler eliminare questa opzione?'; + String get deletePollOptionQuestion => 'Sei sicuro di voler eliminare questa opzione?'; @override String get createLabel => 'Crea'; @@ -616,17 +601,16 @@ Attenzione: il limite massimo di $limit file è stato superato. @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 voti', - 1 => '1 voto', - _ => '$count voti', - }; + null || < 1 => '0 voti', + 1 => '1 voto', + _ => '$count voti', + }; @override String get noPollVotesLabel => 'Attualmente non ci sono voti nel sondaggio'; @override - String get loadingPollVotesError => - 'Errore durante il caricamento dei voti del sondaggio'; + String get loadingPollVotesError => 'Errore durante il caricamento dei voti del sondaggio'; @override String get repliedToLabel => 'risposto a:'; @@ -641,15 +625,13 @@ Attenzione: il limite massimo di $limit file è stato superato. String get slideToCancelLabel => 'Scorri per annullare'; @override - String get holdToRecordLabel => - 'Tieni premuto per registrare, rilascia per inviare'; + String get holdToRecordLabel => 'Tieni premuto per registrare, rilascia per inviare'; @override String get sendAnywayLabel => 'Invia comunque'; @override - String get moderatedMessageBlockedText => - 'Messaggio bloccato dalle politiche di moderazione'; + String get moderatedMessageBlockedText => 'Messaggio bloccato dalle politiche di moderazione'; @override String get moderationReviewModalTitle => 'Sei sicuro?'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart index 25dc633132..a425b69642 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart @@ -49,8 +49,7 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - '$remaining/${total}mbのアップロード中…'; + }) => '$remaining/${total}mbのアップロード中…'; @override String pinnedByUserText({ @@ -129,8 +128,7 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { '圧縮を試しましたがサイズをオーバーしました'; @override - String fileTooLargeError(double limitInMB) => - 'ファイルが大きすぎてアップロードできません。ファイルサイズの制限は${limitInMB}MBです。'; + String fileTooLargeError(double limitInMB) => 'ファイルが大きすぎてアップロードできません。ファイルサイズの制限は${limitInMB}MBです。'; @override String get couldNotReadBytesFromFileError => 'ファイルからバイトを読み取れませんでした'; @@ -163,8 +161,7 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { String get addMoreFilesLabel => 'ファイルの追加'; @override - String get enablePhotoAndVideoAccessMessage => - 'お友達と共有できるように、写真やビデオへのアクセスを有効にしてください。'; + String get enablePhotoAndVideoAccessMessage => 'お友達と共有できるように、写真やビデオへのアクセスを有効にしてください。'; @override String get allowGalleryAccessMessage => 'ギャラリーへのアクセスを許可する'; @@ -349,8 +346,7 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} / $totalPages'; + }) => '${currentPage + 1} / $totalPages'; @override String get fileText => 'ファイル'; @@ -365,7 +361,8 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { String get viewLibrary => 'ライブラリを表示'; @override - String attachmentLimitExceedError(int limit) => ''' + String attachmentLimitExceedError(int limit) => + ''' 添付ファイルの制限を超えました:$limit個のファイル以上を添付することはできません '''; @@ -441,8 +438,7 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { } @override - String get markUnreadError => - 'メッセージを未読にする際にエラーが発生しました。最新の100件のチャンネルメッセージより古い未読メッセージはマークできません。'; + String get markUnreadError => 'メッセージを未読にする際にエラーが発生しました。最新の100件のチャンネルメッセージより古い未読メッセージはマークできません。'; @override String createPollLabel({bool isNew = false}) { @@ -584,10 +580,10 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 票', - 1 => '1 票', - _ => '$count 票', - }; + null || < 1 => '0 票', + 1 => '1 票', + _ => '$count 票', + }; @override String get noPollVotesLabel => '現在投票はありません'; @@ -619,8 +615,7 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { String get moderationReviewModalTitle => 'よろしいですか?'; @override - String get moderationReviewModalDescription => - '''あなたのコメントが他の人にどのような影響を与えるかを考え、コミュニティガイドラインに従ってください。'''; + String get moderationReviewModalDescription => '''あなたのコメントが他の人にどのような影響を与えるかを考え、コミュニティガイドラインに従ってください。'''; @override String get emptyMessagePreviewText => ''; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart index 471dcda48a..b9fd0eba09 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart @@ -49,8 +49,7 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - '$remaining/${total}mb를 업로드중...'; + }) => '$remaining/${total}mb를 업로드중...'; @override String pinnedByUserText({ @@ -129,8 +128,7 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { '우리는 압축해 보았지만 충분하지 않았습니다.'; @override - String fileTooLargeError(double limitInMB) => - '파일이 너무 커서 업로드할 수 없습니다. 파일 크기 제한은 ${limitInMB}MB입니다.'; + String fileTooLargeError(double limitInMB) => '파일이 너무 커서 업로드할 수 없습니다. 파일 크기 제한은 ${limitInMB}MB입니다.'; @override String get couldNotReadBytesFromFileError => '파일에서 바이트를 읽을 수 없습니다.'; @@ -163,8 +161,7 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { String get addMoreFilesLabel => '파일을 추가함'; @override - String get enablePhotoAndVideoAccessMessage => - '친구와 공유할 수 있도록 사진과 동영상에 액세스할 수 있도록 설정하십시오.'; + String get enablePhotoAndVideoAccessMessage => '친구와 공유할 수 있도록 사진과 동영상에 액세스할 수 있도록 설정하십시오.'; @override String get allowGalleryAccessMessage => '갤러리에 대한 액세스를 허용합니다'; @@ -350,8 +347,7 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} / $totalPages'; + }) => '${currentPage + 1} / $totalPages'; //3 / 11 @@ -369,8 +365,7 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { String get viewLibrary => '라이브러리 보기'; @override - String attachmentLimitExceedError(int limit) => - '첨부 파일 제한 초과: $limit 이상의 첨부 파일을 추가할 수 없습니다'; + String attachmentLimitExceedError(int limit) => '첨부 파일 제한 초과: $limit 이상의 첨부 파일을 추가할 수 없습니다'; @override String get downloadLabel => '다운로드'; @@ -588,10 +583,10 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 표', - 1 => '1 표', - _ => '$count 표', - }; + null || < 1 => '0 표', + 1 => '1 표', + _ => '$count 표', + }; @override String get noPollVotesLabel => '현재 투표가 없습니다'; @@ -623,8 +618,7 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { String get moderationReviewModalTitle => '확실합니까?'; @override - String get moderationReviewModalDescription => - '''귀하의 댓글이 다른 사람들에게 어떤 영향을 미칠 수 있는지 고려하고 커뮤니티 가이드라인을 준수하세요.'''; + String get moderationReviewModalDescription => '''귀하의 댓글이 다른 사람들에게 어떤 영향을 미칠 수 있는지 고려하고 커뮤니티 가이드라인을 준수하세요.'''; @override String get emptyMessagePreviewText => ''; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart index 7ea81dae20..70edbb689b 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart @@ -51,8 +51,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Laster opp $remaining/$total ...'; + }) => 'Laster opp $remaining/$total ...'; @override String pinnedByUserText({ @@ -65,8 +64,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { } @override - String get sendMessagePermissionError => - 'Du har ikke tillatelse til å sende meldinger'; + String get sendMessagePermissionError => 'Du har ikke tillatelse til å sende meldinger'; @override String get emptyMessagesText => 'Det er ingen meldinger akkurat nå'; @@ -135,8 +133,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { 'Vi prøvde å komprimere den, men det hjalp ikke.'; @override - String fileTooLargeError(double limitInMB) => - 'Filen er for stor til å laste opp. Filgrense er $limitInMB MB.'; + String fileTooLargeError(double limitInMB) => 'Filen er for stor til å laste opp. Filgrense er $limitInMB MB.'; @override String get addAFileLabel => 'Legg til en fil'; @@ -189,8 +186,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get flagMessageSuccessfulLabel => 'Melding rapportert'; @override - String get flagMessageSuccessfulText => - 'Meldingen har blitt rapportert til en moderator.'; + String get flagMessageSuccessfulText => 'Meldingen har blitt rapportert til en moderator.'; @override String get deleteLabel => 'SLETT'; @@ -199,12 +195,10 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get deleteMessageLabel => 'Slett melding'; @override - String get deleteMessageQuestion => - 'Er du sikker på at du ønsker å slette denne meldingen permanent?'; + String get deleteMessageQuestion => 'Er du sikker på at du ønsker å slette denne meldingen permanent?'; @override - String get operationCouldNotBeCompletedText => - 'Denne handlingen kunne ikke bli gjennomført.'; + String get operationCouldNotBeCompletedText => 'Denne handlingen kunne ikke bli gjennomført.'; @override String get replyLabel => 'Svar'; @@ -274,8 +268,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get letsStartChattingLabel => 'La oss starte å chatte!'; @override - String get sendingFirstMessageLabel => - 'Hva med å sende din første melding til en venn?'; + String get sendingFirstMessageLabel => 'Hva med å sende din første melding til en venn?'; @override String get startAChatLabel => 'Start en chat'; @@ -287,8 +280,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get deleteConversationLabel => 'Slett samtale'; @override - String get deleteConversationQuestion => - 'Er du sikker på at du ønsker å slette denne samtalen?'; + String get deleteConversationQuestion => 'Er du sikker på at du ønsker å slette denne samtalen?'; @override String get streamChatLabel => 'Stream Chat'; @@ -327,8 +319,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get leaveConversationLabel => 'Forlat samtale'; @override - String get leaveConversationQuestion => - 'Er du sikker på at du ønsker å forlate denne samtalen?'; + String get leaveConversationQuestion => 'Er du sikker på at du ønsker å forlate denne samtalen?'; @override String get showInChatLabel => 'Se i chat'; @@ -364,8 +355,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} of $totalPages'; + }) => '${currentPage + 1} of $totalPages'; @override String get fileText => 'Fil'; @@ -374,15 +364,13 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get replyToMessageLabel => 'Svar på melding'; @override - String attachmentLimitExceedError(int limit) => - 'Antall vedlegg oversteget, maks antall: $limit'; + String attachmentLimitExceedError(int limit) => 'Antall vedlegg oversteget, maks antall: $limit'; @override String get slowModeOnLabel => 'Sakte modus PÅ'; @override - String get linkDisabledDetails => - 'Sende lenker er ikke lov i denne samtalen.'; + String get linkDisabledDetails => 'Sende lenker er ikke lov i denne samtalen.'; @override String get linkDisabledError => 'Lenker er deaktivert'; @@ -394,8 +382,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String unreadMessagesSeparatorText() => 'Nye meldinger.'; @override - String get couldNotReadBytesFromFileError => - 'Kunne ikke lese bytes fra filen.'; + String get couldNotReadBytesFromFileError => 'Kunne ikke lese bytes fra filen.'; @override String get downloadLabel => 'Nedlasting'; @@ -435,8 +422,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { } @override - String get enableFileAccessMessage => - 'Aktiver tilgang til filer slik at du kan dele dem med venner.'; + String get enableFileAccessMessage => 'Aktiver tilgang til filer slik at du kan dele dem med venner.'; @override String get allowFileAccessMessage => 'Gi tilgang til filer'; @@ -502,8 +488,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get multipleAnswersLabel => 'Flere svar'; @override - String get maximumVotesPerPersonLabel => - 'Maksimalt antall stemmer per person'; + String get maximumVotesPerPersonLabel => 'Maksimalt antall stemmer per person'; @override String? maxVotesPerPersonValidationError(int votes, Range range) { @@ -545,15 +530,13 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get enterYourCommentLabel => 'Skriv inn kommentaren din'; @override - String get endVoteConfirmationText => - 'Er du sikker på at du vil avslutte avstemningen?'; + String get endVoteConfirmationText => 'Er du sikker på at du vil avslutte avstemningen?'; @override String get deletePollOptionLabel => 'Slett alternativ'; @override - String get deletePollOptionQuestion => - 'Er du sikker på at du vil slette dette alternativet?'; + String get deletePollOptionQuestion => 'Er du sikker på at du vil slette dette alternativet?'; @override String get createLabel => 'Opprett'; @@ -597,10 +580,10 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 stemmer', - 1 => '1 stemme', - _ => '$count stemmer', - }; + null || < 1 => '0 stemmer', + 1 => '1 stemme', + _ => '$count stemmer', + }; @override String get noPollVotesLabel => 'Det er ingen stemmer for øyeblikket'; @@ -627,8 +610,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get sendAnywayLabel => 'Send likevel'; @override - String get moderatedMessageBlockedText => - 'Meldingen ble blokkert av modereringsregler'; + String get moderatedMessageBlockedText => 'Meldingen ble blokkert av modereringsregler'; @override String get moderationReviewModalTitle => 'Er du sikker?'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart index 2b8a79956e..b315cf2bf2 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart @@ -51,8 +51,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Tranferência em andamento $remaining/$total ...'; + }) => 'Tranferência em andamento $remaining/$total ...'; @override String pinnedByUserText({ @@ -71,8 +70,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get genericErrorText => 'Ocorreu um problema'; @override - String get loadingMessagesError => - 'Ocorreu um problema ao carregar a mensagem'; + String get loadingMessagesError => 'Ocorreu um problema ao carregar a mensagem'; @override String resultCountText(int count) => '$count resultados'; @@ -111,8 +109,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get reconnectingLabel => 'Reconectando...'; @override - String get alsoSendAsDirectMessageLabel => - 'Enviar também como mensagem direta'; + String get alsoSendAsDirectMessageLabel => 'Enviar também como mensagem direta'; @override String get addACommentOrSendLabel => 'Adicionar um comnetário ou enviar'; @@ -138,8 +135,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { 'O tamanho máximo dos arquivos é de $limitInMB MB.'; @override - String get couldNotReadBytesFromFileError => - 'Não foi possível ler os bytes do arquivo.'; + String get couldNotReadBytesFromFileError => 'Não foi possível ler os bytes do arquivo.'; @override String get addAFileLabel => 'Adicionar um arquivo'; @@ -179,8 +175,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get flagMessageLabel => 'Denunciar mensagem'; @override - String get flagMessageQuestion => - 'Gostaria de enviar esta mensagem ao moderador para maior investigação?'; + String get flagMessageQuestion => 'Gostaria de enviar esta mensagem ao moderador para maior investigação?'; @override String get flagLabel => 'DENUNCIAR'; @@ -192,8 +187,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get flagMessageSuccessfulLabel => 'Mensagem denunciada'; @override - String get flagMessageSuccessfulText => - 'Esta mensagem foi enviada a um moderador.'; + String get flagMessageSuccessfulText => 'Esta mensagem foi enviada a um moderador.'; @override String get deleteLabel => 'APAGAR'; @@ -202,12 +196,10 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get deleteMessageLabel => 'Apagar mensagem'; @override - String get deleteMessageQuestion => - 'Você tem certeza que deseja apagar essa mensagem permanentemente?'; + String get deleteMessageQuestion => 'Você tem certeza que deseja apagar essa mensagem permanentemente?'; @override - String get operationCouldNotBeCompletedText => - 'A operação não pode ser completada.'; + String get operationCouldNotBeCompletedText => 'A operação não pode ser completada.'; @override String get replyLabel => 'Resposta'; @@ -277,8 +269,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get letsStartChattingLabel => 'Vamos começar a conversar!'; @override - String get sendingFirstMessageLabel => - 'Que tal enviar sua primeira mensagem a um amigo?'; + String get sendingFirstMessageLabel => 'Que tal enviar sua primeira mensagem a um amigo?'; @override String get startAChatLabel => 'Iniciar uma conversa'; @@ -290,8 +281,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get deleteConversationLabel => 'Apagar a conversa'; @override - String get deleteConversationQuestion => - 'Tem certeza que deseja apagar essa conversa?'; + String get deleteConversationQuestion => 'Tem certeza que deseja apagar essa conversa?'; @override String get streamChatLabel => 'Stream Chat'; @@ -330,8 +320,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get leaveConversationLabel => 'Sair da conversa'; @override - String get leaveConversationQuestion => - 'Tem certeza que deseja sair dessa conversa?'; + String get leaveConversationQuestion => 'Tem certeza que deseja sair dessa conversa?'; @override String get showInChatLabel => 'Mostrar no chat'; @@ -367,8 +356,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} de $totalPages'; + }) => '${currentPage + 1} de $totalPages'; @override String get fileText => 'Arquivo'; @@ -377,7 +365,8 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get replyToMessageLabel => 'Responder à mensagem'; @override - String attachmentLimitExceedError(int limit) => ''' + String attachmentLimitExceedError(int limit) => + ''' Não é possível adicionar mais de $limit arquivos de uma vez '''; @@ -433,15 +422,13 @@ Não é possível adicionar mais de $limit arquivos de uma vez } @override - String get linkDisabledDetails => - 'O envio de links não é permitido nesta conversa.'; + String get linkDisabledDetails => 'O envio de links não é permitido nesta conversa.'; @override String get linkDisabledError => 'Os links estão desativados'; @override - String get sendMessagePermissionError => - 'Você não tem permissão para enviar mensagens'; + String get sendMessagePermissionError => 'Você não tem permissão para enviar mensagens'; @override String get viewLibrary => 'Ver biblioteca'; @@ -450,8 +437,7 @@ Não é possível adicionar mais de $limit arquivos de uma vez String unreadMessagesSeparatorText() => 'Novas mensagens'; @override - String get enableFileAccessMessage => - 'Ative o acesso aos arquivos para poder compartilhá-los com amigos.'; + String get enableFileAccessMessage => 'Ative o acesso aos arquivos para poder compartilhá-los com amigos.'; @override String get allowFileAccessMessage => 'Permitir acesso aos arquivos'; @@ -559,15 +545,13 @@ Não é possível adicionar mais de $limit arquivos de uma vez String get enterYourCommentLabel => 'Inserir seu comentário'; @override - String get endVoteConfirmationText => - 'Tem certeza de que deseja encerrar a votação?'; + String get endVoteConfirmationText => 'Tem certeza de que deseja encerrar a votação?'; @override String get deletePollOptionLabel => 'Excluir opção'; @override - String get deletePollOptionQuestion => - 'Tem certeza de que deseja excluir esta opção?'; + String get deletePollOptionQuestion => 'Tem certeza de que deseja excluir esta opção?'; @override String get createLabel => 'Criar'; @@ -611,10 +595,10 @@ Não é possível adicionar mais de $limit arquivos de uma vez @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 votos', - 1 => '1 voto', - _ => '$count votos', - }; + null || < 1 => '0 votos', + 1 => '1 voto', + _ => '$count votos', + }; @override String get noPollVotesLabel => 'Não há votos no momento'; @@ -635,15 +619,13 @@ Não é possível adicionar mais de $limit arquivos de uma vez String get slideToCancelLabel => 'Deslize para cancelar'; @override - String get holdToRecordLabel => - 'Mantenha pressionado para gravar, solte para enviar'; + String get holdToRecordLabel => 'Mantenha pressionado para gravar, solte para enviar'; @override String get sendAnywayLabel => 'Enviar mesmo assim'; @override - String get moderatedMessageBlockedText => - 'Mensagem bloqueada pelas políticas de moderação'; + String get moderatedMessageBlockedText => 'Mensagem bloqueada pelas políticas de moderação'; @override String get moderationReviewModalTitle => 'Tem certeza?'; diff --git a/packages/stream_chat_localizations/lib/stream_chat_localizations.dart b/packages/stream_chat_localizations/lib/stream_chat_localizations.dart index 27c018806a..852e8e2f54 100644 --- a/packages/stream_chat_localizations/lib/stream_chat_localizations.dart +++ b/packages/stream_chat_localizations/lib/stream_chat_localizations.dart @@ -2,8 +2,5 @@ library stream_chat_localizations; export 'package:flutter_localizations/flutter_localizations.dart' - show - GlobalCupertinoLocalizations, - GlobalMaterialLocalizations, - GlobalWidgetsLocalizations; + show GlobalCupertinoLocalizations, GlobalMaterialLocalizations, GlobalWidgetsLocalizations; export 'src/stream_chat_localizations.dart' hide getStreamChatTranslation; diff --git a/packages/stream_chat_localizations/pubspec.yaml b/packages/stream_chat_localizations/pubspec.yaml index 7be029acac..acdce87309 100644 --- a/packages/stream_chat_localizations/pubspec.yaml +++ b/packages/stream_chat_localizations/pubspec.yaml @@ -18,8 +18,8 @@ issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: flutter: diff --git a/packages/stream_chat_localizations/test/basics_test.dart b/packages/stream_chat_localizations/test/basics_test.dart index a05877f43c..f973c08d45 100644 --- a/packages/stream_chat_localizations/test/basics_test.dart +++ b/packages/stream_chat_localizations/test/basics_test.dart @@ -6,28 +6,32 @@ import 'package:stream_chat_localizations/stream_chat_localizations.dart'; void main() { testWidgets('Nested Localizations', (WidgetTester tester) async { - await tester.pumpWidget(MaterialApp( - theme: ThemeData( - useMaterial3: false, + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + useMaterial3: false, + ), + // Creates the outer Localizations widget. + home: ListView( + children: [ + const LocalizationTracker(key: ValueKey('outer')), + Localizations( + locale: const Locale('hi'), + delegates: GlobalStreamChatLocalizations.delegates, + child: const LocalizationTracker(key: ValueKey('inner')), + ), + ], + ), ), - // Creates the outer Localizations widget. - home: ListView( - children: [ - const LocalizationTracker(key: ValueKey('outer')), - Localizations( - locale: const Locale('hi'), - delegates: GlobalStreamChatLocalizations.delegates, - child: const LocalizationTracker(key: ValueKey('inner')), - ), - ], - ), - )); + ); final LocalizationTrackerState outerTracker = tester.state( - find.byKey(const ValueKey('outer'), skipOffstage: false)); + find.byKey(const ValueKey('outer'), skipOffstage: false), + ); expect(outerTracker.captionFontSize, 12.0); final LocalizationTrackerState innerTracker = tester.state( - find.byKey(const ValueKey('inner'), skipOffstage: false)); + find.byKey(const ValueKey('inner'), skipOffstage: false), + ); expect(innerTracker.captionFontSize, 13.0); }); @@ -36,19 +40,21 @@ void main() { 'during didChangeDependencies', (WidgetTester tester) async { // PageView calls ScrollPosition.dispose() during didChangeDependencies. - await tester.pumpWidget(MaterialApp( - supportedLocales: const [ - Locale('en', 'US'), - Locale('hi', 'IN'), - ], - localizationsDelegates: const [ - DummyLocalizations.delegate, - GlobalStreamChatLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - home: PageView(), - )); + await tester.pumpWidget( + MaterialApp( + supportedLocales: const [ + Locale('en', 'US'), + Locale('hi', 'IN'), + ], + localizationsDelegates: const [ + DummyLocalizations.delegate, + GlobalStreamChatLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + home: PageView(), + ), + ); await tester.binding.setLocale('hi', 'IN'); await tester.pump(); @@ -58,14 +64,16 @@ void main() { testWidgets('Locale without countryCode', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/pull/16782 - await tester.pumpWidget(MaterialApp( - localizationsDelegates: GlobalStreamChatLocalizations.delegates, - supportedLocales: const [ - Locale('en', 'US'), - Locale('hi'), - ], - home: Container(), - )); + await tester.pumpWidget( + MaterialApp( + localizationsDelegates: GlobalStreamChatLocalizations.delegates, + supportedLocales: const [ + Locale('en', 'US'), + Locale('hi'), + ], + home: Container(), + ), + ); await tester.binding.setLocale('hi', ''); await tester.pump(); @@ -76,8 +84,7 @@ void main() { /// A localizations delegate that does not contain any useful data, and is only /// used to trigger didChangeDependencies upon locale change. -class _DummyLocalizationsDelegate - extends LocalizationsDelegate { +class _DummyLocalizationsDelegate extends LocalizationsDelegate { const _DummyLocalizationsDelegate(); @override diff --git a/packages/stream_chat_localizations/test/override_test.dart b/packages/stream_chat_localizations/test/override_test.dart index 6b775de167..7714fa5f5a 100644 --- a/packages/stream_chat_localizations/test/override_test.dart +++ b/packages/stream_chat_localizations/test/override_test.dart @@ -16,8 +16,7 @@ class FooStreamChatLocalizations extends StreamChatLocalizationsEn { final String launchUrlError; } -class FooStreamChatLocalizationsDelegate - extends LocalizationsDelegate { +class FooStreamChatLocalizationsDelegate extends LocalizationsDelegate { const FooStreamChatLocalizationsDelegate({ this.supportedLanguage = 'en', this.launchUrlError = 'foo', @@ -27,15 +26,12 @@ class FooStreamChatLocalizationsDelegate final String launchUrlError; @override - bool isSupported(Locale locale) => - supportedLanguage == 'allLanguages' || - locale.languageCode == supportedLanguage; + bool isSupported(Locale locale) => supportedLanguage == 'allLanguages' || locale.languageCode == supportedLanguage; @override - Future load(Locale locale) => - SynchronousFuture( - FooStreamChatLocalizations(locale, launchUrlError), - ); + Future load(Locale locale) => SynchronousFuture( + FooStreamChatLocalizations(locale, launchUrlError), + ); @override bool shouldReload(FooStreamChatLocalizationsDelegate old) => false; @@ -43,24 +39,22 @@ class FooStreamChatLocalizationsDelegate Widget buildFrame({ Locale? locale, - Iterable delegates = - GlobalStreamChatLocalizations.delegates, + Iterable delegates = GlobalStreamChatLocalizations.delegates, required WidgetBuilder buildContent, LocaleResolutionCallback? localeResolutionCallback, Iterable supportedLocales = const [ Locale('en', 'US'), Locale('hi', 'IN'), ], -}) => - MaterialApp( - color: const Color(0xFFFFFFFF), - locale: locale, - supportedLocales: supportedLocales, - localizationsDelegates: delegates, - localeResolutionCallback: localeResolutionCallback, - onGenerateRoute: (RouteSettings settings) => MaterialPageRoute( - builder: (BuildContext context) => buildContent(context)), - ); +}) => MaterialApp( + color: const Color(0xFFFFFFFF), + locale: locale, + supportedLocales: supportedLocales, + localizationsDelegates: delegates, + localeResolutionCallback: localeResolutionCallback, + onGenerateRoute: (RouteSettings settings) => + MaterialPageRoute(builder: (BuildContext context) => buildContent(context)), +); void main() { testWidgets( @@ -104,23 +98,23 @@ void main() { "Localizations.override widget tracks parent's locale", (WidgetTester tester) async { Widget buildLocaleFrame(Locale locale) => buildFrame( - locale: locale, - supportedLocales: [locale], - buildContent: (BuildContext context) => Localizations.override( - context: context, - child: Builder( - builder: (BuildContext context) { - // No StreamChatLocalizations are defined for the first - // Localizations ancestor, so we should get the values from - // the default one, i.e. the one created by WidgetsApp via - // the LocalizationsDelegate provided by MaterialApp. - return Text( - StreamChatLocalizations.of(context)!.launchUrlError, - ); - }, - ), - ), - ); + locale: locale, + supportedLocales: [locale], + buildContent: (BuildContext context) => Localizations.override( + context: context, + child: Builder( + builder: (BuildContext context) { + // No StreamChatLocalizations are defined for the first + // Localizations ancestor, so we should get the values from + // the default one, i.e. the one created by WidgetsApp via + // the LocalizationsDelegate provided by MaterialApp. + return Text( + StreamChatLocalizations.of(context)!.launchUrlError, + ); + }, + ), + ), + ); await tester.pumpWidget(buildLocaleFrame(const Locale('en', 'US'))); expect(find.text('Cannot launch the url'), findsOneWidget); @@ -130,28 +124,27 @@ void main() { }, ); - testWidgets('Localizations.override widget with hardwired locale', - (WidgetTester tester) async { + testWidgets('Localizations.override widget with hardwired locale', (WidgetTester tester) async { Widget buildLocaleFrame(Locale locale) => buildFrame( - locale: locale, - buildContent: (BuildContext context) { - return Localizations.override( - context: context, - locale: const Locale('en', 'US'), - child: Builder( - builder: (BuildContext context) { - // No StreamChatLocalizations are defined for the first - // Localizations ancestor, so we should get the values from - // the default one, i.e. the one created by WidgetsApp via - // the LocalizationsDelegate provided by MaterialApp. - return Text( - StreamChatLocalizations.of(context)!.launchUrlError, - ); - }, - ), - ); - }, + locale: locale, + buildContent: (BuildContext context) { + return Localizations.override( + context: context, + locale: const Locale('en', 'US'), + child: Builder( + builder: (BuildContext context) { + // No StreamChatLocalizations are defined for the first + // Localizations ancestor, so we should get the values from + // the default one, i.e. the one created by WidgetsApp via + // the LocalizationsDelegate provided by MaterialApp. + return Text( + StreamChatLocalizations.of(context)!.launchUrlError, + ); + }, + ), ); + }, + ); await tester.pumpWidget(buildLocaleFrame(const Locale('en', 'US'))); expect(find.text('Cannot launch the url'), findsOneWidget); @@ -165,30 +158,32 @@ void main() { (WidgetTester tester) async { final Key textKey = UniqueKey(); - await tester.pumpWidget(buildFrame( - delegates: [ - ...GlobalStreamChatLocalizations.delegates, - const FooStreamChatLocalizationsDelegate( - supportedLanguage: 'fr', - launchUrlError: "Impossible de lancer l'url", - ), - const FooStreamChatLocalizationsDelegate( - supportedLanguage: 'uz', - launchUrlError: 'test', + await tester.pumpWidget( + buildFrame( + delegates: [ + ...GlobalStreamChatLocalizations.delegates, + const FooStreamChatLocalizationsDelegate( + supportedLanguage: 'fr', + launchUrlError: "Impossible de lancer l'url", + ), + const FooStreamChatLocalizationsDelegate( + supportedLanguage: 'uz', + launchUrlError: 'test', + ), + ], + supportedLocales: const [ + Locale('en'), + Locale('hi'), + Locale('fr'), + Locale('de'), + Locale('uz'), + ], + buildContent: (BuildContext context) => Text( + StreamChatLocalizations.of(context)!.launchUrlError, + key: textKey, ), - ], - supportedLocales: const [ - Locale('en'), - Locale('hi'), - Locale('fr'), - Locale('de'), - Locale('uz'), - ], - buildContent: (BuildContext context) => Text( - StreamChatLocalizations.of(context)!.launchUrlError, - key: textKey, ), - )); + ); expect( tester.widget(find.byKey(textKey)).data, @@ -214,24 +209,25 @@ void main() { (WidgetTester tester) async { final Key textKey = UniqueKey(); - await tester.pumpWidget(buildFrame( - // Accept whatever locale we're given - localeResolutionCallback: - (Locale? locale, Iterable supportedLocales) => locale, - delegates: [ - const FooStreamChatLocalizationsDelegate( - supportedLanguage: 'allLanguages', - ), - ...GlobalStreamChatLocalizations.delegates, - ], - buildContent: (BuildContext context) { - // Should always be 'foo', no matter what the locale is - return Text( - StreamChatLocalizations.of(context)!.launchUrlError, - key: textKey, - ); - }, - )); + await tester.pumpWidget( + buildFrame( + // Accept whatever locale we're given + localeResolutionCallback: (Locale? locale, Iterable supportedLocales) => locale, + delegates: [ + const FooStreamChatLocalizationsDelegate( + supportedLanguage: 'allLanguages', + ), + ...GlobalStreamChatLocalizations.delegates, + ], + buildContent: (BuildContext context) { + // Should always be 'foo', no matter what the locale is + return Text( + StreamChatLocalizations.of(context)!.launchUrlError, + key: textKey, + ); + }, + ), + ); expect(tester.widget(find.byKey(textKey)).data, 'foo'); @@ -250,16 +246,18 @@ void main() { (WidgetTester tester) async { final Key textKey = UniqueKey(); - await tester.pumpWidget(buildFrame( - delegates: [ - const FooStreamChatLocalizationsDelegate(), - ], - // supportedLocales not specified, so all locales resolve to 'en' - buildContent: (BuildContext context) => Text( - StreamChatLocalizations.of(context)!.launchUrlError, - key: textKey, + await tester.pumpWidget( + buildFrame( + delegates: [ + const FooStreamChatLocalizationsDelegate(), + ], + // supportedLocales not specified, so all locales resolve to 'en' + buildContent: (BuildContext context) => Text( + StreamChatLocalizations.of(context)!.launchUrlError, + key: textKey, + ), ), - )); + ); // Unsupported locale '_' (the widget tester's default) resolves to 'en'. expect(tester.widget(find.byKey(textKey)).data, 'foo'); diff --git a/packages/stream_chat_localizations/test/translations_test.dart b/packages/stream_chat_localizations/test/translations_test.dart index 7abac606d3..0c56092af8 100644 --- a/packages/stream_chat_localizations/test/translations_test.dart +++ b/packages/stream_chat_localizations/test/translations_test.dart @@ -7,10 +7,8 @@ void main() { for (final language in kStreamChatSupportedLanguages) { test('translations exist for $language', () async { final locale = Locale(language); - expect( - GlobalStreamChatLocalizations.delegate.isSupported(locale), isTrue); - final localizations = - await GlobalStreamChatLocalizations.delegate.load(locale); + expect(GlobalStreamChatLocalizations.delegate.isSupported(locale), isTrue); + final localizations = await GlobalStreamChatLocalizations.delegate.load(locale); expect(localizations.launchUrlError, isNotNull); expect(localizations.loadingUsersError, isNotNull); expect(localizations.noUsersLabel, isNotNull); @@ -194,18 +192,15 @@ void main() { expect(localizations.couldNotReadBytesFromFileError, isNotNull); expect(localizations.toggleMuteUnmuteAction(isMuted: false), isNotNull); expect(localizations.downloadLabel, isNotNull); - expect(localizations.toggleMuteUnmuteGroupQuestion(isMuted: true), - isNotNull); + expect(localizations.toggleMuteUnmuteGroupQuestion(isMuted: true), isNotNull); expect(localizations.toggleMuteUnmuteGroupText(isMuted: true), isNotNull); - expect( - localizations.toggleMuteUnmuteUserQuestion(isMuted: true), isNotNull); + expect(localizations.toggleMuteUnmuteUserQuestion(isMuted: true), isNotNull); expect(localizations.toggleMuteUnmuteUserText(isMuted: true), isNotNull); expect(localizations.viewLibrary, isNotNull); expect(localizations.unreadMessagesSeparatorText(), isNotNull); expect(localizations.enableFileAccessMessage, isNotNull); expect(localizations.allowFileAccessMessage, isNotNull); - expect( - localizations.unreadCountIndicatorLabel(unreadCount: 2), isNotNull); + expect(localizations.unreadCountIndicatorLabel(unreadCount: 2), isNotNull); expect(localizations.unreadMessagesSeparatorText(), isNotNull); expect(localizations.markUnreadError, isNotNull); expect(localizations.markAsUnreadLabel, isNotNull); diff --git a/packages/stream_chat_persistence/example/lib/main.dart b/packages/stream_chat_persistence/example/lib/main.dart index 6e71a555da..f82143fbbf 100644 --- a/packages/stream_chat_persistence/example/lib/main.dart +++ b/packages/stream_chat_persistence/example/lib/main.dart @@ -22,8 +22,7 @@ Future main() async { await client.connectUser( User( id: 'cool-shadow-7', - image: - 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', + image: 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', ), 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.' 'gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo', @@ -95,31 +94,32 @@ class HomeScreen extends StatelessWidget { body: SafeArea( child: StreamBuilder( stream: messages, - builder: ( - BuildContext context, - AsyncSnapshot snapshot, - ) { - if (snapshot.hasData && snapshot.data != null) { - final _messages = snapshot.data!.messages ?? []; - return MessageView( - messages: _messages.reversed.toList(), - channel: channel, - ); - } else if (snapshot.hasError) { - return const Center( - child: Text( - 'There was an error loading messages. Please see logs.', - ), - ); - } - return const Center( - child: SizedBox( - width: 100, - height: 100, - child: CircularProgressIndicator(), - ), - ); - }, + builder: + ( + BuildContext context, + AsyncSnapshot snapshot, + ) { + if (snapshot.hasData && snapshot.data != null) { + final _messages = snapshot.data!.messages ?? []; + return MessageView( + messages: _messages.reversed.toList(), + channel: channel, + ); + } else if (snapshot.hasError) { + return const Center( + child: Text( + 'There was an error loading messages. Please see logs.', + ), + ); + } + return const Center( + child: SizedBox( + width: 100, + height: 100, + child: CircularProgressIndicator(), + ), + ); + }, ), ), ); diff --git a/packages/stream_chat_persistence/example/pubspec.yaml b/packages/stream_chat_persistence/example/pubspec.yaml index 05bb0e7956..e9b5296d15 100644 --- a/packages/stream_chat_persistence/example/pubspec.yaml +++ b/packages/stream_chat_persistence/example/pubspec.yaml @@ -16,8 +16,8 @@ version: 1.0.0+1 # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: cupertino_icons: ^1.0.3 diff --git a/packages/stream_chat_persistence/lib/src/converter/voting_visibility_converter.dart b/packages/stream_chat_persistence/lib/src/converter/voting_visibility_converter.dart index 2d5786d558..047be71a46 100644 --- a/packages/stream_chat_persistence/lib/src/converter/voting_visibility_converter.dart +++ b/packages/stream_chat_persistence/lib/src/converter/voting_visibility_converter.dart @@ -2,8 +2,7 @@ import 'package:drift/drift.dart'; import 'package:stream_chat/stream_chat.dart'; /// A [TypeConverter] that serializes [VotingVisibility] to a [String] column. -class VotingVisibilityConverter - extends TypeConverter { +class VotingVisibilityConverter extends TypeConverter { /// Constant default constructor. const VotingVisibilityConverter(); diff --git a/packages/stream_chat_persistence/lib/src/dao/channel_dao.dart b/packages/stream_chat_persistence/lib/src/dao/channel_dao.dart index f3a3de415a..88754425c6 100644 --- a/packages/stream_chat_persistence/lib/src/dao/channel_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/channel_dao.dart @@ -9,20 +9,21 @@ part 'channel_dao.g.dart'; /// The Data Access Object for operations in [Channels] table. @DriftAccessor(tables: [Channels, Users]) -class ChannelDao extends DatabaseAccessor - with _$ChannelDaoMixin { +class ChannelDao extends DatabaseAccessor with _$ChannelDaoMixin { /// Creates a new channel dao instance ChannelDao(super.db); /// Get channel by cid - Future getChannelByCid(String cid) async => - (select(channels)..where((c) => c.cid.equals(cid))).join([ + Future getChannelByCid(String cid) async => (select(channels)..where((c) => c.cid.equals(cid))) + .join([ leftOuterJoin(users, channels.createdById.equalsExp(users.id)), - ]).map((rows) { + ]) + .map((rows) { final channel = rows.readTable(channels); final createdBy = rows.readTableOrNull(users); return channel.toChannelModel(createdBy: createdBy?.toUser()); - }).getSingleOrNull(); + }) + .getSingleOrNull(); /// Delete all channels by matching cid in [cids] /// @@ -34,17 +35,18 @@ class ChannelDao extends DatabaseAccessor (delete(channels)..where((tbl) => tbl.cid.isIn(cids))).go(); /// Get the channel cids saved in the storage - Future> get cids => (select(channels) - ..orderBy([(c) => OrderingTerm.desc(c.lastMessageAt)]) - ..limit(250)) - .map((c) => c.cid) - .get(); + Future> get cids => + (select(channels) + ..orderBy([(c) => OrderingTerm.desc(c.lastMessageAt)]) + ..limit(250)) + .map((c) => c.cid) + .get(); /// Updates all the channels using the new [channelList] data Future updateChannels(List channelList) => batch( - (it) => it.insertAllOnConflictUpdate( - channels, - channelList.map((c) => c.toEntity()).toList(), - ), - ); + (it) => it.insertAllOnConflictUpdate( + channels, + channelList.map((c) => c.toEntity()).toList(), + ), + ); } diff --git a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart index b0931ee3be..a8f0d922f8 100644 --- a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart @@ -12,8 +12,7 @@ part 'channel_query_dao.g.dart'; /// The Data Access Object for operations in [ChannelQueries] table. @DriftAccessor(tables: [ChannelQueries, Channels, Users]) -class ChannelQueryDao extends DatabaseAccessor - with _$ChannelQueryDaoMixin { +class ChannelQueryDao extends DatabaseAccessor with _$ChannelQueryDaoMixin { /// Creates a new channel query dao instance ChannelQueryDao(super.db); @@ -32,35 +31,29 @@ class ChannelQueryDao extends DatabaseAccessor Filter? filter, List cids, { bool clearQueryCache = false, - }) async => - transaction(() async { - final hash = _computeHash(filter); - if (clearQueryCache) { - await batch((it) { - it.deleteWhere( - channelQueries, - (c) => c.queryHash.equals(hash), - ); - }); - } - - await batch((it) { - it.insertAllOnConflictUpdate( - channelQueries, - cids - .map((cid) => - ChannelQueryEntity(queryHash: hash, channelCid: cid)) - .toList(), - ); - }); + }) async => transaction(() async { + final hash = _computeHash(filter); + if (clearQueryCache) { + await batch((it) { + it.deleteWhere( + channelQueries, + (c) => c.queryHash.equals(hash), + ); }); + } + + await batch((it) { + it.insertAllOnConflictUpdate( + channelQueries, + cids.map((cid) => ChannelQueryEntity(queryHash: hash, channelCid: cid)).toList(), + ); + }); + }); /// Future> getCachedChannelCids(Filter? filter) { final hash = _computeHash(filter); - return (select(channelQueries)..where((c) => c.queryHash.equals(hash))) - .map((c) => c.channelCid) - .get(); + return (select(channelQueries)..where((c) => c.queryHash.equals(hash))).map((c) => c.channelCid).get(); } /// Get list of channels by filter, sort and paginationParams @@ -68,13 +61,16 @@ class ChannelQueryDao extends DatabaseAccessor final cachedChannelCids = await getCachedChannelCids(filter); final query = select(channels)..where((c) => c.cid.isIn(cachedChannelCids)); - final cachedChannels = await query.join([ - leftOuterJoin(users, channels.createdById.equalsExp(users.id)), - ]).map((row) { - final createdByEntity = row.readTableOrNull(users); - final channelEntity = row.readTable(channels); - return channelEntity.toChannelModel(createdBy: createdByEntity?.toUser()); - }).get(); + final cachedChannels = await query + .join([ + leftOuterJoin(users, channels.createdById.equalsExp(users.id)), + ]) + .map((row) { + final createdByEntity = row.readTableOrNull(users); + final channelEntity = row.readTable(channels); + return channelEntity.toChannelModel(createdBy: createdByEntity?.toUser()); + }) + .get(); return cachedChannels; } diff --git a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart index fa7ff9829d..976d5931f0 100644 --- a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart @@ -9,37 +9,32 @@ part 'connection_event_dao.g.dart'; /// The Data Access Object for operations in [ConnectionEvents] table. @DriftAccessor(tables: [ConnectionEvents]) -class ConnectionEventDao extends DatabaseAccessor - with _$ConnectionEventDaoMixin { +class ConnectionEventDao extends DatabaseAccessor with _$ConnectionEventDaoMixin { /// Creates a new connection event dao instance ConnectionEventDao(super.db); /// Get the latest stored connection event - Future get connectionEvent => select(connectionEvents) - .map((eventEntity) => eventEntity.toEvent()) - .getSingleOrNull(); + Future get connectionEvent => + select(connectionEvents).map((eventEntity) => eventEntity.toEvent()).getSingleOrNull(); /// Get the latest stored lastSyncAt - Future get lastSyncAt => - select(connectionEvents).getSingleOrNull().then((r) => r?.lastSyncAt); + Future get lastSyncAt => select(connectionEvents).getSingleOrNull().then((r) => r?.lastSyncAt); /// Update stored connection event with latest data Future updateConnectionEvent(Event event) => transaction(() async { - final connectionInfo = await select(connectionEvents).getSingleOrNull(); - return into(connectionEvents).insertOnConflictUpdate( - ConnectionEventEntity( - id: 1, - type: event.type, - lastSyncAt: connectionInfo?.lastSyncAt, - lastEventAt: event.createdAt, - totalUnreadCount: - event.totalUnreadCount ?? connectionInfo?.totalUnreadCount, - ownUser: event.me?.toJson() ?? connectionInfo?.ownUser, - unreadChannels: - event.unreadChannels ?? connectionInfo?.unreadChannels, - ), - ); - }); + final connectionInfo = await select(connectionEvents).getSingleOrNull(); + return into(connectionEvents).insertOnConflictUpdate( + ConnectionEventEntity( + id: 1, + type: event.type, + lastSyncAt: connectionInfo?.lastSyncAt, + lastEventAt: event.createdAt, + totalUnreadCount: event.totalUnreadCount ?? connectionInfo?.totalUnreadCount, + ownUser: event.me?.toJson() ?? connectionInfo?.ownUser, + unreadChannels: event.unreadChannels ?? connectionInfo?.unreadChannels, + ), + ); + }); /// Update stored lastSyncAt with latest data Future updateLastSyncAt(DateTime lastSyncAt) async => diff --git a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.g.dart b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.g.dart index 32541de5d5..10a23aa21a 100644 --- a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.g.dart +++ b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.g.dart @@ -4,6 +4,5 @@ part of 'connection_event_dao.dart'; // ignore_for_file: type=lint mixin _$ConnectionEventDaoMixin on DatabaseAccessor { - $ConnectionEventsTable get connectionEvents => - attachedDatabase.connectionEvents; + $ConnectionEventsTable get connectionEvents => attachedDatabase.connectionEvents; } diff --git a/packages/stream_chat_persistence/lib/src/dao/draft_message_dao.dart b/packages/stream_chat_persistence/lib/src/dao/draft_message_dao.dart index 5065b3bda4..a0602a0679 100644 --- a/packages/stream_chat_persistence/lib/src/dao/draft_message_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/draft_message_dao.dart @@ -10,8 +10,7 @@ part 'draft_message_dao.g.dart'; /// The Data Access Object for operations in [DraftMessages] table. @DriftAccessor(tables: [DraftMessages, Messages]) -class DraftMessageDao extends DatabaseAccessor - with _$DraftMessageDaoMixin { +class DraftMessageDao extends DatabaseAccessor with _$DraftMessageDaoMixin { /// Creates a new draft message dao instance DraftMessageDao(this._db) : super(_db); @@ -26,19 +25,19 @@ class DraftMessageDao extends DatabaseAccessor final parentMessage = await switch (entity.parentId) { final id? => _db.messageDao.getMessageById( - id, - fetchDraft: fetchDraft, - fetchSharedLocation: fetchSharedLocation, - ), + id, + fetchDraft: fetchDraft, + fetchSharedLocation: fetchSharedLocation, + ), _ => null, }; final quotedMessage = await switch (entity.quotedMessageId) { final id? => _db.messageDao.getMessageById( - id, - fetchDraft: fetchDraft, - fetchSharedLocation: fetchSharedLocation, - ), + id, + fetchDraft: fetchDraft, + fetchSharedLocation: fetchSharedLocation, + ), _ => null, }; diff --git a/packages/stream_chat_persistence/lib/src/dao/location_dao.dart b/packages/stream_chat_persistence/lib/src/dao/location_dao.dart index 6e12421179..f6d6bb6cfa 100644 --- a/packages/stream_chat_persistence/lib/src/dao/location_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/location_dao.dart @@ -10,8 +10,7 @@ part 'location_dao.g.dart'; /// The Data Access Object for operations in [Locations] table. @DriftAccessor(tables: [Locations]) -class LocationDao extends DatabaseAccessor - with _$LocationDaoMixin { +class LocationDao extends DatabaseAccessor with _$LocationDaoMixin { /// Creates a new location dao instance LocationDao(this._db) : super(_db); @@ -31,10 +30,10 @@ class LocationDao extends DatabaseAccessor final message = await switch (entity.messageId) { final id? => _db.messageDao.getMessageById( - id, - fetchDraft: fetchDraft, - fetchSharedLocation: fetchSharedLocation, - ), + id, + fetchDraft: fetchDraft, + fetchSharedLocation: fetchSharedLocation, + ), _ => null, }; @@ -54,8 +53,9 @@ class LocationDao extends DatabaseAccessor /// Get location by message ID Future getLocationByMessageId(String messageId) async { - final query = select(locations) // - ..where((tbl) => tbl.messageId.equals(messageId)); + final query = + select(locations) // + ..where((tbl) => tbl.messageId.equals(messageId)); final result = await query.getSingleOrNull(); if (result == null) return null; @@ -74,8 +74,7 @@ class LocationDao extends DatabaseAccessor } /// Delete locations by channel ID - Future deleteLocationsByCid(String cid) => - (delete(locations)..where((tbl) => tbl.channelCid.equals(cid))).go(); + Future deleteLocationsByCid(String cid) => (delete(locations)..where((tbl) => tbl.channelCid.equals(cid))).go(); /// Delete locations by message IDs Future deleteLocationsByMessageIds(List messageIds) => diff --git a/packages/stream_chat_persistence/lib/src/dao/member_dao.dart b/packages/stream_chat_persistence/lib/src/dao/member_dao.dart index fb345d3bff..7d61ecb4cc 100644 --- a/packages/stream_chat_persistence/lib/src/dao/member_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/member_dao.dart @@ -9,38 +9,39 @@ part 'member_dao.g.dart'; /// The Data Access Object for operations in [Members] table. @DriftAccessor(tables: [Members, Users]) -class MemberDao extends DatabaseAccessor - with _$MemberDaoMixin { +class MemberDao extends DatabaseAccessor with _$MemberDaoMixin { /// Creates a new member dao instance MemberDao(super.db); /// Get all members where [Members.channelCid] matches [cid] Future> getMembersByCid(String cid) async => (select(members).join([ - leftOuterJoin(users, members.userId.equalsExp(users.id)), - ]) + leftOuterJoin(users, members.userId.equalsExp(users.id)), + ]) ..where(members.channelCid.equals(cid)) ..orderBy([OrderingTerm.asc(members.createdAt)])) .map((row) { - final userEntity = row.readTable(users); - final memberEntity = row.readTable(members); - return memberEntity.toMember(user: userEntity.toUser()); - }).get(); + final userEntity = row.readTable(users); + final memberEntity = row.readTable(members); + return memberEntity.toMember(user: userEntity.toUser()); + }) + .get(); /// Updates all the members using the new [memberList] data - Future updateMembers(String cid, List memberList) => - bulkUpdateMembers({cid: memberList}); + Future updateMembers(String cid, List memberList) => bulkUpdateMembers({cid: memberList}); /// Bulk updates the members data of multiple channels Future bulkUpdateMembers( Map?> channelWithMembers, ) { final entities = channelWithMembers.entries - .map((entry) => - entry.value?.map( - (member) => member.toEntity(cid: entry.key), - ) ?? - []) + .map( + (entry) => + entry.value?.map( + (member) => member.toEntity(cid: entry.key), + ) ?? + [], + ) .expand((it) => it) .toList(growable: false); return batch( @@ -50,9 +51,9 @@ class MemberDao extends DatabaseAccessor /// Deletes all the members whose [Members.channelCid] is present in [cids] Future deleteMemberByCids(List cids) async => batch((it) { - it.deleteWhere( - members, - (m) => m.channelCid.isIn(cids), - ); - }); + it.deleteWhere( + members, + (m) => m.channelCid.isIn(cids), + ); + }); } diff --git a/packages/stream_chat_persistence/lib/src/dao/message_dao.dart b/packages/stream_chat_persistence/lib/src/dao/message_dao.dart index 53e15c48f1..86b8f7275c 100644 --- a/packages/stream_chat_persistence/lib/src/dao/message_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/message_dao.dart @@ -12,8 +12,7 @@ part 'message_dao.g.dart'; /// The Data Access Object for operations in [Messages] table. @DriftAccessor(tables: [Messages, Users]) -class MessageDao extends DatabaseAccessor - with _$MessageDaoMixin { +class MessageDao extends DatabaseAccessor with _$MessageDaoMixin { /// Creates a new message dao instance MessageDao(this._db) : super(_db); @@ -63,9 +62,9 @@ class MessageDao extends DatabaseAccessor final draft = await switch (fetchDraft) { true => _db.draftMessageDao.getDraftMessageByCid( - msgEntity.channelCid, - parentId: msgEntity.id, - ), + msgEntity.channelCid, + parentId: msgEntity.id, + ), _ => null, }; @@ -101,8 +100,7 @@ class MessageDao extends DatabaseAccessor _pinnedByUsers, messages.pinnedByUserId.equalsExp(_pinnedByUsers.id), ), - ]) - ..where(messages.id.equals(id)); + ])..where(messages.id.equals(id)); final result = await query.getSingleOrNull(); if (result == null) return null; @@ -116,19 +114,20 @@ class MessageDao extends DatabaseAccessor /// Returns all the messages of a particular thread by matching /// [Messages.channelCid] with [cid] - Future> getThreadMessages(String cid) async => - Future.wait(await (select(messages).join([ - leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), - leftOuterJoin( - _pinnedByUsers, - messages.pinnedByUserId.equalsExp(_pinnedByUsers.id), - ), - ]) - ..where(messages.channelCid.equals(cid)) - ..where(messages.parentId.isNotNull()) - ..orderBy([OrderingTerm.asc(messages.createdAt)])) - .map(_messageFromJoinRow) - .get()); + Future> getThreadMessages(String cid) async => Future.wait( + await (select(messages).join([ + leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), + leftOuterJoin( + _pinnedByUsers, + messages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), + ]) + ..where(messages.channelCid.equals(cid)) + ..where(messages.parentId.isNotNull()) + ..orderBy([OrderingTerm.asc(messages.createdAt)])) + .map(_messageFromJoinRow) + .get(), + ); /// Returns all the messages of a particular thread by matching /// [Messages.parentId] with [parentId] @@ -136,18 +135,20 @@ class MessageDao extends DatabaseAccessor String parentId, { PaginationParams? options, }) async { - final msgList = await Future.wait(await (select(messages).join([ - leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), - leftOuterJoin( - _pinnedByUsers, - messages.pinnedByUserId.equalsExp(_pinnedByUsers.id), - ), - ]) - ..where(messages.parentId.isNotNull()) - ..where(messages.parentId.equals(parentId)) - ..orderBy([OrderingTerm.asc(messages.createdAt)])) - .map(_messageFromJoinRow) - .get()); + final msgList = await Future.wait( + await (select(messages).join([ + leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), + leftOuterJoin( + _pinnedByUsers, + messages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), + ]) + ..where(messages.parentId.isNotNull()) + ..where(messages.parentId.equals(parentId)) + ..orderBy([OrderingTerm.asc(messages.createdAt)])) + .map(_messageFromJoinRow) + .get(), + ); if (msgList.isNotEmpty) { if (options?.lessThan != null) { @@ -182,16 +183,17 @@ class MessageDao extends DatabaseAccessor bool fetchSharedLocation = true, PaginationParams? messagePagination, }) async { - final query = select(messages).join([ - leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), - leftOuterJoin( - _pinnedByUsers, - messages.pinnedByUserId.equalsExp(_pinnedByUsers.id), - ), - ]) - ..where(messages.channelCid.equals(cid)) - ..where(messages.parentId.isNull() | messages.showInChannel.equals(true)) - ..orderBy([OrderingTerm.asc(messages.createdAt)]); + final query = + select(messages).join([ + leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), + leftOuterJoin( + _pinnedByUsers, + messages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), + ]) + ..where(messages.channelCid.equals(cid)) + ..where(messages.parentId.isNull() | messages.showInChannel.equals(true)) + ..orderBy([OrderingTerm.asc(messages.createdAt)]); final result = await query.get(); if (result.isEmpty) return []; @@ -224,9 +226,7 @@ class MessageDao extends DatabaseAccessor } } if (messagePagination?.limit != null) { - return msgList - .skip(max(0, msgList.length - messagePagination!.limit)) - .toList(); + return msgList.skip(max(0, msgList.length - messagePagination!.limit)).toList(); } } return msgList; @@ -253,8 +253,7 @@ class MessageDao extends DatabaseAccessor }) async { if (hardDelete) { // Hard delete: remove from database - final deleteQuery = delete(messages) - ..where((tbl) => tbl.userId.equals(userId)); + final deleteQuery = delete(messages)..where((tbl) => tbl.userId.equals(userId)); if (cid != null) { deleteQuery.where((tbl) => tbl.channelCid.equals(cid)); @@ -264,8 +263,7 @@ class MessageDao extends DatabaseAccessor } // Soft delete: update messages to mark as deleted - final updateQuery = update(messages) - ..where((tbl) => tbl.userId.equals(userId)); + final updateQuery = update(messages)..where((tbl) => tbl.userId.equals(userId)); if (cid != null) { updateQuery.where((tbl) => tbl.channelCid.equals(cid)); @@ -282,19 +280,20 @@ class MessageDao extends DatabaseAccessor /// Updates the message data of a particular channel with /// the new [messageList] data - Future updateMessages(String cid, List messageList) => - bulkUpdateMessages({cid: messageList}); + Future updateMessages(String cid, List messageList) => bulkUpdateMessages({cid: messageList}); /// Bulk updates the message data of multiple channels Future bulkUpdateMessages( Map?> channelWithMessages, ) { final entities = channelWithMessages.entries - .map((entry) => - entry.value?.map( - (message) => message.toEntity(cid: entry.key), - ) ?? - []) + .map( + (entry) => + entry.value?.map( + (message) => message.toEntity(cid: entry.key), + ) ?? + [], + ) .expand((it) => it) .toList(growable: false); return batch( diff --git a/packages/stream_chat_persistence/lib/src/dao/pinned_message_dao.dart b/packages/stream_chat_persistence/lib/src/dao/pinned_message_dao.dart index afd0c18c33..413c7164f4 100644 --- a/packages/stream_chat_persistence/lib/src/dao/pinned_message_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/pinned_message_dao.dart @@ -12,8 +12,7 @@ part 'pinned_message_dao.g.dart'; /// The Data Access Object for operations in [Messages] table. @DriftAccessor(tables: [PinnedMessages, Users]) -class PinnedMessageDao extends DatabaseAccessor - with _$PinnedMessageDaoMixin { +class PinnedMessageDao extends DatabaseAccessor with _$PinnedMessageDaoMixin { /// Creates a new message dao instance PinnedMessageDao(this._db) : super(_db); @@ -45,10 +44,8 @@ class PinnedMessageDao extends DatabaseAccessor final userEntity = rows.readTableOrNull(_users); final pinnedByEntity = rows.readTableOrNull(_pinnedByUsers); final msgEntity = rows.readTable(pinnedMessages); - final latestReactions = - await _db.pinnedMessageReactionDao.getReactions(msgEntity.id); - final ownReactions = - await _db.pinnedMessageReactionDao.getReactionsByUserId( + final latestReactions = await _db.pinnedMessageReactionDao.getReactions(msgEntity.id); + final ownReactions = await _db.pinnedMessageReactionDao.getReactionsByUserId( msgEntity.id, _db.userId, ); @@ -65,9 +62,9 @@ class PinnedMessageDao extends DatabaseAccessor final draft = await switch (fetchDraft) { true => _db.draftMessageDao.getDraftMessageByCid( - msgEntity.channelCid, - parentId: msgEntity.id, - ), + msgEntity.channelCid, + parentId: msgEntity.id, + ), _ => null, }; @@ -100,8 +97,7 @@ class PinnedMessageDao extends DatabaseAccessor _pinnedByUsers, pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id), ), - ]) - ..where(pinnedMessages.id.equals(id)); + ])..where(pinnedMessages.id.equals(id)); final result = await query.getSingleOrNull(); if (result == null) return null; @@ -115,19 +111,20 @@ class PinnedMessageDao extends DatabaseAccessor /// Returns all the messages of a particular thread by matching /// [PinnedMessages.channelCid] with [cid] - Future> getThreadMessages(String cid) async => - Future.wait(await (select(pinnedMessages).join([ - leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), - leftOuterJoin( - _pinnedByUsers, - pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id), - ), - ]) - ..where(pinnedMessages.channelCid.equals(cid)) - ..where(pinnedMessages.parentId.isNotNull()) - ..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)])) - .map(_messageFromJoinRow) - .get()); + Future> getThreadMessages(String cid) async => Future.wait( + await (select(pinnedMessages).join([ + leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), + leftOuterJoin( + _pinnedByUsers, + pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), + ]) + ..where(pinnedMessages.channelCid.equals(cid)) + ..where(pinnedMessages.parentId.isNotNull()) + ..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)])) + .map(_messageFromJoinRow) + .get(), + ); /// Returns all the messages of a particular thread by matching /// [PinnedMessages.parentId] with [parentId] @@ -135,18 +132,20 @@ class PinnedMessageDao extends DatabaseAccessor String parentId, { PaginationParams? options, }) async { - final msgList = await Future.wait(await (select(pinnedMessages).join([ - leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), - leftOuterJoin( - _pinnedByUsers, - pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id), - ), - ]) - ..where(pinnedMessages.parentId.isNotNull()) - ..where(pinnedMessages.parentId.equals(parentId)) - ..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)])) - .map(_messageFromJoinRow) - .get()); + final msgList = await Future.wait( + await (select(pinnedMessages).join([ + leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), + leftOuterJoin( + _pinnedByUsers, + pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), + ]) + ..where(pinnedMessages.parentId.isNotNull()) + ..where(pinnedMessages.parentId.equals(parentId)) + ..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)])) + .map(_messageFromJoinRow) + .get(), + ); if (msgList.isNotEmpty) { if (options?.lessThan != null) { @@ -180,17 +179,17 @@ class PinnedMessageDao extends DatabaseAccessor bool fetchSharedLocation = true, PaginationParams? messagePagination, }) async { - final query = select(pinnedMessages).join([ - leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), - leftOuterJoin( - _pinnedByUsers, - pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id), - ), - ]) - ..where(pinnedMessages.channelCid.equals(cid)) - ..where(pinnedMessages.parentId.isNull() | - pinnedMessages.showInChannel.equals(true)) - ..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)]); + final query = + select(pinnedMessages).join([ + leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), + leftOuterJoin( + _pinnedByUsers, + pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), + ]) + ..where(pinnedMessages.channelCid.equals(cid)) + ..where(pinnedMessages.parentId.isNull() | pinnedMessages.showInChannel.equals(true)) + ..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)]); final result = await query.get(); if (result.isEmpty) return []; @@ -250,8 +249,7 @@ class PinnedMessageDao extends DatabaseAccessor }) async { if (hardDelete) { // Hard delete: remove from database - final deleteQuery = delete(pinnedMessages) - ..where((tbl) => tbl.userId.equals(userId)); + final deleteQuery = delete(pinnedMessages)..where((tbl) => tbl.userId.equals(userId)); if (cid != null) { deleteQuery.where((tbl) => tbl.channelCid.equals(cid)); @@ -261,8 +259,7 @@ class PinnedMessageDao extends DatabaseAccessor } // Soft delete: update messages to mark as deleted - final updateQuery = update(pinnedMessages) - ..where((tbl) => tbl.userId.equals(userId)); + final updateQuery = update(pinnedMessages)..where((tbl) => tbl.userId.equals(userId)); if (cid != null) { updateQuery.where((tbl) => tbl.channelCid.equals(cid)); @@ -279,19 +276,20 @@ class PinnedMessageDao extends DatabaseAccessor /// Updates the message data of a particular channel with /// the new [messageList] data - Future updateMessages(String cid, List messageList) => - bulkUpdateMessages({cid: messageList}); + Future updateMessages(String cid, List messageList) => bulkUpdateMessages({cid: messageList}); /// Bulk updates the message data of multiple channels Future bulkUpdateMessages( Map?> channelWithMessages, ) { final entities = channelWithMessages.entries - .map((entry) => - entry.value?.map( - (message) => message.toPinnedEntity(cid: entry.key), - ) ?? - []) + .map( + (entry) => + entry.value?.map( + (message) => message.toPinnedEntity(cid: entry.key), + ) ?? + [], + ) .expand((it) => it) .toList(growable: false); return batch( diff --git a/packages/stream_chat_persistence/lib/src/dao/pinned_message_reaction_dao.dart b/packages/stream_chat_persistence/lib/src/dao/pinned_message_reaction_dao.dart index c5c5a6d456..9a81c6e46e 100644 --- a/packages/stream_chat_persistence/lib/src/dao/pinned_message_reaction_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/pinned_message_reaction_dao.dart @@ -9,8 +9,7 @@ part 'pinned_message_reaction_dao.g.dart'; /// The Data Access Object for operations in [PinnedMessageReactions] table. @DriftAccessor(tables: [PinnedMessageReactions, Users]) -class PinnedMessageReactionDao extends DatabaseAccessor - with _$PinnedMessageReactionDaoMixin { +class PinnedMessageReactionDao extends DatabaseAccessor with _$PinnedMessageReactionDaoMixin { /// Creates a new reaction dao instance PinnedMessageReactionDao(super.db); @@ -18,15 +17,16 @@ class PinnedMessageReactionDao extends DatabaseAccessor /// [Reactions.messageId] with [messageId] Future> getReactions(String messageId) => (select(pinnedMessageReactions).join([ - leftOuterJoin(users, pinnedMessageReactions.userId.equalsExp(users.id)), - ]) + leftOuterJoin(users, pinnedMessageReactions.userId.equalsExp(users.id)), + ]) ..where(pinnedMessageReactions.messageId.equals(messageId)) ..orderBy([OrderingTerm.asc(pinnedMessageReactions.createdAt)])) .map((rows) { - final userEntity = rows.readTableOrNull(users); - final reactionEntity = rows.readTable(pinnedMessageReactions); - return reactionEntity.toReaction(user: userEntity?.toUser()); - }).get(); + final userEntity = rows.readTableOrNull(users); + final reactionEntity = rows.readTable(pinnedMessageReactions); + return reactionEntity.toReaction(user: userEntity?.toUser()); + }) + .get(); /// Returns all the reactions of a particular message /// added by a particular user by matching @@ -42,19 +42,18 @@ class PinnedMessageReactionDao extends DatabaseAccessor /// Updates the reactions data with the new [reactionList] data Future updateReactions(List reactionList) => batch((it) { - it.insertAllOnConflictUpdate( - pinnedMessageReactions, - reactionList.map((r) => r.toPinnedEntity()).toList(), - ); - }); + it.insertAllOnConflictUpdate( + pinnedMessageReactions, + reactionList.map((r) => r.toPinnedEntity()).toList(), + ); + }); /// Deletes all the reactions whose [Reactions.messageId] is /// present in [messageIds] - Future deleteReactionsByMessageIds(List messageIds) => - batch((it) { - it.deleteWhere( - pinnedMessageReactions, - (r) => r.messageId.isIn(messageIds), - ); - }); + Future deleteReactionsByMessageIds(List messageIds) => batch((it) { + it.deleteWhere( + pinnedMessageReactions, + (r) => r.messageId.isIn(messageIds), + ); + }); } diff --git a/packages/stream_chat_persistence/lib/src/dao/pinned_message_reaction_dao.g.dart b/packages/stream_chat_persistence/lib/src/dao/pinned_message_reaction_dao.g.dart index d25c2f4512..0b7393b68b 100644 --- a/packages/stream_chat_persistence/lib/src/dao/pinned_message_reaction_dao.g.dart +++ b/packages/stream_chat_persistence/lib/src/dao/pinned_message_reaction_dao.g.dart @@ -5,7 +5,6 @@ part of 'pinned_message_reaction_dao.dart'; // ignore_for_file: type=lint mixin _$PinnedMessageReactionDaoMixin on DatabaseAccessor { $PinnedMessagesTable get pinnedMessages => attachedDatabase.pinnedMessages; - $PinnedMessageReactionsTable get pinnedMessageReactions => - attachedDatabase.pinnedMessageReactions; + $PinnedMessageReactionsTable get pinnedMessageReactions => attachedDatabase.pinnedMessageReactions; $UsersTable get users => attachedDatabase.users; } diff --git a/packages/stream_chat_persistence/lib/src/dao/poll_dao.dart b/packages/stream_chat_persistence/lib/src/dao/poll_dao.dart index 10a10a024d..43924285e7 100644 --- a/packages/stream_chat_persistence/lib/src/dao/poll_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/poll_dao.dart @@ -45,28 +45,27 @@ class PollDao extends DatabaseAccessor with _$PollDaoMixin { } /// Returns the poll by matching [Polls.id] with [pollId] - Future getPollById(String pollId) async => - await (select(polls)..where((it) => it.id.equals(pollId))) - .join([leftOuterJoin(users, polls.createdById.equalsExp(users.id))]) - .map(_pollFromJoinRow) - .getSingleOrNull(); + Future getPollById(String pollId) async => await (select(polls)..where((it) => it.id.equals(pollId))) + .join([leftOuterJoin(users, polls.createdById.equalsExp(users.id))]) + .map(_pollFromJoinRow) + .getSingleOrNull(); /// Updates all the polls using the new [pollList] data Future updatePolls(List pollList) => batch( - (it) => it.insertAllOnConflictUpdate( - polls, - pollList.map((it) => it.toEntity()), - ), - ); + (it) => it.insertAllOnConflictUpdate( + polls, + pollList.map((it) => it.toEntity()), + ), + ); /// Returns the list of all the polls stored in db - Future> getPolls() async => Future.wait(await (select(polls) - ..orderBy([(it) => OrderingTerm.desc(it.createdAt)])) - .join([leftOuterJoin(users, polls.createdById.equalsExp(users.id))]) - .map(_pollFromJoinRow) - .get()); + Future> getPolls() async => Future.wait( + await (select(polls)..orderBy([(it) => OrderingTerm.desc(it.createdAt)])) + .join([leftOuterJoin(users, polls.createdById.equalsExp(users.id))]) + .map(_pollFromJoinRow) + .get(), + ); /// Deletes all the polls whose [Polls.id] is present in [pollIds] - Future deletePollsByIds(List pollIds) => - (delete(polls)..where((tbl) => tbl.id.isIn(pollIds))).go(); + Future deletePollsByIds(List pollIds) => (delete(polls)..where((tbl) => tbl.id.isIn(pollIds))).go(); } diff --git a/packages/stream_chat_persistence/lib/src/dao/poll_vote_dao.dart b/packages/stream_chat_persistence/lib/src/dao/poll_vote_dao.dart index 55884f4a70..6fb765d8e3 100644 --- a/packages/stream_chat_persistence/lib/src/dao/poll_vote_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/poll_vote_dao.dart @@ -11,8 +11,7 @@ part 'poll_vote_dao.g.dart'; /// The Data Access Object for operations in [Polls] table. @DriftAccessor(tables: [PollVotes, Users]) -class PollVoteDao extends DatabaseAccessor - with _$PollVoteDaoMixin { +class PollVoteDao extends DatabaseAccessor with _$PollVoteDaoMixin { /// Creates a new poll vote dao instance PollVoteDao(super.db); @@ -20,23 +19,24 @@ class PollVoteDao extends DatabaseAccessor /// [Reactions.messageId] with [messageId] Future> getPollVotes(String pollId) => (select(pollVotes).join([ - leftOuterJoin(users, pollVotes.userId.equalsExp(users.id)), - ]) + leftOuterJoin(users, pollVotes.userId.equalsExp(users.id)), + ]) ..where(pollVotes.pollId.equals(pollId)) ..orderBy([OrderingTerm.asc(pollVotes.createdAt)])) .map((rows) { - final userEntity = rows.readTableOrNull(users); - final pollVoteEntity = rows.readTable(pollVotes); - return pollVoteEntity.toPollVote(user: userEntity?.toUser()); - }).get(); + final userEntity = rows.readTableOrNull(users); + final pollVoteEntity = rows.readTable(pollVotes); + return pollVoteEntity.toPollVote(user: userEntity?.toUser()); + }) + .get(); /// Updates the poll votes data with the new [pollVoteList] data Future updatePollVotes(List pollVoteList) => batch( - (it) => it.insertAllOnConflictUpdate( - pollVotes, - pollVoteList.map((it) => it.toEntity()), - ), - ); + (it) => it.insertAllOnConflictUpdate( + pollVotes, + pollVoteList.map((it) => it.toEntity()), + ), + ); /// Deletes all the poll votes whose [PollVote.pollId] is /// present in [pollIds] diff --git a/packages/stream_chat_persistence/lib/src/dao/reaction_dao.dart b/packages/stream_chat_persistence/lib/src/dao/reaction_dao.dart index d6dae9bd99..2a68626159 100644 --- a/packages/stream_chat_persistence/lib/src/dao/reaction_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/reaction_dao.dart @@ -9,8 +9,7 @@ part 'reaction_dao.g.dart'; /// The Data Access Object for operations in [Reactions] table. @DriftAccessor(tables: [Reactions, Users]) -class ReactionDao extends DatabaseAccessor - with _$ReactionDaoMixin { +class ReactionDao extends DatabaseAccessor with _$ReactionDaoMixin { /// Creates a new reaction dao instance ReactionDao(super.db); @@ -18,15 +17,16 @@ class ReactionDao extends DatabaseAccessor /// [Reactions.messageId] with [messageId] Future> getReactions(String messageId) => (select(reactions).join([ - leftOuterJoin(users, reactions.userId.equalsExp(users.id)), - ]) + leftOuterJoin(users, reactions.userId.equalsExp(users.id)), + ]) ..where(reactions.messageId.equals(messageId)) ..orderBy([OrderingTerm.asc(reactions.createdAt)])) .map((rows) { - final userEntity = rows.readTableOrNull(users); - final reactionEntity = rows.readTable(reactions); - return reactionEntity.toReaction(user: userEntity?.toUser()); - }).get(); + final userEntity = rows.readTableOrNull(users); + final reactionEntity = rows.readTable(reactions); + return reactionEntity.toReaction(user: userEntity?.toUser()); + }) + .get(); /// Returns all the reactions of a particular message /// added by a particular user by matching @@ -42,19 +42,18 @@ class ReactionDao extends DatabaseAccessor /// Updates the reactions data with the new [reactionList] data Future updateReactions(List reactionList) => batch((it) { - it.insertAllOnConflictUpdate( - reactions, - reactionList.map((r) => r.toEntity()).toList(), - ); - }); + it.insertAllOnConflictUpdate( + reactions, + reactionList.map((r) => r.toEntity()).toList(), + ); + }); /// Deletes all the reactions whose [Reactions.messageId] is /// present in [messageIds] - Future deleteReactionsByMessageIds(List messageIds) => - batch((it) { - it.deleteWhere( - reactions, - (r) => r.messageId.isIn(messageIds), - ); - }); + Future deleteReactionsByMessageIds(List messageIds) => batch((it) { + it.deleteWhere( + reactions, + (r) => r.messageId.isIn(messageIds), + ); + }); } diff --git a/packages/stream_chat_persistence/lib/src/dao/read_dao.dart b/packages/stream_chat_persistence/lib/src/dao/read_dao.dart index 4e2024f1ff..c6beec0f9f 100644 --- a/packages/stream_chat_persistence/lib/src/dao/read_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/read_dao.dart @@ -14,32 +14,35 @@ class ReadDao extends DatabaseAccessor with _$ReadDaoMixin { ReadDao(super.db); /// Get all reads where [Reads.channelCid] matches [cid] - Future> getReadsByCid(String cid) async => (select(reads).join([ - leftOuterJoin(users, reads.userId.equalsExp(users.id)), - ]) + Future> getReadsByCid(String cid) async => + (select(reads).join([ + leftOuterJoin(users, reads.userId.equalsExp(users.id)), + ]) ..where(reads.channelCid.equals(cid)) ..orderBy([ OrderingTerm.asc(reads.lastRead), ])) .map((row) { - final userEntity = row.readTable(users); - final readEntity = row.readTable(reads); - return readEntity.toRead(user: userEntity.toUser()); - }).get(); + final userEntity = row.readTable(users); + final readEntity = row.readTable(reads); + return readEntity.toRead(user: userEntity.toUser()); + }) + .get(); /// Updates the read data of a particular channel with /// the new [readList] data - Future updateReads(String cid, List readList) => - bulkUpdateReads({cid: readList}); + Future updateReads(String cid, List readList) => bulkUpdateReads({cid: readList}); /// Bulk updates the reads data of multiple channels Future bulkUpdateReads(Map?> channelWithReads) { final entities = channelWithReads.entries - .map((entry) => - entry.value?.map( - (read) => read.toEntity(cid: entry.key), - ) ?? - []) + .map( + (entry) => + entry.value?.map( + (read) => read.toEntity(cid: entry.key), + ) ?? + [], + ) .expand((it) => it) .toList(growable: false); return batch((batch) => batch.insertAllOnConflictUpdate(reads, entities)); diff --git a/packages/stream_chat_persistence/lib/src/dao/user_dao.dart b/packages/stream_chat_persistence/lib/src/dao/user_dao.dart index 1366dd2541..214425d882 100644 --- a/packages/stream_chat_persistence/lib/src/dao/user_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/user_dao.dart @@ -14,15 +14,13 @@ class UserDao extends DatabaseAccessor with _$UserDaoMixin { /// Updates the users data with the new [userList] data Future updateUsers(List userList) => batch( - (it) => it.insertAllOnConflictUpdate( - users, - userList.map((u) => u.toEntity()).toList(), - ), - ); + (it) => it.insertAllOnConflictUpdate( + users, + userList.map((u) => u.toEntity()).toList(), + ), + ); /// Returns the list of all the users stored in db Future> getUsers() => - (select(users)..orderBy([(u) => OrderingTerm.desc(u.createdAt)])) - .map((it) => it.toUser()) - .get(); + (select(users)..orderBy([(u) => OrderingTerm.desc(u.createdAt)])).map((it) => it.toUser()).get(); } diff --git a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart index ab0a5c3ff5..3fc6eb552d 100644 --- a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart +++ b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart @@ -61,18 +61,18 @@ class DriftChatDatabase extends _$DriftChatDatabase { @override MigrationStrategy get migration => MigrationStrategy( - beforeOpen: (details) async { - await customStatement('PRAGMA foreign_keys = ON'); - }, - onUpgrade: (migrator, from, to) async { - if (from != to) { - for (final table in allTables) { - await migrator.deleteTable(table.actualTableName); - } - await migrator.createAll(); - } - }, - ); + beforeOpen: (details) async { + await customStatement('PRAGMA foreign_keys = ON'); + }, + onUpgrade: (migrator, from, to) async { + if (from != to) { + for (final table in allTables) { + await migrator.deleteTable(table.actualTableName); + } + await migrator.createAll(); + } + }, + ); /// Deletes all the tables Future flush() async { diff --git a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart index 59e1bd5ef9..899609385e 100644 --- a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart +++ b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart @@ -3,8 +3,7 @@ part of 'drift_chat_database.dart'; // ignore_for_file: type=lint -class $ChannelsTable extends Channels - with TableInfo<$ChannelsTable, ChannelEntity> { +class $ChannelsTable extends Channels with TableInfo<$ChannelsTable, ChannelEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; @@ -12,124 +11,164 @@ class $ChannelsTable extends Channels static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); static const VerificationMeta _typeMeta = const VerificationMeta('type'); @override late final GeneratedColumn type = GeneratedColumn( - 'type', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); static const VerificationMeta _cidMeta = const VerificationMeta('cid'); @override late final GeneratedColumn cid = GeneratedColumn( - 'cid', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - @override - late final GeneratedColumnWithTypeConverter?, String> - ownCapabilities = GeneratedColumn( - 'own_capabilities', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $ChannelsTable.$converterownCapabilitiesn); - @override - late final GeneratedColumnWithTypeConverter, String> - config = GeneratedColumn('config', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>($ChannelsTable.$converterconfig); + 'cid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + late final GeneratedColumnWithTypeConverter?, String> ownCapabilities = GeneratedColumn( + 'own_capabilities', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($ChannelsTable.$converterownCapabilitiesn); + @override + late final GeneratedColumnWithTypeConverter, String> config = GeneratedColumn( + 'config', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($ChannelsTable.$converterconfig); static const VerificationMeta _frozenMeta = const VerificationMeta('frozen'); @override late final GeneratedColumn frozen = GeneratedColumn( - 'frozen', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("frozen" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _lastMessageAtMeta = - const VerificationMeta('lastMessageAt'); - @override - late final GeneratedColumn lastMessageAt = - GeneratedColumn('last_message_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _createdAtMeta = - const VerificationMeta('createdAt'); + 'frozen', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("frozen" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _lastMessageAtMeta = const VerificationMeta('lastMessageAt'); + @override + late final GeneratedColumn lastMessageAt = GeneratedColumn( + 'last_message_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - static const VerificationMeta _updatedAtMeta = - const VerificationMeta('updatedAt'); + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); @override late final GeneratedColumn updatedAt = GeneratedColumn( - 'updated_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - static const VerificationMeta _deletedAtMeta = - const VerificationMeta('deletedAt'); + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _deletedAtMeta = const VerificationMeta('deletedAt'); @override late final GeneratedColumn deletedAt = GeneratedColumn( - 'deleted_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _memberCountMeta = - const VerificationMeta('memberCount'); + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _memberCountMeta = const VerificationMeta('memberCount'); @override late final GeneratedColumn memberCount = GeneratedColumn( - 'member_count', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(0)); - static const VerificationMeta _messageCountMeta = - const VerificationMeta('messageCount'); + 'member_count', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0), + ); + static const VerificationMeta _messageCountMeta = const VerificationMeta('messageCount'); @override late final GeneratedColumn messageCount = GeneratedColumn( - 'message_count', aliasedName, true, - type: DriftSqlType.int, requiredDuringInsert: false); - static const VerificationMeta _createdByIdMeta = - const VerificationMeta('createdById'); + 'message_count', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdByIdMeta = const VerificationMeta('createdById'); @override late final GeneratedColumn createdById = GeneratedColumn( - 'created_by_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - @override - late final GeneratedColumnWithTypeConverter?, String> - filterTags = GeneratedColumn('filter_tags', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>($ChannelsTable.$converterfilterTagsn); - @override - late final GeneratedColumnWithTypeConverter?, String> - extraData = GeneratedColumn('extra_data', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $ChannelsTable.$converterextraDatan); + 'created_by_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + late final GeneratedColumnWithTypeConverter?, String> filterTags = GeneratedColumn( + 'filter_tags', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($ChannelsTable.$converterfilterTagsn); + @override + late final GeneratedColumnWithTypeConverter?, String> extraData = GeneratedColumn( + 'extra_data', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($ChannelsTable.$converterextraDatan); @override List get $columns => [ - id, - type, - cid, - ownCapabilities, - config, - frozen, - lastMessageAt, - createdAt, - updatedAt, - deletedAt, - memberCount, - messageCount, - createdById, - filterTags, - extraData - ]; + id, + type, + cid, + ownCapabilities, + config, + frozen, + lastMessageAt, + createdAt, + updatedAt, + deletedAt, + memberCount, + messageCount, + createdById, + filterTags, + extraData, + ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'channels'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { @@ -138,56 +177,41 @@ class $ChannelsTable extends Channels context.missing(_idMeta); } if (data.containsKey('type')) { - context.handle( - _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + context.handle(_typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); } else if (isInserting) { context.missing(_typeMeta); } if (data.containsKey('cid')) { - context.handle( - _cidMeta, cid.isAcceptableOrUnknown(data['cid']!, _cidMeta)); + context.handle(_cidMeta, cid.isAcceptableOrUnknown(data['cid']!, _cidMeta)); } else if (isInserting) { context.missing(_cidMeta); } if (data.containsKey('frozen')) { - context.handle(_frozenMeta, - frozen.isAcceptableOrUnknown(data['frozen']!, _frozenMeta)); + context.handle(_frozenMeta, frozen.isAcceptableOrUnknown(data['frozen']!, _frozenMeta)); } if (data.containsKey('last_message_at')) { context.handle( - _lastMessageAtMeta, - lastMessageAt.isAcceptableOrUnknown( - data['last_message_at']!, _lastMessageAtMeta)); + _lastMessageAtMeta, + lastMessageAt.isAcceptableOrUnknown(data['last_message_at']!, _lastMessageAtMeta), + ); } if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { - context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } if (data.containsKey('deleted_at')) { - context.handle(_deletedAtMeta, - deletedAt.isAcceptableOrUnknown(data['deleted_at']!, _deletedAtMeta)); + context.handle(_deletedAtMeta, deletedAt.isAcceptableOrUnknown(data['deleted_at']!, _deletedAtMeta)); } if (data.containsKey('member_count')) { - context.handle( - _memberCountMeta, - memberCount.isAcceptableOrUnknown( - data['member_count']!, _memberCountMeta)); + context.handle(_memberCountMeta, memberCount.isAcceptableOrUnknown(data['member_count']!, _memberCountMeta)); } if (data.containsKey('message_count')) { - context.handle( - _messageCountMeta, - messageCount.isAcceptableOrUnknown( - data['message_count']!, _messageCountMeta)); + context.handle(_messageCountMeta, messageCount.isAcceptableOrUnknown(data['message_count']!, _messageCountMeta)); } if (data.containsKey('created_by_id')) { - context.handle( - _createdByIdMeta, - createdById.isAcceptableOrUnknown( - data['created_by_id']!, _createdByIdMeta)); + context.handle(_createdByIdMeta, createdById.isAcceptableOrUnknown(data['created_by_id']!, _createdByIdMeta)); } return context; } @@ -198,40 +222,32 @@ class $ChannelsTable extends Channels ChannelEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return ChannelEntity( - id: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}id'])!, - type: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}type'])!, - cid: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}cid'])!, + id: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}id'])!, + type: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}type'])!, + cid: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}cid'])!, ownCapabilities: $ChannelsTable.$converterownCapabilitiesn.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}own_capabilities'])), - config: $ChannelsTable.$converterconfig.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}config'])!), - frozen: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}frozen'])!, + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}own_capabilities']), + ), + config: $ChannelsTable.$converterconfig.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}config'])!, + ), + frozen: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}frozen'])!, lastMessageAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}last_message_at']), - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, - updatedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, - deletedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}deleted_at']), - memberCount: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}member_count'])!, - messageCount: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}message_count']), - createdById: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}created_by_id']), - filterTags: $ChannelsTable.$converterfilterTagsn.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}filter_tags'])), - extraData: $ChannelsTable.$converterextraDatan.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])), + DriftSqlType.dateTime, + data['${effectivePrefix}last_message_at'], + ), + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + deletedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}deleted_at']), + memberCount: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}member_count'])!, + messageCount: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}message_count']), + createdById: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}created_by_id']), + filterTags: $ChannelsTable.$converterfilterTagsn.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}filter_tags']), + ), + extraData: $ChannelsTable.$converterextraDatan.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}extra_data']), + ), ); } @@ -240,20 +256,19 @@ class $ChannelsTable extends Channels return $ChannelsTable(attachedDatabase, alias); } - static TypeConverter, String> $converterownCapabilities = - ListConverter(); - static TypeConverter?, String?> $converterownCapabilitiesn = - NullAwareTypeConverter.wrap($converterownCapabilities); - static TypeConverter, String> $converterconfig = - MapConverter(); - static TypeConverter, String> $converterfilterTags = - ListConverter(); - static TypeConverter?, String?> $converterfilterTagsn = - NullAwareTypeConverter.wrap($converterfilterTags); - static TypeConverter, String> $converterextraData = - MapConverter(); - static TypeConverter?, String?> $converterextraDatan = - NullAwareTypeConverter.wrap($converterextraData); + static TypeConverter, String> $converterownCapabilities = ListConverter(); + static TypeConverter?, String?> $converterownCapabilitiesn = NullAwareTypeConverter.wrap( + $converterownCapabilities, + ); + static TypeConverter, String> $converterconfig = MapConverter(); + static TypeConverter, String> $converterfilterTags = ListConverter(); + static TypeConverter?, String?> $converterfilterTagsn = NullAwareTypeConverter.wrap( + $converterfilterTags, + ); + static TypeConverter, String> $converterextraData = MapConverter(); + static TypeConverter?, String?> $converterextraDatan = NullAwareTypeConverter.wrap( + $converterextraData, + ); } class ChannelEntity extends DataClass implements Insertable { @@ -301,22 +316,23 @@ class ChannelEntity extends DataClass implements Insertable { /// Map of custom channel extraData final Map? extraData; - const ChannelEntity( - {required this.id, - required this.type, - required this.cid, - this.ownCapabilities, - required this.config, - required this.frozen, - this.lastMessageAt, - required this.createdAt, - required this.updatedAt, - this.deletedAt, - required this.memberCount, - this.messageCount, - this.createdById, - this.filterTags, - this.extraData}); + const ChannelEntity({ + required this.id, + required this.type, + required this.cid, + this.ownCapabilities, + required this.config, + required this.frozen, + this.lastMessageAt, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.memberCount, + this.messageCount, + this.createdById, + this.filterTags, + this.extraData, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -324,12 +340,10 @@ class ChannelEntity extends DataClass implements Insertable { map['type'] = Variable(type); map['cid'] = Variable(cid); if (!nullToAbsent || ownCapabilities != null) { - map['own_capabilities'] = Variable( - $ChannelsTable.$converterownCapabilitiesn.toSql(ownCapabilities)); + map['own_capabilities'] = Variable($ChannelsTable.$converterownCapabilitiesn.toSql(ownCapabilities)); } { - map['config'] = - Variable($ChannelsTable.$converterconfig.toSql(config)); + map['config'] = Variable($ChannelsTable.$converterconfig.toSql(config)); } map['frozen'] = Variable(frozen); if (!nullToAbsent || lastMessageAt != null) { @@ -348,25 +362,21 @@ class ChannelEntity extends DataClass implements Insertable { map['created_by_id'] = Variable(createdById); } if (!nullToAbsent || filterTags != null) { - map['filter_tags'] = Variable( - $ChannelsTable.$converterfilterTagsn.toSql(filterTags)); + map['filter_tags'] = Variable($ChannelsTable.$converterfilterTagsn.toSql(filterTags)); } if (!nullToAbsent || extraData != null) { - map['extra_data'] = Variable( - $ChannelsTable.$converterextraDatan.toSql(extraData)); + map['extra_data'] = Variable($ChannelsTable.$converterextraDatan.toSql(extraData)); } return map; } - factory ChannelEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory ChannelEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return ChannelEntity( id: serializer.fromJson(json['id']), type: serializer.fromJson(json['type']), cid: serializer.fromJson(json['cid']), - ownCapabilities: - serializer.fromJson?>(json['ownCapabilities']), + ownCapabilities: serializer.fromJson?>(json['ownCapabilities']), config: serializer.fromJson>(json['config']), frozen: serializer.fromJson(json['frozen']), lastMessageAt: serializer.fromJson(json['lastMessageAt']), @@ -402,68 +412,55 @@ class ChannelEntity extends DataClass implements Insertable { }; } - ChannelEntity copyWith( - {String? id, - String? type, - String? cid, - Value?> ownCapabilities = const Value.absent(), - Map? config, - bool? frozen, - Value lastMessageAt = const Value.absent(), - DateTime? createdAt, - DateTime? updatedAt, - Value deletedAt = const Value.absent(), - int? memberCount, - Value messageCount = const Value.absent(), - Value createdById = const Value.absent(), - Value?> filterTags = const Value.absent(), - Value?> extraData = const Value.absent()}) => - ChannelEntity( - id: id ?? this.id, - type: type ?? this.type, - cid: cid ?? this.cid, - ownCapabilities: ownCapabilities.present - ? ownCapabilities.value - : this.ownCapabilities, - config: config ?? this.config, - frozen: frozen ?? this.frozen, - lastMessageAt: - lastMessageAt.present ? lastMessageAt.value : this.lastMessageAt, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, - memberCount: memberCount ?? this.memberCount, - messageCount: - messageCount.present ? messageCount.value : this.messageCount, - createdById: createdById.present ? createdById.value : this.createdById, - filterTags: filterTags.present ? filterTags.value : this.filterTags, - extraData: extraData.present ? extraData.value : this.extraData, - ); + ChannelEntity copyWith({ + String? id, + String? type, + String? cid, + Value?> ownCapabilities = const Value.absent(), + Map? config, + bool? frozen, + Value lastMessageAt = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + int? memberCount, + Value messageCount = const Value.absent(), + Value createdById = const Value.absent(), + Value?> filterTags = const Value.absent(), + Value?> extraData = const Value.absent(), + }) => ChannelEntity( + id: id ?? this.id, + type: type ?? this.type, + cid: cid ?? this.cid, + ownCapabilities: ownCapabilities.present ? ownCapabilities.value : this.ownCapabilities, + config: config ?? this.config, + frozen: frozen ?? this.frozen, + lastMessageAt: lastMessageAt.present ? lastMessageAt.value : this.lastMessageAt, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + memberCount: memberCount ?? this.memberCount, + messageCount: messageCount.present ? messageCount.value : this.messageCount, + createdById: createdById.present ? createdById.value : this.createdById, + filterTags: filterTags.present ? filterTags.value : this.filterTags, + extraData: extraData.present ? extraData.value : this.extraData, + ); ChannelEntity copyWithCompanion(ChannelsCompanion data) { return ChannelEntity( id: data.id.present ? data.id.value : this.id, type: data.type.present ? data.type.value : this.type, cid: data.cid.present ? data.cid.value : this.cid, - ownCapabilities: data.ownCapabilities.present - ? data.ownCapabilities.value - : this.ownCapabilities, + ownCapabilities: data.ownCapabilities.present ? data.ownCapabilities.value : this.ownCapabilities, config: data.config.present ? data.config.value : this.config, frozen: data.frozen.present ? data.frozen.value : this.frozen, - lastMessageAt: data.lastMessageAt.present - ? data.lastMessageAt.value - : this.lastMessageAt, + lastMessageAt: data.lastMessageAt.present ? data.lastMessageAt.value : this.lastMessageAt, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, - memberCount: - data.memberCount.present ? data.memberCount.value : this.memberCount, - messageCount: data.messageCount.present - ? data.messageCount.value - : this.messageCount, - createdById: - data.createdById.present ? data.createdById.value : this.createdById, - filterTags: - data.filterTags.present ? data.filterTags.value : this.filterTags, + memberCount: data.memberCount.present ? data.memberCount.value : this.memberCount, + messageCount: data.messageCount.present ? data.messageCount.value : this.messageCount, + createdById: data.createdById.present ? data.createdById.value : this.createdById, + filterTags: data.filterTags.present ? data.filterTags.value : this.filterTags, extraData: data.extraData.present ? data.extraData.value : this.extraData, ); } @@ -492,21 +489,22 @@ class ChannelEntity extends DataClass implements Insertable { @override int get hashCode => Object.hash( - id, - type, - cid, - ownCapabilities, - config, - frozen, - lastMessageAt, - createdAt, - updatedAt, - deletedAt, - memberCount, - messageCount, - createdById, - filterTags, - extraData); + id, + type, + cid, + ownCapabilities, + config, + frozen, + lastMessageAt, + createdAt, + updatedAt, + deletedAt, + memberCount, + messageCount, + createdById, + filterTags, + extraData, + ); @override bool operator ==(Object other) => identical(this, other) || @@ -580,10 +578,10 @@ class ChannelsCompanion extends UpdateCompanion { this.filterTags = const Value.absent(), this.extraData = const Value.absent(), this.rowid = const Value.absent(), - }) : id = Value(id), - type = Value(type), - cid = Value(cid), - config = Value(config); + }) : id = Value(id), + type = Value(type), + cid = Value(cid), + config = Value(config); static Insertable custom({ Expression? id, Expression? type, @@ -622,23 +620,24 @@ class ChannelsCompanion extends UpdateCompanion { }); } - ChannelsCompanion copyWith( - {Value? id, - Value? type, - Value? cid, - Value?>? ownCapabilities, - Value>? config, - Value? frozen, - Value? lastMessageAt, - Value? createdAt, - Value? updatedAt, - Value? deletedAt, - Value? memberCount, - Value? messageCount, - Value? createdById, - Value?>? filterTags, - Value?>? extraData, - Value? rowid}) { + ChannelsCompanion copyWith({ + Value? id, + Value? type, + Value? cid, + Value?>? ownCapabilities, + Value>? config, + Value? frozen, + Value? lastMessageAt, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? memberCount, + Value? messageCount, + Value? createdById, + Value?>? filterTags, + Value?>? extraData, + Value? rowid, + }) { return ChannelsCompanion( id: id ?? this.id, type: type ?? this.type, @@ -672,13 +671,12 @@ class ChannelsCompanion extends UpdateCompanion { map['cid'] = Variable(cid.value); } if (ownCapabilities.present) { - map['own_capabilities'] = Variable($ChannelsTable - .$converterownCapabilitiesn - .toSql(ownCapabilities.value)); + map['own_capabilities'] = Variable( + $ChannelsTable.$converterownCapabilitiesn.toSql(ownCapabilities.value), + ); } if (config.present) { - map['config'] = - Variable($ChannelsTable.$converterconfig.toSql(config.value)); + map['config'] = Variable($ChannelsTable.$converterconfig.toSql(config.value)); } if (frozen.present) { map['frozen'] = Variable(frozen.value); @@ -705,12 +703,10 @@ class ChannelsCompanion extends UpdateCompanion { map['created_by_id'] = Variable(createdById.value); } if (filterTags.present) { - map['filter_tags'] = Variable( - $ChannelsTable.$converterfilterTagsn.toSql(filterTags.value)); + map['filter_tags'] = Variable($ChannelsTable.$converterfilterTagsn.toSql(filterTags.value)); } if (extraData.present) { - map['extra_data'] = Variable( - $ChannelsTable.$converterextraDatan.toSql(extraData.value)); + map['extra_data'] = Variable($ChannelsTable.$converterextraDatan.toSql(extraData.value)); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -742,8 +738,7 @@ class ChannelsCompanion extends UpdateCompanion { } } -class $MessagesTable extends Messages - with TableInfo<$MessagesTable, MessageEntity> { +class $MessagesTable extends Messages with TableInfo<$MessagesTable, MessageEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; @@ -751,251 +746,336 @@ class $MessagesTable extends Messages static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _messageTextMeta = - const VerificationMeta('messageText'); + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _messageTextMeta = const VerificationMeta('messageText'); @override late final GeneratedColumn messageText = GeneratedColumn( - 'message_text', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - @override - late final GeneratedColumnWithTypeConverter, String> - attachments = GeneratedColumn('attachments', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>($MessagesTable.$converterattachments); + 'message_text', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + late final GeneratedColumnWithTypeConverter, String> attachments = GeneratedColumn( + 'attachments', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($MessagesTable.$converterattachments); static const VerificationMeta _stateMeta = const VerificationMeta('state'); @override late final GeneratedColumn state = GeneratedColumn( - 'state', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + 'state', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); static const VerificationMeta _typeMeta = const VerificationMeta('type'); @override late final GeneratedColumn type = GeneratedColumn( - 'type', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: const Constant('regular')); - @override - late final GeneratedColumnWithTypeConverter, String> - mentionedUsers = GeneratedColumn( - 'mentioned_users', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>($MessagesTable.$convertermentionedUsers); - @override - late final GeneratedColumnWithTypeConverter?, - String> reactionGroups = GeneratedColumn( - 'reaction_groups', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $MessagesTable.$converterreactionGroupsn); - static const VerificationMeta _parentIdMeta = - const VerificationMeta('parentId'); + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('regular'), + ); + @override + late final GeneratedColumnWithTypeConverter, String> mentionedUsers = GeneratedColumn( + 'mentioned_users', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($MessagesTable.$convertermentionedUsers); + @override + late final GeneratedColumnWithTypeConverter?, String> reactionGroups = + GeneratedColumn( + 'reaction_groups', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($MessagesTable.$converterreactionGroupsn); + static const VerificationMeta _parentIdMeta = const VerificationMeta('parentId'); @override late final GeneratedColumn parentId = GeneratedColumn( - 'parent_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _quotedMessageIdMeta = - const VerificationMeta('quotedMessageId'); + 'parent_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _quotedMessageIdMeta = const VerificationMeta('quotedMessageId'); @override late final GeneratedColumn quotedMessageId = GeneratedColumn( - 'quoted_message_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'quoted_message_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); static const VerificationMeta _pollIdMeta = const VerificationMeta('pollId'); @override late final GeneratedColumn pollId = GeneratedColumn( - 'poll_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _replyCountMeta = - const VerificationMeta('replyCount'); + 'poll_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _replyCountMeta = const VerificationMeta('replyCount'); @override late final GeneratedColumn replyCount = GeneratedColumn( - 'reply_count', aliasedName, true, - type: DriftSqlType.int, requiredDuringInsert: false); - static const VerificationMeta _showInChannelMeta = - const VerificationMeta('showInChannel'); + 'reply_count', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + static const VerificationMeta _showInChannelMeta = const VerificationMeta('showInChannel'); @override late final GeneratedColumn showInChannel = GeneratedColumn( - 'show_in_channel', aliasedName, true, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("show_in_channel" IN (0, 1))')); - static const VerificationMeta _shadowedMeta = - const VerificationMeta('shadowed'); + 'show_in_channel', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("show_in_channel" IN (0, 1))'), + ); + static const VerificationMeta _shadowedMeta = const VerificationMeta('shadowed'); @override late final GeneratedColumn shadowed = GeneratedColumn( - 'shadowed', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("shadowed" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _commandMeta = - const VerificationMeta('command'); + 'shadowed', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("shadowed" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _commandMeta = const VerificationMeta('command'); @override late final GeneratedColumn command = GeneratedColumn( - 'command', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _localCreatedAtMeta = - const VerificationMeta('localCreatedAt'); - @override - late final GeneratedColumn localCreatedAt = - GeneratedColumn('local_created_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _remoteCreatedAtMeta = - const VerificationMeta('remoteCreatedAt'); - @override - late final GeneratedColumn remoteCreatedAt = - GeneratedColumn('remote_created_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _localUpdatedAtMeta = - const VerificationMeta('localUpdatedAt'); - @override - late final GeneratedColumn localUpdatedAt = - GeneratedColumn('local_updated_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _remoteUpdatedAtMeta = - const VerificationMeta('remoteUpdatedAt'); - @override - late final GeneratedColumn remoteUpdatedAt = - GeneratedColumn('remote_updated_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _localDeletedAtMeta = - const VerificationMeta('localDeletedAt'); - @override - late final GeneratedColumn localDeletedAt = - GeneratedColumn('local_deleted_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _remoteDeletedAtMeta = - const VerificationMeta('remoteDeletedAt'); - @override - late final GeneratedColumn remoteDeletedAt = - GeneratedColumn('remote_deleted_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _deletedForMeMeta = - const VerificationMeta('deletedForMe'); + 'command', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _localCreatedAtMeta = const VerificationMeta('localCreatedAt'); + @override + late final GeneratedColumn localCreatedAt = GeneratedColumn( + 'local_created_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _remoteCreatedAtMeta = const VerificationMeta('remoteCreatedAt'); + @override + late final GeneratedColumn remoteCreatedAt = GeneratedColumn( + 'remote_created_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _localUpdatedAtMeta = const VerificationMeta('localUpdatedAt'); + @override + late final GeneratedColumn localUpdatedAt = GeneratedColumn( + 'local_updated_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _remoteUpdatedAtMeta = const VerificationMeta('remoteUpdatedAt'); + @override + late final GeneratedColumn remoteUpdatedAt = GeneratedColumn( + 'remote_updated_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _localDeletedAtMeta = const VerificationMeta('localDeletedAt'); + @override + late final GeneratedColumn localDeletedAt = GeneratedColumn( + 'local_deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _remoteDeletedAtMeta = const VerificationMeta('remoteDeletedAt'); + @override + late final GeneratedColumn remoteDeletedAt = GeneratedColumn( + 'remote_deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _deletedForMeMeta = const VerificationMeta('deletedForMe'); @override late final GeneratedColumn deletedForMe = GeneratedColumn( - 'deleted_for_me', aliasedName, true, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("deleted_for_me" IN (0, 1))')); - static const VerificationMeta _messageTextUpdatedAtMeta = - const VerificationMeta('messageTextUpdatedAt'); - @override - late final GeneratedColumn messageTextUpdatedAt = - GeneratedColumn('message_text_updated_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); + 'deleted_for_me', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("deleted_for_me" IN (0, 1))'), + ); + static const VerificationMeta _messageTextUpdatedAtMeta = const VerificationMeta('messageTextUpdatedAt'); + @override + late final GeneratedColumn messageTextUpdatedAt = GeneratedColumn( + 'message_text_updated_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); @override late final GeneratedColumn userId = GeneratedColumn( - 'user_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _channelRoleMeta = - const VerificationMeta('channelRole'); + 'user_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _channelRoleMeta = const VerificationMeta('channelRole'); @override late final GeneratedColumn channelRole = GeneratedColumn( - 'channel_role', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'channel_role', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); static const VerificationMeta _pinnedMeta = const VerificationMeta('pinned'); @override late final GeneratedColumn pinned = GeneratedColumn( - 'pinned', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("pinned" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _pinnedAtMeta = - const VerificationMeta('pinnedAt'); + 'pinned', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("pinned" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _pinnedAtMeta = const VerificationMeta('pinnedAt'); @override late final GeneratedColumn pinnedAt = GeneratedColumn( - 'pinned_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _pinExpiresMeta = - const VerificationMeta('pinExpires'); + 'pinned_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _pinExpiresMeta = const VerificationMeta('pinExpires'); @override late final GeneratedColumn pinExpires = GeneratedColumn( - 'pin_expires', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _pinnedByUserIdMeta = - const VerificationMeta('pinnedByUserId'); + 'pin_expires', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _pinnedByUserIdMeta = const VerificationMeta('pinnedByUserId'); @override late final GeneratedColumn pinnedByUserId = GeneratedColumn( - 'pinned_by_user_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _channelCidMeta = - const VerificationMeta('channelCid'); + 'pinned_by_user_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); @override late final GeneratedColumn channelCid = GeneratedColumn( - 'channel_cid', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES channels (cid) ON DELETE CASCADE')); - @override - late final GeneratedColumnWithTypeConverter?, String> - i18n = GeneratedColumn('i18n', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>($MessagesTable.$converteri18n); - @override - late final GeneratedColumnWithTypeConverter?, String> - restrictedVisibility = GeneratedColumn( - 'restricted_visibility', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $MessagesTable.$converterrestrictedVisibilityn); - @override - late final GeneratedColumnWithTypeConverter?, String> - extraData = GeneratedColumn('extra_data', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $MessagesTable.$converterextraDatan); + 'channel_cid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES channels (cid) ON DELETE CASCADE'), + ); + @override + late final GeneratedColumnWithTypeConverter?, String> i18n = GeneratedColumn( + 'i18n', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($MessagesTable.$converteri18n); + @override + late final GeneratedColumnWithTypeConverter?, String> restrictedVisibility = GeneratedColumn( + 'restricted_visibility', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($MessagesTable.$converterrestrictedVisibilityn); + @override + late final GeneratedColumnWithTypeConverter?, String> extraData = GeneratedColumn( + 'extra_data', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($MessagesTable.$converterextraDatan); @override List get $columns => [ - id, - messageText, - attachments, - state, - type, - mentionedUsers, - reactionGroups, - parentId, - quotedMessageId, - pollId, - replyCount, - showInChannel, - shadowed, - command, - localCreatedAt, - remoteCreatedAt, - localUpdatedAt, - remoteUpdatedAt, - localDeletedAt, - remoteDeletedAt, - deletedForMe, - messageTextUpdatedAt, - userId, - channelRole, - pinned, - pinnedAt, - pinExpires, - pinnedByUserId, - channelCid, - i18n, - restrictedVisibility, - extraData - ]; + id, + messageText, + attachments, + state, + type, + mentionedUsers, + reactionGroups, + parentId, + quotedMessageId, + pollId, + replyCount, + showInChannel, + shadowed, + command, + localCreatedAt, + remoteCreatedAt, + localUpdatedAt, + remoteUpdatedAt, + localDeletedAt, + remoteDeletedAt, + deletedForMe, + messageTextUpdatedAt, + userId, + channelRole, + pinned, + pinnedAt, + pinExpires, + pinnedByUserId, + channelCid, + i18n, + restrictedVisibility, + extraData, + ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'messages'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { @@ -1004,138 +1084,111 @@ class $MessagesTable extends Messages context.missing(_idMeta); } if (data.containsKey('message_text')) { - context.handle( - _messageTextMeta, - messageText.isAcceptableOrUnknown( - data['message_text']!, _messageTextMeta)); + context.handle(_messageTextMeta, messageText.isAcceptableOrUnknown(data['message_text']!, _messageTextMeta)); } if (data.containsKey('state')) { - context.handle( - _stateMeta, state.isAcceptableOrUnknown(data['state']!, _stateMeta)); + context.handle(_stateMeta, state.isAcceptableOrUnknown(data['state']!, _stateMeta)); } else if (isInserting) { context.missing(_stateMeta); } if (data.containsKey('type')) { - context.handle( - _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + context.handle(_typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); } if (data.containsKey('parent_id')) { - context.handle(_parentIdMeta, - parentId.isAcceptableOrUnknown(data['parent_id']!, _parentIdMeta)); + context.handle(_parentIdMeta, parentId.isAcceptableOrUnknown(data['parent_id']!, _parentIdMeta)); } if (data.containsKey('quoted_message_id')) { context.handle( - _quotedMessageIdMeta, - quotedMessageId.isAcceptableOrUnknown( - data['quoted_message_id']!, _quotedMessageIdMeta)); + _quotedMessageIdMeta, + quotedMessageId.isAcceptableOrUnknown(data['quoted_message_id']!, _quotedMessageIdMeta), + ); } if (data.containsKey('poll_id')) { - context.handle(_pollIdMeta, - pollId.isAcceptableOrUnknown(data['poll_id']!, _pollIdMeta)); + context.handle(_pollIdMeta, pollId.isAcceptableOrUnknown(data['poll_id']!, _pollIdMeta)); } if (data.containsKey('reply_count')) { - context.handle( - _replyCountMeta, - replyCount.isAcceptableOrUnknown( - data['reply_count']!, _replyCountMeta)); + context.handle(_replyCountMeta, replyCount.isAcceptableOrUnknown(data['reply_count']!, _replyCountMeta)); } if (data.containsKey('show_in_channel')) { context.handle( - _showInChannelMeta, - showInChannel.isAcceptableOrUnknown( - data['show_in_channel']!, _showInChannelMeta)); + _showInChannelMeta, + showInChannel.isAcceptableOrUnknown(data['show_in_channel']!, _showInChannelMeta), + ); } if (data.containsKey('shadowed')) { - context.handle(_shadowedMeta, - shadowed.isAcceptableOrUnknown(data['shadowed']!, _shadowedMeta)); + context.handle(_shadowedMeta, shadowed.isAcceptableOrUnknown(data['shadowed']!, _shadowedMeta)); } if (data.containsKey('command')) { - context.handle(_commandMeta, - command.isAcceptableOrUnknown(data['command']!, _commandMeta)); + context.handle(_commandMeta, command.isAcceptableOrUnknown(data['command']!, _commandMeta)); } if (data.containsKey('local_created_at')) { context.handle( - _localCreatedAtMeta, - localCreatedAt.isAcceptableOrUnknown( - data['local_created_at']!, _localCreatedAtMeta)); + _localCreatedAtMeta, + localCreatedAt.isAcceptableOrUnknown(data['local_created_at']!, _localCreatedAtMeta), + ); } if (data.containsKey('remote_created_at')) { context.handle( - _remoteCreatedAtMeta, - remoteCreatedAt.isAcceptableOrUnknown( - data['remote_created_at']!, _remoteCreatedAtMeta)); + _remoteCreatedAtMeta, + remoteCreatedAt.isAcceptableOrUnknown(data['remote_created_at']!, _remoteCreatedAtMeta), + ); } if (data.containsKey('local_updated_at')) { context.handle( - _localUpdatedAtMeta, - localUpdatedAt.isAcceptableOrUnknown( - data['local_updated_at']!, _localUpdatedAtMeta)); + _localUpdatedAtMeta, + localUpdatedAt.isAcceptableOrUnknown(data['local_updated_at']!, _localUpdatedAtMeta), + ); } if (data.containsKey('remote_updated_at')) { context.handle( - _remoteUpdatedAtMeta, - remoteUpdatedAt.isAcceptableOrUnknown( - data['remote_updated_at']!, _remoteUpdatedAtMeta)); + _remoteUpdatedAtMeta, + remoteUpdatedAt.isAcceptableOrUnknown(data['remote_updated_at']!, _remoteUpdatedAtMeta), + ); } if (data.containsKey('local_deleted_at')) { context.handle( - _localDeletedAtMeta, - localDeletedAt.isAcceptableOrUnknown( - data['local_deleted_at']!, _localDeletedAtMeta)); + _localDeletedAtMeta, + localDeletedAt.isAcceptableOrUnknown(data['local_deleted_at']!, _localDeletedAtMeta), + ); } if (data.containsKey('remote_deleted_at')) { context.handle( - _remoteDeletedAtMeta, - remoteDeletedAt.isAcceptableOrUnknown( - data['remote_deleted_at']!, _remoteDeletedAtMeta)); + _remoteDeletedAtMeta, + remoteDeletedAt.isAcceptableOrUnknown(data['remote_deleted_at']!, _remoteDeletedAtMeta), + ); } if (data.containsKey('deleted_for_me')) { - context.handle( - _deletedForMeMeta, - deletedForMe.isAcceptableOrUnknown( - data['deleted_for_me']!, _deletedForMeMeta)); + context.handle(_deletedForMeMeta, deletedForMe.isAcceptableOrUnknown(data['deleted_for_me']!, _deletedForMeMeta)); } if (data.containsKey('message_text_updated_at')) { context.handle( - _messageTextUpdatedAtMeta, - messageTextUpdatedAt.isAcceptableOrUnknown( - data['message_text_updated_at']!, _messageTextUpdatedAtMeta)); + _messageTextUpdatedAtMeta, + messageTextUpdatedAt.isAcceptableOrUnknown(data['message_text_updated_at']!, _messageTextUpdatedAtMeta), + ); } if (data.containsKey('user_id')) { - context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + context.handle(_userIdMeta, userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } if (data.containsKey('channel_role')) { - context.handle( - _channelRoleMeta, - channelRole.isAcceptableOrUnknown( - data['channel_role']!, _channelRoleMeta)); + context.handle(_channelRoleMeta, channelRole.isAcceptableOrUnknown(data['channel_role']!, _channelRoleMeta)); } if (data.containsKey('pinned')) { - context.handle(_pinnedMeta, - pinned.isAcceptableOrUnknown(data['pinned']!, _pinnedMeta)); + context.handle(_pinnedMeta, pinned.isAcceptableOrUnknown(data['pinned']!, _pinnedMeta)); } if (data.containsKey('pinned_at')) { - context.handle(_pinnedAtMeta, - pinnedAt.isAcceptableOrUnknown(data['pinned_at']!, _pinnedAtMeta)); + context.handle(_pinnedAtMeta, pinnedAt.isAcceptableOrUnknown(data['pinned_at']!, _pinnedAtMeta)); } if (data.containsKey('pin_expires')) { - context.handle( - _pinExpiresMeta, - pinExpires.isAcceptableOrUnknown( - data['pin_expires']!, _pinExpiresMeta)); + context.handle(_pinExpiresMeta, pinExpires.isAcceptableOrUnknown(data['pin_expires']!, _pinExpiresMeta)); } if (data.containsKey('pinned_by_user_id')) { context.handle( - _pinnedByUserIdMeta, - pinnedByUserId.isAcceptableOrUnknown( - data['pinned_by_user_id']!, _pinnedByUserIdMeta)); + _pinnedByUserIdMeta, + pinnedByUserId.isAcceptableOrUnknown(data['pinned_by_user_id']!, _pinnedByUserIdMeta), + ); } if (data.containsKey('channel_cid')) { - context.handle( - _channelCidMeta, - channelCid.isAcceptableOrUnknown( - data['channel_cid']!, _channelCidMeta)); + context.handle(_channelCidMeta, channelCid.isAcceptableOrUnknown(data['channel_cid']!, _channelCidMeta)); } else if (isInserting) { context.missing(_channelCidMeta); } @@ -1148,76 +1201,77 @@ class $MessagesTable extends Messages MessageEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return MessageEntity( - id: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}id'])!, - messageText: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}message_text']), - attachments: $MessagesTable.$converterattachments.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}attachments'])!), - state: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}state'])!, - type: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}type'])!, + id: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}id'])!, + messageText: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}message_text']), + attachments: $MessagesTable.$converterattachments.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}attachments'])!, + ), + state: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}state'])!, + type: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}type'])!, mentionedUsers: $MessagesTable.$convertermentionedUsers.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}mentioned_users'])!), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}mentioned_users'])!, + ), reactionGroups: $MessagesTable.$converterreactionGroupsn.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}reaction_groups'])), - parentId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}parent_id']), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}reaction_groups']), + ), + parentId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}parent_id']), quotedMessageId: attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}quoted_message_id']), - pollId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}poll_id']), - replyCount: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}reply_count']), - showInChannel: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}show_in_channel']), - shadowed: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}shadowed'])!, - command: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}command']), + DriftSqlType.string, + data['${effectivePrefix}quoted_message_id'], + ), + pollId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}poll_id']), + replyCount: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}reply_count']), + showInChannel: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}show_in_channel']), + shadowed: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}shadowed'])!, + command: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}command']), localCreatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}local_created_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}local_created_at'], + ), remoteCreatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}remote_created_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}remote_created_at'], + ), localUpdatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}local_updated_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}local_updated_at'], + ), remoteUpdatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}remote_updated_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}remote_updated_at'], + ), localDeletedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}local_deleted_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}local_deleted_at'], + ), remoteDeletedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}remote_deleted_at']), - deletedForMe: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}deleted_for_me']), + DriftSqlType.dateTime, + data['${effectivePrefix}remote_deleted_at'], + ), + deletedForMe: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}deleted_for_me']), messageTextUpdatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, - data['${effectivePrefix}message_text_updated_at']), - userId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}user_id']), - channelRole: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_role']), - pinned: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}pinned'])!, - pinnedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}pinned_at']), - pinExpires: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}pin_expires']), + DriftSqlType.dateTime, + data['${effectivePrefix}message_text_updated_at'], + ), + userId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}user_id']), + channelRole: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_role']), + pinned: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}pinned'])!, + pinnedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}pinned_at']), + pinExpires: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}pin_expires']), pinnedByUserId: attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}pinned_by_user_id']), - channelCid: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, - i18n: $MessagesTable.$converteri18n.fromSql(attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}i18n'])), - restrictedVisibility: $MessagesTable.$converterrestrictedVisibilityn - .fromSql(attachedDatabase.typeMapping.read(DriftSqlType.string, - data['${effectivePrefix}restricted_visibility'])), - extraData: $MessagesTable.$converterextraDatan.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])), + DriftSqlType.string, + data['${effectivePrefix}pinned_by_user_id'], + ), + channelCid: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, + i18n: $MessagesTable.$converteri18n.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}i18n']), + ), + restrictedVisibility: $MessagesTable.$converterrestrictedVisibilityn.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}restricted_visibility']), + ), + extraData: $MessagesTable.$converterextraDatan.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}extra_data']), + ), ); } @@ -1226,25 +1280,21 @@ class $MessagesTable extends Messages return $MessagesTable(attachedDatabase, alias); } - static TypeConverter, String> $converterattachments = - ListConverter(); - static TypeConverter, String> $convertermentionedUsers = - ListConverter(); - static TypeConverter, String> - $converterreactionGroups = ReactionGroupsConverter(); - static TypeConverter?, String?> - $converterreactionGroupsn = - NullAwareTypeConverter.wrap($converterreactionGroups); - static TypeConverter?, String?> $converteri18n = - NullableMapConverter(); - static TypeConverter, String> $converterrestrictedVisibility = - ListConverter(); - static TypeConverter?, String?> $converterrestrictedVisibilityn = - NullAwareTypeConverter.wrap($converterrestrictedVisibility); - static TypeConverter, String> $converterextraData = - MapConverter(); - static TypeConverter?, String?> $converterextraDatan = - NullAwareTypeConverter.wrap($converterextraData); + static TypeConverter, String> $converterattachments = ListConverter(); + static TypeConverter, String> $convertermentionedUsers = ListConverter(); + static TypeConverter, String> $converterreactionGroups = ReactionGroupsConverter(); + static TypeConverter?, String?> $converterreactionGroupsn = NullAwareTypeConverter.wrap( + $converterreactionGroups, + ); + static TypeConverter?, String?> $converteri18n = NullableMapConverter(); + static TypeConverter, String> $converterrestrictedVisibility = ListConverter(); + static TypeConverter?, String?> $converterrestrictedVisibilityn = NullAwareTypeConverter.wrap( + $converterrestrictedVisibility, + ); + static TypeConverter, String> $converterextraData = MapConverter(); + static TypeConverter?, String?> $converterextraDatan = NullAwareTypeConverter.wrap( + $converterextraData, + ); } class MessageEntity extends DataClass implements Insertable { @@ -1344,39 +1394,40 @@ class MessageEntity extends DataClass implements Insertable { /// Message custom extraData final Map? extraData; - const MessageEntity( - {required this.id, - this.messageText, - required this.attachments, - required this.state, - required this.type, - required this.mentionedUsers, - this.reactionGroups, - this.parentId, - this.quotedMessageId, - this.pollId, - this.replyCount, - this.showInChannel, - required this.shadowed, - this.command, - this.localCreatedAt, - this.remoteCreatedAt, - this.localUpdatedAt, - this.remoteUpdatedAt, - this.localDeletedAt, - this.remoteDeletedAt, - this.deletedForMe, - this.messageTextUpdatedAt, - this.userId, - this.channelRole, - required this.pinned, - this.pinnedAt, - this.pinExpires, - this.pinnedByUserId, - required this.channelCid, - this.i18n, - this.restrictedVisibility, - this.extraData}); + const MessageEntity({ + required this.id, + this.messageText, + required this.attachments, + required this.state, + required this.type, + required this.mentionedUsers, + this.reactionGroups, + this.parentId, + this.quotedMessageId, + this.pollId, + this.replyCount, + this.showInChannel, + required this.shadowed, + this.command, + this.localCreatedAt, + this.remoteCreatedAt, + this.localUpdatedAt, + this.remoteUpdatedAt, + this.localDeletedAt, + this.remoteDeletedAt, + this.deletedForMe, + this.messageTextUpdatedAt, + this.userId, + this.channelRole, + required this.pinned, + this.pinnedAt, + this.pinExpires, + this.pinnedByUserId, + required this.channelCid, + this.i18n, + this.restrictedVisibility, + this.extraData, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -1385,18 +1436,15 @@ class MessageEntity extends DataClass implements Insertable { map['message_text'] = Variable(messageText); } { - map['attachments'] = Variable( - $MessagesTable.$converterattachments.toSql(attachments)); + map['attachments'] = Variable($MessagesTable.$converterattachments.toSql(attachments)); } map['state'] = Variable(state); map['type'] = Variable(type); { - map['mentioned_users'] = Variable( - $MessagesTable.$convertermentionedUsers.toSql(mentionedUsers)); + map['mentioned_users'] = Variable($MessagesTable.$convertermentionedUsers.toSql(mentionedUsers)); } if (!nullToAbsent || reactionGroups != null) { - map['reaction_groups'] = Variable( - $MessagesTable.$converterreactionGroupsn.toSql(reactionGroups)); + map['reaction_groups'] = Variable($MessagesTable.$converterreactionGroupsn.toSql(reactionGroups)); } if (!nullToAbsent || parentId != null) { map['parent_id'] = Variable(parentId); @@ -1462,19 +1510,17 @@ class MessageEntity extends DataClass implements Insertable { map['i18n'] = Variable($MessagesTable.$converteri18n.toSql(i18n)); } if (!nullToAbsent || restrictedVisibility != null) { - map['restricted_visibility'] = Variable($MessagesTable - .$converterrestrictedVisibilityn - .toSql(restrictedVisibility)); + map['restricted_visibility'] = Variable( + $MessagesTable.$converterrestrictedVisibilityn.toSql(restrictedVisibility), + ); } if (!nullToAbsent || extraData != null) { - map['extra_data'] = Variable( - $MessagesTable.$converterextraDatan.toSql(extraData)); + map['extra_data'] = Variable($MessagesTable.$converterextraDatan.toSql(extraData)); } return map; } - factory MessageEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory MessageEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return MessageEntity( id: serializer.fromJson(json['id']), @@ -1483,8 +1529,7 @@ class MessageEntity extends DataClass implements Insertable { state: serializer.fromJson(json['state']), type: serializer.fromJson(json['type']), mentionedUsers: serializer.fromJson>(json['mentionedUsers']), - reactionGroups: serializer - .fromJson?>(json['reactionGroups']), + reactionGroups: serializer.fromJson?>(json['reactionGroups']), parentId: serializer.fromJson(json['parentId']), quotedMessageId: serializer.fromJson(json['quotedMessageId']), pollId: serializer.fromJson(json['pollId']), @@ -1499,8 +1544,7 @@ class MessageEntity extends DataClass implements Insertable { localDeletedAt: serializer.fromJson(json['localDeletedAt']), remoteDeletedAt: serializer.fromJson(json['remoteDeletedAt']), deletedForMe: serializer.fromJson(json['deletedForMe']), - messageTextUpdatedAt: - serializer.fromJson(json['messageTextUpdatedAt']), + messageTextUpdatedAt: serializer.fromJson(json['messageTextUpdatedAt']), userId: serializer.fromJson(json['userId']), channelRole: serializer.fromJson(json['channelRole']), pinned: serializer.fromJson(json['pinned']), @@ -1509,8 +1553,7 @@ class MessageEntity extends DataClass implements Insertable { pinnedByUserId: serializer.fromJson(json['pinnedByUserId']), channelCid: serializer.fromJson(json['channelCid']), i18n: serializer.fromJson?>(json['i18n']), - restrictedVisibility: - serializer.fromJson?>(json['restrictedVisibility']), + restrictedVisibility: serializer.fromJson?>(json['restrictedVisibility']), extraData: serializer.fromJson?>(json['extraData']), ); } @@ -1524,8 +1567,7 @@ class MessageEntity extends DataClass implements Insertable { 'state': serializer.toJson(state), 'type': serializer.toJson(type), 'mentionedUsers': serializer.toJson>(mentionedUsers), - 'reactionGroups': - serializer.toJson?>(reactionGroups), + 'reactionGroups': serializer.toJson?>(reactionGroups), 'parentId': serializer.toJson(parentId), 'quotedMessageId': serializer.toJson(quotedMessageId), 'pollId': serializer.toJson(pollId), @@ -1540,8 +1582,7 @@ class MessageEntity extends DataClass implements Insertable { 'localDeletedAt': serializer.toJson(localDeletedAt), 'remoteDeletedAt': serializer.toJson(remoteDeletedAt), 'deletedForMe': serializer.toJson(deletedForMe), - 'messageTextUpdatedAt': - serializer.toJson(messageTextUpdatedAt), + 'messageTextUpdatedAt': serializer.toJson(messageTextUpdatedAt), 'userId': serializer.toJson(userId), 'channelRole': serializer.toJson(channelRole), 'pinned': serializer.toJson(pinned), @@ -1550,162 +1591,111 @@ class MessageEntity extends DataClass implements Insertable { 'pinnedByUserId': serializer.toJson(pinnedByUserId), 'channelCid': serializer.toJson(channelCid), 'i18n': serializer.toJson?>(i18n), - 'restrictedVisibility': - serializer.toJson?>(restrictedVisibility), + 'restrictedVisibility': serializer.toJson?>(restrictedVisibility), 'extraData': serializer.toJson?>(extraData), }; } - MessageEntity copyWith( - {String? id, - Value messageText = const Value.absent(), - List? attachments, - String? state, - String? type, - List? mentionedUsers, - Value?> reactionGroups = - const Value.absent(), - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value pollId = const Value.absent(), - Value replyCount = const Value.absent(), - Value showInChannel = const Value.absent(), - bool? shadowed, - Value command = const Value.absent(), - Value localCreatedAt = const Value.absent(), - Value remoteCreatedAt = const Value.absent(), - Value localUpdatedAt = const Value.absent(), - Value remoteUpdatedAt = const Value.absent(), - Value localDeletedAt = const Value.absent(), - Value remoteDeletedAt = const Value.absent(), - Value deletedForMe = const Value.absent(), - Value messageTextUpdatedAt = const Value.absent(), - Value userId = const Value.absent(), - Value channelRole = const Value.absent(), - bool? pinned, - Value pinnedAt = const Value.absent(), - Value pinExpires = const Value.absent(), - Value pinnedByUserId = const Value.absent(), - String? channelCid, - Value?> i18n = const Value.absent(), - Value?> restrictedVisibility = const Value.absent(), - Value?> extraData = const Value.absent()}) => - MessageEntity( - id: id ?? this.id, - messageText: messageText.present ? messageText.value : this.messageText, - attachments: attachments ?? this.attachments, - state: state ?? this.state, - type: type ?? this.type, - mentionedUsers: mentionedUsers ?? this.mentionedUsers, - reactionGroups: - reactionGroups.present ? reactionGroups.value : this.reactionGroups, - parentId: parentId.present ? parentId.value : this.parentId, - quotedMessageId: quotedMessageId.present - ? quotedMessageId.value - : this.quotedMessageId, - pollId: pollId.present ? pollId.value : this.pollId, - replyCount: replyCount.present ? replyCount.value : this.replyCount, - showInChannel: - showInChannel.present ? showInChannel.value : this.showInChannel, - shadowed: shadowed ?? this.shadowed, - command: command.present ? command.value : this.command, - localCreatedAt: - localCreatedAt.present ? localCreatedAt.value : this.localCreatedAt, - remoteCreatedAt: remoteCreatedAt.present - ? remoteCreatedAt.value - : this.remoteCreatedAt, - localUpdatedAt: - localUpdatedAt.present ? localUpdatedAt.value : this.localUpdatedAt, - remoteUpdatedAt: remoteUpdatedAt.present - ? remoteUpdatedAt.value - : this.remoteUpdatedAt, - localDeletedAt: - localDeletedAt.present ? localDeletedAt.value : this.localDeletedAt, - remoteDeletedAt: remoteDeletedAt.present - ? remoteDeletedAt.value - : this.remoteDeletedAt, - deletedForMe: - deletedForMe.present ? deletedForMe.value : this.deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt.present - ? messageTextUpdatedAt.value - : this.messageTextUpdatedAt, - userId: userId.present ? userId.value : this.userId, - channelRole: channelRole.present ? channelRole.value : this.channelRole, - pinned: pinned ?? this.pinned, - pinnedAt: pinnedAt.present ? pinnedAt.value : this.pinnedAt, - pinExpires: pinExpires.present ? pinExpires.value : this.pinExpires, - pinnedByUserId: - pinnedByUserId.present ? pinnedByUserId.value : this.pinnedByUserId, - channelCid: channelCid ?? this.channelCid, - i18n: i18n.present ? i18n.value : this.i18n, - restrictedVisibility: restrictedVisibility.present - ? restrictedVisibility.value - : this.restrictedVisibility, - extraData: extraData.present ? extraData.value : this.extraData, - ); + MessageEntity copyWith({ + String? id, + Value messageText = const Value.absent(), + List? attachments, + String? state, + String? type, + List? mentionedUsers, + Value?> reactionGroups = const Value.absent(), + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value pollId = const Value.absent(), + Value replyCount = const Value.absent(), + Value showInChannel = const Value.absent(), + bool? shadowed, + Value command = const Value.absent(), + Value localCreatedAt = const Value.absent(), + Value remoteCreatedAt = const Value.absent(), + Value localUpdatedAt = const Value.absent(), + Value remoteUpdatedAt = const Value.absent(), + Value localDeletedAt = const Value.absent(), + Value remoteDeletedAt = const Value.absent(), + Value deletedForMe = const Value.absent(), + Value messageTextUpdatedAt = const Value.absent(), + Value userId = const Value.absent(), + Value channelRole = const Value.absent(), + bool? pinned, + Value pinnedAt = const Value.absent(), + Value pinExpires = const Value.absent(), + Value pinnedByUserId = const Value.absent(), + String? channelCid, + Value?> i18n = const Value.absent(), + Value?> restrictedVisibility = const Value.absent(), + Value?> extraData = const Value.absent(), + }) => MessageEntity( + id: id ?? this.id, + messageText: messageText.present ? messageText.value : this.messageText, + attachments: attachments ?? this.attachments, + state: state ?? this.state, + type: type ?? this.type, + mentionedUsers: mentionedUsers ?? this.mentionedUsers, + reactionGroups: reactionGroups.present ? reactionGroups.value : this.reactionGroups, + parentId: parentId.present ? parentId.value : this.parentId, + quotedMessageId: quotedMessageId.present ? quotedMessageId.value : this.quotedMessageId, + pollId: pollId.present ? pollId.value : this.pollId, + replyCount: replyCount.present ? replyCount.value : this.replyCount, + showInChannel: showInChannel.present ? showInChannel.value : this.showInChannel, + shadowed: shadowed ?? this.shadowed, + command: command.present ? command.value : this.command, + localCreatedAt: localCreatedAt.present ? localCreatedAt.value : this.localCreatedAt, + remoteCreatedAt: remoteCreatedAt.present ? remoteCreatedAt.value : this.remoteCreatedAt, + localUpdatedAt: localUpdatedAt.present ? localUpdatedAt.value : this.localUpdatedAt, + remoteUpdatedAt: remoteUpdatedAt.present ? remoteUpdatedAt.value : this.remoteUpdatedAt, + localDeletedAt: localDeletedAt.present ? localDeletedAt.value : this.localDeletedAt, + remoteDeletedAt: remoteDeletedAt.present ? remoteDeletedAt.value : this.remoteDeletedAt, + deletedForMe: deletedForMe.present ? deletedForMe.value : this.deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt.present ? messageTextUpdatedAt.value : this.messageTextUpdatedAt, + userId: userId.present ? userId.value : this.userId, + channelRole: channelRole.present ? channelRole.value : this.channelRole, + pinned: pinned ?? this.pinned, + pinnedAt: pinnedAt.present ? pinnedAt.value : this.pinnedAt, + pinExpires: pinExpires.present ? pinExpires.value : this.pinExpires, + pinnedByUserId: pinnedByUserId.present ? pinnedByUserId.value : this.pinnedByUserId, + channelCid: channelCid ?? this.channelCid, + i18n: i18n.present ? i18n.value : this.i18n, + restrictedVisibility: restrictedVisibility.present ? restrictedVisibility.value : this.restrictedVisibility, + extraData: extraData.present ? extraData.value : this.extraData, + ); MessageEntity copyWithCompanion(MessagesCompanion data) { return MessageEntity( id: data.id.present ? data.id.value : this.id, - messageText: - data.messageText.present ? data.messageText.value : this.messageText, - attachments: - data.attachments.present ? data.attachments.value : this.attachments, + messageText: data.messageText.present ? data.messageText.value : this.messageText, + attachments: data.attachments.present ? data.attachments.value : this.attachments, state: data.state.present ? data.state.value : this.state, type: data.type.present ? data.type.value : this.type, - mentionedUsers: data.mentionedUsers.present - ? data.mentionedUsers.value - : this.mentionedUsers, - reactionGroups: data.reactionGroups.present - ? data.reactionGroups.value - : this.reactionGroups, + mentionedUsers: data.mentionedUsers.present ? data.mentionedUsers.value : this.mentionedUsers, + reactionGroups: data.reactionGroups.present ? data.reactionGroups.value : this.reactionGroups, parentId: data.parentId.present ? data.parentId.value : this.parentId, - quotedMessageId: data.quotedMessageId.present - ? data.quotedMessageId.value - : this.quotedMessageId, + quotedMessageId: data.quotedMessageId.present ? data.quotedMessageId.value : this.quotedMessageId, pollId: data.pollId.present ? data.pollId.value : this.pollId, - replyCount: - data.replyCount.present ? data.replyCount.value : this.replyCount, - showInChannel: data.showInChannel.present - ? data.showInChannel.value - : this.showInChannel, + replyCount: data.replyCount.present ? data.replyCount.value : this.replyCount, + showInChannel: data.showInChannel.present ? data.showInChannel.value : this.showInChannel, shadowed: data.shadowed.present ? data.shadowed.value : this.shadowed, command: data.command.present ? data.command.value : this.command, - localCreatedAt: data.localCreatedAt.present - ? data.localCreatedAt.value - : this.localCreatedAt, - remoteCreatedAt: data.remoteCreatedAt.present - ? data.remoteCreatedAt.value - : this.remoteCreatedAt, - localUpdatedAt: data.localUpdatedAt.present - ? data.localUpdatedAt.value - : this.localUpdatedAt, - remoteUpdatedAt: data.remoteUpdatedAt.present - ? data.remoteUpdatedAt.value - : this.remoteUpdatedAt, - localDeletedAt: data.localDeletedAt.present - ? data.localDeletedAt.value - : this.localDeletedAt, - remoteDeletedAt: data.remoteDeletedAt.present - ? data.remoteDeletedAt.value - : this.remoteDeletedAt, - deletedForMe: data.deletedForMe.present - ? data.deletedForMe.value - : this.deletedForMe, + localCreatedAt: data.localCreatedAt.present ? data.localCreatedAt.value : this.localCreatedAt, + remoteCreatedAt: data.remoteCreatedAt.present ? data.remoteCreatedAt.value : this.remoteCreatedAt, + localUpdatedAt: data.localUpdatedAt.present ? data.localUpdatedAt.value : this.localUpdatedAt, + remoteUpdatedAt: data.remoteUpdatedAt.present ? data.remoteUpdatedAt.value : this.remoteUpdatedAt, + localDeletedAt: data.localDeletedAt.present ? data.localDeletedAt.value : this.localDeletedAt, + remoteDeletedAt: data.remoteDeletedAt.present ? data.remoteDeletedAt.value : this.remoteDeletedAt, + deletedForMe: data.deletedForMe.present ? data.deletedForMe.value : this.deletedForMe, messageTextUpdatedAt: data.messageTextUpdatedAt.present ? data.messageTextUpdatedAt.value : this.messageTextUpdatedAt, userId: data.userId.present ? data.userId.value : this.userId, - channelRole: - data.channelRole.present ? data.channelRole.value : this.channelRole, + channelRole: data.channelRole.present ? data.channelRole.value : this.channelRole, pinned: data.pinned.present ? data.pinned.value : this.pinned, pinnedAt: data.pinnedAt.present ? data.pinnedAt.value : this.pinnedAt, - pinExpires: - data.pinExpires.present ? data.pinExpires.value : this.pinExpires, - pinnedByUserId: data.pinnedByUserId.present - ? data.pinnedByUserId.value - : this.pinnedByUserId, - channelCid: - data.channelCid.present ? data.channelCid.value : this.channelCid, + pinExpires: data.pinExpires.present ? data.pinExpires.value : this.pinExpires, + pinnedByUserId: data.pinnedByUserId.present ? data.pinnedByUserId.value : this.pinnedByUserId, + channelCid: data.channelCid.present ? data.channelCid.value : this.channelCid, i18n: data.i18n.present ? data.i18n.value : this.i18n, restrictedVisibility: data.restrictedVisibility.present ? data.restrictedVisibility.value @@ -1755,39 +1745,39 @@ class MessageEntity extends DataClass implements Insertable { @override int get hashCode => Object.hashAll([ - id, - messageText, - attachments, - state, - type, - mentionedUsers, - reactionGroups, - parentId, - quotedMessageId, - pollId, - replyCount, - showInChannel, - shadowed, - command, - localCreatedAt, - remoteCreatedAt, - localUpdatedAt, - remoteUpdatedAt, - localDeletedAt, - remoteDeletedAt, - deletedForMe, - messageTextUpdatedAt, - userId, - channelRole, - pinned, - pinnedAt, - pinExpires, - pinnedByUserId, - channelCid, - i18n, - restrictedVisibility, - extraData - ]); + id, + messageText, + attachments, + state, + type, + mentionedUsers, + reactionGroups, + parentId, + quotedMessageId, + pollId, + replyCount, + showInChannel, + shadowed, + command, + localCreatedAt, + remoteCreatedAt, + localUpdatedAt, + remoteUpdatedAt, + localDeletedAt, + remoteDeletedAt, + deletedForMe, + messageTextUpdatedAt, + userId, + channelRole, + pinned, + pinnedAt, + pinExpires, + pinnedByUserId, + channelCid, + i18n, + restrictedVisibility, + extraData, + ]); @override bool operator ==(Object other) => identical(this, other) || @@ -1929,11 +1919,11 @@ class MessagesCompanion extends UpdateCompanion { this.restrictedVisibility = const Value.absent(), this.extraData = const Value.absent(), this.rowid = const Value.absent(), - }) : id = Value(id), - attachments = Value(attachments), - state = Value(state), - mentionedUsers = Value(mentionedUsers), - channelCid = Value(channelCid); + }) : id = Value(id), + attachments = Value(attachments), + state = Value(state), + mentionedUsers = Value(mentionedUsers), + channelCid = Value(channelCid); static Insertable custom({ Expression? id, Expression? messageText, @@ -1991,8 +1981,7 @@ class MessagesCompanion extends UpdateCompanion { if (localDeletedAt != null) 'local_deleted_at': localDeletedAt, if (remoteDeletedAt != null) 'remote_deleted_at': remoteDeletedAt, if (deletedForMe != null) 'deleted_for_me': deletedForMe, - if (messageTextUpdatedAt != null) - 'message_text_updated_at': messageTextUpdatedAt, + if (messageTextUpdatedAt != null) 'message_text_updated_at': messageTextUpdatedAt, if (userId != null) 'user_id': userId, if (channelRole != null) 'channel_role': channelRole, if (pinned != null) 'pinned': pinned, @@ -2001,47 +1990,47 @@ class MessagesCompanion extends UpdateCompanion { if (pinnedByUserId != null) 'pinned_by_user_id': pinnedByUserId, if (channelCid != null) 'channel_cid': channelCid, if (i18n != null) 'i18n': i18n, - if (restrictedVisibility != null) - 'restricted_visibility': restrictedVisibility, + if (restrictedVisibility != null) 'restricted_visibility': restrictedVisibility, if (extraData != null) 'extra_data': extraData, if (rowid != null) 'rowid': rowid, }); } - MessagesCompanion copyWith( - {Value? id, - Value? messageText, - Value>? attachments, - Value? state, - Value? type, - Value>? mentionedUsers, - Value?>? reactionGroups, - Value? parentId, - Value? quotedMessageId, - Value? pollId, - Value? replyCount, - Value? showInChannel, - Value? shadowed, - Value? command, - Value? localCreatedAt, - Value? remoteCreatedAt, - Value? localUpdatedAt, - Value? remoteUpdatedAt, - Value? localDeletedAt, - Value? remoteDeletedAt, - Value? deletedForMe, - Value? messageTextUpdatedAt, - Value? userId, - Value? channelRole, - Value? pinned, - Value? pinnedAt, - Value? pinExpires, - Value? pinnedByUserId, - Value? channelCid, - Value?>? i18n, - Value?>? restrictedVisibility, - Value?>? extraData, - Value? rowid}) { + MessagesCompanion copyWith({ + Value? id, + Value? messageText, + Value>? attachments, + Value? state, + Value? type, + Value>? mentionedUsers, + Value?>? reactionGroups, + Value? parentId, + Value? quotedMessageId, + Value? pollId, + Value? replyCount, + Value? showInChannel, + Value? shadowed, + Value? command, + Value? localCreatedAt, + Value? remoteCreatedAt, + Value? localUpdatedAt, + Value? remoteUpdatedAt, + Value? localDeletedAt, + Value? remoteDeletedAt, + Value? deletedForMe, + Value? messageTextUpdatedAt, + Value? userId, + Value? channelRole, + Value? pinned, + Value? pinnedAt, + Value? pinExpires, + Value? pinnedByUserId, + Value? channelCid, + Value?>? i18n, + Value?>? restrictedVisibility, + Value?>? extraData, + Value? rowid, + }) { return MessagesCompanion( id: id ?? this.id, messageText: messageText ?? this.messageText, @@ -2089,8 +2078,7 @@ class MessagesCompanion extends UpdateCompanion { map['message_text'] = Variable(messageText.value); } if (attachments.present) { - map['attachments'] = Variable( - $MessagesTable.$converterattachments.toSql(attachments.value)); + map['attachments'] = Variable($MessagesTable.$converterattachments.toSql(attachments.value)); } if (state.present) { map['state'] = Variable(state.value); @@ -2099,12 +2087,10 @@ class MessagesCompanion extends UpdateCompanion { map['type'] = Variable(type.value); } if (mentionedUsers.present) { - map['mentioned_users'] = Variable( - $MessagesTable.$convertermentionedUsers.toSql(mentionedUsers.value)); + map['mentioned_users'] = Variable($MessagesTable.$convertermentionedUsers.toSql(mentionedUsers.value)); } if (reactionGroups.present) { - map['reaction_groups'] = Variable( - $MessagesTable.$converterreactionGroupsn.toSql(reactionGroups.value)); + map['reaction_groups'] = Variable($MessagesTable.$converterreactionGroupsn.toSql(reactionGroups.value)); } if (parentId.present) { map['parent_id'] = Variable(parentId.value); @@ -2149,8 +2135,7 @@ class MessagesCompanion extends UpdateCompanion { map['deleted_for_me'] = Variable(deletedForMe.value); } if (messageTextUpdatedAt.present) { - map['message_text_updated_at'] = - Variable(messageTextUpdatedAt.value); + map['message_text_updated_at'] = Variable(messageTextUpdatedAt.value); } if (userId.present) { map['user_id'] = Variable(userId.value); @@ -2174,17 +2159,15 @@ class MessagesCompanion extends UpdateCompanion { map['channel_cid'] = Variable(channelCid.value); } if (i18n.present) { - map['i18n'] = - Variable($MessagesTable.$converteri18n.toSql(i18n.value)); + map['i18n'] = Variable($MessagesTable.$converteri18n.toSql(i18n.value)); } if (restrictedVisibility.present) { - map['restricted_visibility'] = Variable($MessagesTable - .$converterrestrictedVisibilityn - .toSql(restrictedVisibility.value)); + map['restricted_visibility'] = Variable( + $MessagesTable.$converterrestrictedVisibilityn.toSql(restrictedVisibility.value), + ); } if (extraData.present) { - map['extra_data'] = Variable( - $MessagesTable.$converterextraDatan.toSql(extraData.value)); + map['extra_data'] = Variable($MessagesTable.$converterextraDatan.toSql(extraData.value)); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -2233,8 +2216,7 @@ class MessagesCompanion extends UpdateCompanion { } } -class $DraftMessagesTable extends DraftMessages - with TableInfo<$DraftMessagesTable, DraftMessageEntity> { +class $DraftMessagesTable extends DraftMessages with TableInfo<$DraftMessagesTable, DraftMessageEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; @@ -2242,126 +2224,157 @@ class $DraftMessagesTable extends DraftMessages static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _messageTextMeta = - const VerificationMeta('messageText'); + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _messageTextMeta = const VerificationMeta('messageText'); @override late final GeneratedColumn messageText = GeneratedColumn( - 'message_text', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - @override - late final GeneratedColumnWithTypeConverter, String> - attachments = GeneratedColumn('attachments', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>( - $DraftMessagesTable.$converterattachments); + 'message_text', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + late final GeneratedColumnWithTypeConverter, String> attachments = GeneratedColumn( + 'attachments', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($DraftMessagesTable.$converterattachments); static const VerificationMeta _typeMeta = const VerificationMeta('type'); @override late final GeneratedColumn type = GeneratedColumn( - 'type', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: const Constant('regular')); - @override - late final GeneratedColumnWithTypeConverter, String> - mentionedUsers = GeneratedColumn( - 'mentioned_users', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>( - $DraftMessagesTable.$convertermentionedUsers); - static const VerificationMeta _parentIdMeta = - const VerificationMeta('parentId'); + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('regular'), + ); + @override + late final GeneratedColumnWithTypeConverter, String> mentionedUsers = GeneratedColumn( + 'mentioned_users', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($DraftMessagesTable.$convertermentionedUsers); + static const VerificationMeta _parentIdMeta = const VerificationMeta('parentId'); @override late final GeneratedColumn parentId = GeneratedColumn( - 'parent_id', aliasedName, true, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES messages (id) ON DELETE CASCADE')); - static const VerificationMeta _quotedMessageIdMeta = - const VerificationMeta('quotedMessageId'); + 'parent_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES messages (id) ON DELETE CASCADE'), + ); + static const VerificationMeta _quotedMessageIdMeta = const VerificationMeta('quotedMessageId'); @override late final GeneratedColumn quotedMessageId = GeneratedColumn( - 'quoted_message_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'quoted_message_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); static const VerificationMeta _pollIdMeta = const VerificationMeta('pollId'); @override late final GeneratedColumn pollId = GeneratedColumn( - 'poll_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _showInChannelMeta = - const VerificationMeta('showInChannel'); + 'poll_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _showInChannelMeta = const VerificationMeta('showInChannel'); @override late final GeneratedColumn showInChannel = GeneratedColumn( - 'show_in_channel', aliasedName, true, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("show_in_channel" IN (0, 1))')); - static const VerificationMeta _commandMeta = - const VerificationMeta('command'); + 'show_in_channel', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("show_in_channel" IN (0, 1))'), + ); + static const VerificationMeta _commandMeta = const VerificationMeta('command'); @override late final GeneratedColumn command = GeneratedColumn( - 'command', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'command', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); static const VerificationMeta _silentMeta = const VerificationMeta('silent'); @override late final GeneratedColumn silent = GeneratedColumn( - 'silent', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("silent" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _createdAtMeta = - const VerificationMeta('createdAt'); + 'silent', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("silent" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - static const VerificationMeta _channelCidMeta = - const VerificationMeta('channelCid'); + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); @override late final GeneratedColumn channelCid = GeneratedColumn( - 'channel_cid', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES channels (cid) ON DELETE CASCADE')); - @override - late final GeneratedColumnWithTypeConverter?, String> - extraData = GeneratedColumn('extra_data', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $DraftMessagesTable.$converterextraDatan); + 'channel_cid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES channels (cid) ON DELETE CASCADE'), + ); + @override + late final GeneratedColumnWithTypeConverter?, String> extraData = GeneratedColumn( + 'extra_data', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($DraftMessagesTable.$converterextraDatan); @override List get $columns => [ - id, - messageText, - attachments, - type, - mentionedUsers, - parentId, - quotedMessageId, - pollId, - showInChannel, - command, - silent, - createdAt, - channelCid, - extraData - ]; + id, + messageText, + attachments, + type, + mentionedUsers, + parentId, + quotedMessageId, + pollId, + showInChannel, + command, + silent, + createdAt, + channelCid, + extraData, + ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'draft_messages'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { @@ -2370,52 +2383,40 @@ class $DraftMessagesTable extends DraftMessages context.missing(_idMeta); } if (data.containsKey('message_text')) { - context.handle( - _messageTextMeta, - messageText.isAcceptableOrUnknown( - data['message_text']!, _messageTextMeta)); + context.handle(_messageTextMeta, messageText.isAcceptableOrUnknown(data['message_text']!, _messageTextMeta)); } if (data.containsKey('type')) { - context.handle( - _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + context.handle(_typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); } if (data.containsKey('parent_id')) { - context.handle(_parentIdMeta, - parentId.isAcceptableOrUnknown(data['parent_id']!, _parentIdMeta)); + context.handle(_parentIdMeta, parentId.isAcceptableOrUnknown(data['parent_id']!, _parentIdMeta)); } if (data.containsKey('quoted_message_id')) { context.handle( - _quotedMessageIdMeta, - quotedMessageId.isAcceptableOrUnknown( - data['quoted_message_id']!, _quotedMessageIdMeta)); + _quotedMessageIdMeta, + quotedMessageId.isAcceptableOrUnknown(data['quoted_message_id']!, _quotedMessageIdMeta), + ); } if (data.containsKey('poll_id')) { - context.handle(_pollIdMeta, - pollId.isAcceptableOrUnknown(data['poll_id']!, _pollIdMeta)); + context.handle(_pollIdMeta, pollId.isAcceptableOrUnknown(data['poll_id']!, _pollIdMeta)); } if (data.containsKey('show_in_channel')) { context.handle( - _showInChannelMeta, - showInChannel.isAcceptableOrUnknown( - data['show_in_channel']!, _showInChannelMeta)); + _showInChannelMeta, + showInChannel.isAcceptableOrUnknown(data['show_in_channel']!, _showInChannelMeta), + ); } if (data.containsKey('command')) { - context.handle(_commandMeta, - command.isAcceptableOrUnknown(data['command']!, _commandMeta)); + context.handle(_commandMeta, command.isAcceptableOrUnknown(data['command']!, _commandMeta)); } if (data.containsKey('silent')) { - context.handle(_silentMeta, - silent.isAcceptableOrUnknown(data['silent']!, _silentMeta)); + context.handle(_silentMeta, silent.isAcceptableOrUnknown(data['silent']!, _silentMeta)); } if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('channel_cid')) { - context.handle( - _channelCidMeta, - channelCid.isAcceptableOrUnknown( - data['channel_cid']!, _channelCidMeta)); + context.handle(_channelCidMeta, channelCid.isAcceptableOrUnknown(data['channel_cid']!, _channelCidMeta)); } else if (isInserting) { context.missing(_channelCidMeta); } @@ -2428,37 +2429,29 @@ class $DraftMessagesTable extends DraftMessages DraftMessageEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return DraftMessageEntity( - id: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}id'])!, - messageText: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}message_text']), + id: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}id'])!, + messageText: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}message_text']), attachments: $DraftMessagesTable.$converterattachments.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}attachments'])!), - type: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}type'])!, + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}attachments'])!, + ), + type: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}type'])!, mentionedUsers: $DraftMessagesTable.$convertermentionedUsers.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}mentioned_users'])!), - parentId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}parent_id']), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}mentioned_users'])!, + ), + parentId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}parent_id']), quotedMessageId: attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}quoted_message_id']), - pollId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}poll_id']), - showInChannel: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}show_in_channel']), - command: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}command']), - silent: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}silent'])!, - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, - channelCid: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, + DriftSqlType.string, + data['${effectivePrefix}quoted_message_id'], + ), + pollId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}poll_id']), + showInChannel: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}show_in_channel']), + command: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}command']), + silent: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}silent'])!, + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + channelCid: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, extraData: $DraftMessagesTable.$converterextraDatan.fromSql( - attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}extra_data']), + ), ); } @@ -2467,18 +2460,15 @@ class $DraftMessagesTable extends DraftMessages return $DraftMessagesTable(attachedDatabase, alias); } - static TypeConverter, String> $converterattachments = - ListConverter(); - static TypeConverter, String> $convertermentionedUsers = - ListConverter(); - static TypeConverter, String> $converterextraData = - MapConverter(); - static TypeConverter?, String?> $converterextraDatan = - NullAwareTypeConverter.wrap($converterextraData); + static TypeConverter, String> $converterattachments = ListConverter(); + static TypeConverter, String> $convertermentionedUsers = ListConverter(); + static TypeConverter, String> $converterextraData = MapConverter(); + static TypeConverter?, String?> $converterextraDatan = NullAwareTypeConverter.wrap( + $converterextraData, + ); } -class DraftMessageEntity extends DataClass - implements Insertable { +class DraftMessageEntity extends DataClass implements Insertable { /// The message id final String id; @@ -2521,21 +2511,22 @@ class DraftMessageEntity extends DataClass /// Message custom extraData final Map? extraData; - const DraftMessageEntity( - {required this.id, - this.messageText, - required this.attachments, - required this.type, - required this.mentionedUsers, - this.parentId, - this.quotedMessageId, - this.pollId, - this.showInChannel, - this.command, - required this.silent, - required this.createdAt, - required this.channelCid, - this.extraData}); + const DraftMessageEntity({ + required this.id, + this.messageText, + required this.attachments, + required this.type, + required this.mentionedUsers, + this.parentId, + this.quotedMessageId, + this.pollId, + this.showInChannel, + this.command, + required this.silent, + required this.createdAt, + required this.channelCid, + this.extraData, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -2544,13 +2535,11 @@ class DraftMessageEntity extends DataClass map['message_text'] = Variable(messageText); } { - map['attachments'] = Variable( - $DraftMessagesTable.$converterattachments.toSql(attachments)); + map['attachments'] = Variable($DraftMessagesTable.$converterattachments.toSql(attachments)); } map['type'] = Variable(type); { - map['mentioned_users'] = Variable( - $DraftMessagesTable.$convertermentionedUsers.toSql(mentionedUsers)); + map['mentioned_users'] = Variable($DraftMessagesTable.$convertermentionedUsers.toSql(mentionedUsers)); } if (!nullToAbsent || parentId != null) { map['parent_id'] = Variable(parentId); @@ -2571,14 +2560,12 @@ class DraftMessageEntity extends DataClass map['created_at'] = Variable(createdAt); map['channel_cid'] = Variable(channelCid); if (!nullToAbsent || extraData != null) { - map['extra_data'] = Variable( - $DraftMessagesTable.$converterextraDatan.toSql(extraData)); + map['extra_data'] = Variable($DraftMessagesTable.$converterextraDatan.toSql(extraData)); } return map; } - factory DraftMessageEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory DraftMessageEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return DraftMessageEntity( id: serializer.fromJson(json['id']), @@ -2618,64 +2605,52 @@ class DraftMessageEntity extends DataClass }; } - DraftMessageEntity copyWith( - {String? id, - Value messageText = const Value.absent(), - List? attachments, - String? type, - List? mentionedUsers, - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value pollId = const Value.absent(), - Value showInChannel = const Value.absent(), - Value command = const Value.absent(), - bool? silent, - DateTime? createdAt, - String? channelCid, - Value?> extraData = const Value.absent()}) => - DraftMessageEntity( - id: id ?? this.id, - messageText: messageText.present ? messageText.value : this.messageText, - attachments: attachments ?? this.attachments, - type: type ?? this.type, - mentionedUsers: mentionedUsers ?? this.mentionedUsers, - parentId: parentId.present ? parentId.value : this.parentId, - quotedMessageId: quotedMessageId.present - ? quotedMessageId.value - : this.quotedMessageId, - pollId: pollId.present ? pollId.value : this.pollId, - showInChannel: - showInChannel.present ? showInChannel.value : this.showInChannel, - command: command.present ? command.value : this.command, - silent: silent ?? this.silent, - createdAt: createdAt ?? this.createdAt, - channelCid: channelCid ?? this.channelCid, - extraData: extraData.present ? extraData.value : this.extraData, - ); + DraftMessageEntity copyWith({ + String? id, + Value messageText = const Value.absent(), + List? attachments, + String? type, + List? mentionedUsers, + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value pollId = const Value.absent(), + Value showInChannel = const Value.absent(), + Value command = const Value.absent(), + bool? silent, + DateTime? createdAt, + String? channelCid, + Value?> extraData = const Value.absent(), + }) => DraftMessageEntity( + id: id ?? this.id, + messageText: messageText.present ? messageText.value : this.messageText, + attachments: attachments ?? this.attachments, + type: type ?? this.type, + mentionedUsers: mentionedUsers ?? this.mentionedUsers, + parentId: parentId.present ? parentId.value : this.parentId, + quotedMessageId: quotedMessageId.present ? quotedMessageId.value : this.quotedMessageId, + pollId: pollId.present ? pollId.value : this.pollId, + showInChannel: showInChannel.present ? showInChannel.value : this.showInChannel, + command: command.present ? command.value : this.command, + silent: silent ?? this.silent, + createdAt: createdAt ?? this.createdAt, + channelCid: channelCid ?? this.channelCid, + extraData: extraData.present ? extraData.value : this.extraData, + ); DraftMessageEntity copyWithCompanion(DraftMessagesCompanion data) { return DraftMessageEntity( id: data.id.present ? data.id.value : this.id, - messageText: - data.messageText.present ? data.messageText.value : this.messageText, - attachments: - data.attachments.present ? data.attachments.value : this.attachments, + messageText: data.messageText.present ? data.messageText.value : this.messageText, + attachments: data.attachments.present ? data.attachments.value : this.attachments, type: data.type.present ? data.type.value : this.type, - mentionedUsers: data.mentionedUsers.present - ? data.mentionedUsers.value - : this.mentionedUsers, + mentionedUsers: data.mentionedUsers.present ? data.mentionedUsers.value : this.mentionedUsers, parentId: data.parentId.present ? data.parentId.value : this.parentId, - quotedMessageId: data.quotedMessageId.present - ? data.quotedMessageId.value - : this.quotedMessageId, + quotedMessageId: data.quotedMessageId.present ? data.quotedMessageId.value : this.quotedMessageId, pollId: data.pollId.present ? data.pollId.value : this.pollId, - showInChannel: data.showInChannel.present - ? data.showInChannel.value - : this.showInChannel, + showInChannel: data.showInChannel.present ? data.showInChannel.value : this.showInChannel, command: data.command.present ? data.command.value : this.command, silent: data.silent.present ? data.silent.value : this.silent, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, - channelCid: - data.channelCid.present ? data.channelCid.value : this.channelCid, + channelCid: data.channelCid.present ? data.channelCid.value : this.channelCid, extraData: data.extraData.present ? data.extraData.value : this.extraData, ); } @@ -2703,20 +2678,21 @@ class DraftMessageEntity extends DataClass @override int get hashCode => Object.hash( - id, - messageText, - attachments, - type, - mentionedUsers, - parentId, - quotedMessageId, - pollId, - showInChannel, - command, - silent, - createdAt, - channelCid, - extraData); + id, + messageText, + attachments, + type, + mentionedUsers, + parentId, + quotedMessageId, + pollId, + showInChannel, + command, + silent, + createdAt, + channelCid, + extraData, + ); @override bool operator ==(Object other) => identical(this, other) || @@ -2786,10 +2762,10 @@ class DraftMessagesCompanion extends UpdateCompanion { required String channelCid, this.extraData = const Value.absent(), this.rowid = const Value.absent(), - }) : id = Value(id), - attachments = Value(attachments), - mentionedUsers = Value(mentionedUsers), - channelCid = Value(channelCid); + }) : id = Value(id), + attachments = Value(attachments), + mentionedUsers = Value(mentionedUsers), + channelCid = Value(channelCid); static Insertable custom({ Expression? id, Expression? messageText, @@ -2826,22 +2802,23 @@ class DraftMessagesCompanion extends UpdateCompanion { }); } - DraftMessagesCompanion copyWith( - {Value? id, - Value? messageText, - Value>? attachments, - Value? type, - Value>? mentionedUsers, - Value? parentId, - Value? quotedMessageId, - Value? pollId, - Value? showInChannel, - Value? command, - Value? silent, - Value? createdAt, - Value? channelCid, - Value?>? extraData, - Value? rowid}) { + DraftMessagesCompanion copyWith({ + Value? id, + Value? messageText, + Value>? attachments, + Value? type, + Value>? mentionedUsers, + Value? parentId, + Value? quotedMessageId, + Value? pollId, + Value? showInChannel, + Value? command, + Value? silent, + Value? createdAt, + Value? channelCid, + Value?>? extraData, + Value? rowid, + }) { return DraftMessagesCompanion( id: id ?? this.id, messageText: messageText ?? this.messageText, @@ -2871,16 +2848,15 @@ class DraftMessagesCompanion extends UpdateCompanion { map['message_text'] = Variable(messageText.value); } if (attachments.present) { - map['attachments'] = Variable( - $DraftMessagesTable.$converterattachments.toSql(attachments.value)); + map['attachments'] = Variable($DraftMessagesTable.$converterattachments.toSql(attachments.value)); } if (type.present) { map['type'] = Variable(type.value); } if (mentionedUsers.present) { - map['mentioned_users'] = Variable($DraftMessagesTable - .$convertermentionedUsers - .toSql(mentionedUsers.value)); + map['mentioned_users'] = Variable( + $DraftMessagesTable.$convertermentionedUsers.toSql(mentionedUsers.value), + ); } if (parentId.present) { map['parent_id'] = Variable(parentId.value); @@ -2907,8 +2883,7 @@ class DraftMessagesCompanion extends UpdateCompanion { map['channel_cid'] = Variable(channelCid.value); } if (extraData.present) { - map['extra_data'] = Variable( - $DraftMessagesTable.$converterextraDatan.toSql(extraData.value)); + map['extra_data'] = Variable($DraftMessagesTable.$converterextraDatan.toSql(extraData.value)); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -2939,139 +2914,150 @@ class DraftMessagesCompanion extends UpdateCompanion { } } -class $LocationsTable extends Locations - with TableInfo<$LocationsTable, LocationEntity> { +class $LocationsTable extends Locations with TableInfo<$LocationsTable, LocationEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; $LocationsTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _channelCidMeta = - const VerificationMeta('channelCid'); + static const VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); @override late final GeneratedColumn channelCid = GeneratedColumn( - 'channel_cid', aliasedName, true, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES channels (cid) ON DELETE CASCADE')); - static const VerificationMeta _messageIdMeta = - const VerificationMeta('messageId'); + 'channel_cid', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES channels (cid) ON DELETE CASCADE'), + ); + static const VerificationMeta _messageIdMeta = const VerificationMeta('messageId'); @override late final GeneratedColumn messageId = GeneratedColumn( - 'message_id', aliasedName, true, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES messages (id) ON DELETE CASCADE')); + 'message_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES messages (id) ON DELETE CASCADE'), + ); static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); @override late final GeneratedColumn userId = GeneratedColumn( - 'user_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _latitudeMeta = - const VerificationMeta('latitude'); + 'user_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _latitudeMeta = const VerificationMeta('latitude'); @override late final GeneratedColumn latitude = GeneratedColumn( - 'latitude', aliasedName, false, - type: DriftSqlType.double, requiredDuringInsert: true); - static const VerificationMeta _longitudeMeta = - const VerificationMeta('longitude'); + 'latitude', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + ); + static const VerificationMeta _longitudeMeta = const VerificationMeta('longitude'); @override late final GeneratedColumn longitude = GeneratedColumn( - 'longitude', aliasedName, false, - type: DriftSqlType.double, requiredDuringInsert: true); - static const VerificationMeta _createdByDeviceIdMeta = - const VerificationMeta('createdByDeviceId'); - @override - late final GeneratedColumn createdByDeviceId = - GeneratedColumn('created_by_device_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'longitude', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + ); + static const VerificationMeta _createdByDeviceIdMeta = const VerificationMeta('createdByDeviceId'); + @override + late final GeneratedColumn createdByDeviceId = GeneratedColumn( + 'created_by_device_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); static const VerificationMeta _endAtMeta = const VerificationMeta('endAt'); @override late final GeneratedColumn endAt = GeneratedColumn( - 'end_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _createdAtMeta = - const VerificationMeta('createdAt'); + 'end_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - static const VerificationMeta _updatedAtMeta = - const VerificationMeta('updatedAt'); + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); @override late final GeneratedColumn updatedAt = GeneratedColumn( - 'updated_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); @override List get $columns => [ - channelCid, - messageId, - userId, - latitude, - longitude, - createdByDeviceId, - endAt, - createdAt, - updatedAt - ]; + channelCid, + messageId, + userId, + latitude, + longitude, + createdByDeviceId, + endAt, + createdAt, + updatedAt, + ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'locations'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('channel_cid')) { - context.handle( - _channelCidMeta, - channelCid.isAcceptableOrUnknown( - data['channel_cid']!, _channelCidMeta)); + context.handle(_channelCidMeta, channelCid.isAcceptableOrUnknown(data['channel_cid']!, _channelCidMeta)); } if (data.containsKey('message_id')) { - context.handle(_messageIdMeta, - messageId.isAcceptableOrUnknown(data['message_id']!, _messageIdMeta)); + context.handle(_messageIdMeta, messageId.isAcceptableOrUnknown(data['message_id']!, _messageIdMeta)); } if (data.containsKey('user_id')) { - context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + context.handle(_userIdMeta, userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } if (data.containsKey('latitude')) { - context.handle(_latitudeMeta, - latitude.isAcceptableOrUnknown(data['latitude']!, _latitudeMeta)); + context.handle(_latitudeMeta, latitude.isAcceptableOrUnknown(data['latitude']!, _latitudeMeta)); } else if (isInserting) { context.missing(_latitudeMeta); } if (data.containsKey('longitude')) { - context.handle(_longitudeMeta, - longitude.isAcceptableOrUnknown(data['longitude']!, _longitudeMeta)); + context.handle(_longitudeMeta, longitude.isAcceptableOrUnknown(data['longitude']!, _longitudeMeta)); } else if (isInserting) { context.missing(_longitudeMeta); } if (data.containsKey('created_by_device_id')) { context.handle( - _createdByDeviceIdMeta, - createdByDeviceId.isAcceptableOrUnknown( - data['created_by_device_id']!, _createdByDeviceIdMeta)); + _createdByDeviceIdMeta, + createdByDeviceId.isAcceptableOrUnknown(data['created_by_device_id']!, _createdByDeviceIdMeta), + ); } if (data.containsKey('end_at')) { - context.handle( - _endAtMeta, endAt.isAcceptableOrUnknown(data['end_at']!, _endAtMeta)); + context.handle(_endAtMeta, endAt.isAcceptableOrUnknown(data['end_at']!, _endAtMeta)); } if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { - context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } return context; } @@ -3082,24 +3068,18 @@ class $LocationsTable extends Locations LocationEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return LocationEntity( - channelCid: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_cid']), - messageId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}message_id']), - userId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}user_id']), - latitude: attachedDatabase.typeMapping - .read(DriftSqlType.double, data['${effectivePrefix}latitude'])!, - longitude: attachedDatabase.typeMapping - .read(DriftSqlType.double, data['${effectivePrefix}longitude'])!, + channelCid: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_cid']), + messageId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}message_id']), + userId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}user_id']), + latitude: attachedDatabase.typeMapping.read(DriftSqlType.double, data['${effectivePrefix}latitude'])!, + longitude: attachedDatabase.typeMapping.read(DriftSqlType.double, data['${effectivePrefix}longitude'])!, createdByDeviceId: attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}created_by_device_id']), - endAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}end_at']), - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, - updatedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + DriftSqlType.string, + data['${effectivePrefix}created_by_device_id'], + ), + endAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}end_at']), + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, ); } @@ -3137,16 +3117,17 @@ class LocationEntity extends DataClass implements Insertable { /// The date at which the location was last updated final DateTime updatedAt; - const LocationEntity( - {this.channelCid, - this.messageId, - this.userId, - required this.latitude, - required this.longitude, - this.createdByDeviceId, - this.endAt, - required this.createdAt, - required this.updatedAt}); + const LocationEntity({ + this.channelCid, + this.messageId, + this.userId, + required this.latitude, + required this.longitude, + this.createdByDeviceId, + this.endAt, + required this.createdAt, + required this.updatedAt, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -3172,8 +3153,7 @@ class LocationEntity extends DataClass implements Insertable { return map; } - factory LocationEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory LocationEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return LocationEntity( channelCid: serializer.fromJson(json['channelCid']), @@ -3181,8 +3161,7 @@ class LocationEntity extends DataClass implements Insertable { userId: serializer.fromJson(json['userId']), latitude: serializer.fromJson(json['latitude']), longitude: serializer.fromJson(json['longitude']), - createdByDeviceId: - serializer.fromJson(json['createdByDeviceId']), + createdByDeviceId: serializer.fromJson(json['createdByDeviceId']), endAt: serializer.fromJson(json['endAt']), createdAt: serializer.fromJson(json['createdAt']), updatedAt: serializer.fromJson(json['updatedAt']), @@ -3204,40 +3183,35 @@ class LocationEntity extends DataClass implements Insertable { }; } - LocationEntity copyWith( - {Value channelCid = const Value.absent(), - Value messageId = const Value.absent(), - Value userId = const Value.absent(), - double? latitude, - double? longitude, - Value createdByDeviceId = const Value.absent(), - Value endAt = const Value.absent(), - DateTime? createdAt, - DateTime? updatedAt}) => - LocationEntity( - channelCid: channelCid.present ? channelCid.value : this.channelCid, - messageId: messageId.present ? messageId.value : this.messageId, - userId: userId.present ? userId.value : this.userId, - latitude: latitude ?? this.latitude, - longitude: longitude ?? this.longitude, - createdByDeviceId: createdByDeviceId.present - ? createdByDeviceId.value - : this.createdByDeviceId, - endAt: endAt.present ? endAt.value : this.endAt, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - ); + LocationEntity copyWith({ + Value channelCid = const Value.absent(), + Value messageId = const Value.absent(), + Value userId = const Value.absent(), + double? latitude, + double? longitude, + Value createdByDeviceId = const Value.absent(), + Value endAt = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + }) => LocationEntity( + channelCid: channelCid.present ? channelCid.value : this.channelCid, + messageId: messageId.present ? messageId.value : this.messageId, + userId: userId.present ? userId.value : this.userId, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + createdByDeviceId: createdByDeviceId.present ? createdByDeviceId.value : this.createdByDeviceId, + endAt: endAt.present ? endAt.value : this.endAt, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); LocationEntity copyWithCompanion(LocationsCompanion data) { return LocationEntity( - channelCid: - data.channelCid.present ? data.channelCid.value : this.channelCid, + channelCid: data.channelCid.present ? data.channelCid.value : this.channelCid, messageId: data.messageId.present ? data.messageId.value : this.messageId, userId: data.userId.present ? data.userId.value : this.userId, latitude: data.latitude.present ? data.latitude.value : this.latitude, longitude: data.longitude.present ? data.longitude.value : this.longitude, - createdByDeviceId: data.createdByDeviceId.present - ? data.createdByDeviceId.value - : this.createdByDeviceId, + createdByDeviceId: data.createdByDeviceId.present ? data.createdByDeviceId.value : this.createdByDeviceId, endAt: data.endAt.present ? data.endAt.value : this.endAt, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, @@ -3261,8 +3235,8 @@ class LocationEntity extends DataClass implements Insertable { } @override - int get hashCode => Object.hash(channelCid, messageId, userId, latitude, - longitude, createdByDeviceId, endAt, createdAt, updatedAt); + int get hashCode => + Object.hash(channelCid, messageId, userId, latitude, longitude, createdByDeviceId, endAt, createdAt, updatedAt); @override bool operator ==(Object other) => identical(this, other) || @@ -3312,8 +3286,8 @@ class LocationsCompanion extends UpdateCompanion { this.createdAt = const Value.absent(), this.updatedAt = const Value.absent(), this.rowid = const Value.absent(), - }) : latitude = Value(latitude), - longitude = Value(longitude); + }) : latitude = Value(latitude), + longitude = Value(longitude); static Insertable custom({ Expression? channelCid, Expression? messageId, @@ -3340,17 +3314,18 @@ class LocationsCompanion extends UpdateCompanion { }); } - LocationsCompanion copyWith( - {Value? channelCid, - Value? messageId, - Value? userId, - Value? latitude, - Value? longitude, - Value? createdByDeviceId, - Value? endAt, - Value? createdAt, - Value? updatedAt, - Value? rowid}) { + LocationsCompanion copyWith({ + Value? channelCid, + Value? messageId, + Value? userId, + Value? latitude, + Value? longitude, + Value? createdByDeviceId, + Value? endAt, + Value? createdAt, + Value? updatedAt, + Value? rowid, + }) { return LocationsCompanion( channelCid: channelCid ?? this.channelCid, messageId: messageId ?? this.messageId, @@ -3419,8 +3394,7 @@ class LocationsCompanion extends UpdateCompanion { } } -class $PinnedMessagesTable extends PinnedMessages - with TableInfo<$PinnedMessagesTable, PinnedMessageEntity> { +class $PinnedMessagesTable extends PinnedMessages with TableInfo<$PinnedMessagesTable, PinnedMessageEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; @@ -3428,252 +3402,335 @@ class $PinnedMessagesTable extends PinnedMessages static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _messageTextMeta = - const VerificationMeta('messageText'); + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _messageTextMeta = const VerificationMeta('messageText'); @override late final GeneratedColumn messageText = GeneratedColumn( - 'message_text', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - @override - late final GeneratedColumnWithTypeConverter, String> - attachments = GeneratedColumn('attachments', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>( - $PinnedMessagesTable.$converterattachments); + 'message_text', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + late final GeneratedColumnWithTypeConverter, String> attachments = GeneratedColumn( + 'attachments', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($PinnedMessagesTable.$converterattachments); static const VerificationMeta _stateMeta = const VerificationMeta('state'); @override late final GeneratedColumn state = GeneratedColumn( - 'state', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + 'state', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); static const VerificationMeta _typeMeta = const VerificationMeta('type'); @override late final GeneratedColumn type = GeneratedColumn( - 'type', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: const Constant('regular')); - @override - late final GeneratedColumnWithTypeConverter, String> - mentionedUsers = GeneratedColumn( - 'mentioned_users', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>( - $PinnedMessagesTable.$convertermentionedUsers); - @override - late final GeneratedColumnWithTypeConverter?, - String> reactionGroups = GeneratedColumn( - 'reaction_groups', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $PinnedMessagesTable.$converterreactionGroupsn); - static const VerificationMeta _parentIdMeta = - const VerificationMeta('parentId'); + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('regular'), + ); + @override + late final GeneratedColumnWithTypeConverter, String> mentionedUsers = GeneratedColumn( + 'mentioned_users', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($PinnedMessagesTable.$convertermentionedUsers); + @override + late final GeneratedColumnWithTypeConverter?, String> reactionGroups = + GeneratedColumn( + 'reaction_groups', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($PinnedMessagesTable.$converterreactionGroupsn); + static const VerificationMeta _parentIdMeta = const VerificationMeta('parentId'); @override late final GeneratedColumn parentId = GeneratedColumn( - 'parent_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _quotedMessageIdMeta = - const VerificationMeta('quotedMessageId'); + 'parent_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _quotedMessageIdMeta = const VerificationMeta('quotedMessageId'); @override late final GeneratedColumn quotedMessageId = GeneratedColumn( - 'quoted_message_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'quoted_message_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); static const VerificationMeta _pollIdMeta = const VerificationMeta('pollId'); @override late final GeneratedColumn pollId = GeneratedColumn( - 'poll_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _replyCountMeta = - const VerificationMeta('replyCount'); + 'poll_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _replyCountMeta = const VerificationMeta('replyCount'); @override late final GeneratedColumn replyCount = GeneratedColumn( - 'reply_count', aliasedName, true, - type: DriftSqlType.int, requiredDuringInsert: false); - static const VerificationMeta _showInChannelMeta = - const VerificationMeta('showInChannel'); + 'reply_count', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + static const VerificationMeta _showInChannelMeta = const VerificationMeta('showInChannel'); @override late final GeneratedColumn showInChannel = GeneratedColumn( - 'show_in_channel', aliasedName, true, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("show_in_channel" IN (0, 1))')); - static const VerificationMeta _shadowedMeta = - const VerificationMeta('shadowed'); + 'show_in_channel', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("show_in_channel" IN (0, 1))'), + ); + static const VerificationMeta _shadowedMeta = const VerificationMeta('shadowed'); @override late final GeneratedColumn shadowed = GeneratedColumn( - 'shadowed', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("shadowed" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _commandMeta = - const VerificationMeta('command'); + 'shadowed', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("shadowed" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _commandMeta = const VerificationMeta('command'); @override late final GeneratedColumn command = GeneratedColumn( - 'command', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _localCreatedAtMeta = - const VerificationMeta('localCreatedAt'); - @override - late final GeneratedColumn localCreatedAt = - GeneratedColumn('local_created_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _remoteCreatedAtMeta = - const VerificationMeta('remoteCreatedAt'); - @override - late final GeneratedColumn remoteCreatedAt = - GeneratedColumn('remote_created_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _localUpdatedAtMeta = - const VerificationMeta('localUpdatedAt'); - @override - late final GeneratedColumn localUpdatedAt = - GeneratedColumn('local_updated_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _remoteUpdatedAtMeta = - const VerificationMeta('remoteUpdatedAt'); - @override - late final GeneratedColumn remoteUpdatedAt = - GeneratedColumn('remote_updated_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _localDeletedAtMeta = - const VerificationMeta('localDeletedAt'); - @override - late final GeneratedColumn localDeletedAt = - GeneratedColumn('local_deleted_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _remoteDeletedAtMeta = - const VerificationMeta('remoteDeletedAt'); - @override - late final GeneratedColumn remoteDeletedAt = - GeneratedColumn('remote_deleted_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _deletedForMeMeta = - const VerificationMeta('deletedForMe'); + 'command', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _localCreatedAtMeta = const VerificationMeta('localCreatedAt'); + @override + late final GeneratedColumn localCreatedAt = GeneratedColumn( + 'local_created_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _remoteCreatedAtMeta = const VerificationMeta('remoteCreatedAt'); + @override + late final GeneratedColumn remoteCreatedAt = GeneratedColumn( + 'remote_created_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _localUpdatedAtMeta = const VerificationMeta('localUpdatedAt'); + @override + late final GeneratedColumn localUpdatedAt = GeneratedColumn( + 'local_updated_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _remoteUpdatedAtMeta = const VerificationMeta('remoteUpdatedAt'); + @override + late final GeneratedColumn remoteUpdatedAt = GeneratedColumn( + 'remote_updated_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _localDeletedAtMeta = const VerificationMeta('localDeletedAt'); + @override + late final GeneratedColumn localDeletedAt = GeneratedColumn( + 'local_deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _remoteDeletedAtMeta = const VerificationMeta('remoteDeletedAt'); + @override + late final GeneratedColumn remoteDeletedAt = GeneratedColumn( + 'remote_deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _deletedForMeMeta = const VerificationMeta('deletedForMe'); @override late final GeneratedColumn deletedForMe = GeneratedColumn( - 'deleted_for_me', aliasedName, true, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("deleted_for_me" IN (0, 1))')); - static const VerificationMeta _messageTextUpdatedAtMeta = - const VerificationMeta('messageTextUpdatedAt'); - @override - late final GeneratedColumn messageTextUpdatedAt = - GeneratedColumn('message_text_updated_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); + 'deleted_for_me', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("deleted_for_me" IN (0, 1))'), + ); + static const VerificationMeta _messageTextUpdatedAtMeta = const VerificationMeta('messageTextUpdatedAt'); + @override + late final GeneratedColumn messageTextUpdatedAt = GeneratedColumn( + 'message_text_updated_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); @override late final GeneratedColumn userId = GeneratedColumn( - 'user_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _channelRoleMeta = - const VerificationMeta('channelRole'); + 'user_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _channelRoleMeta = const VerificationMeta('channelRole'); @override late final GeneratedColumn channelRole = GeneratedColumn( - 'channel_role', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'channel_role', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); static const VerificationMeta _pinnedMeta = const VerificationMeta('pinned'); @override late final GeneratedColumn pinned = GeneratedColumn( - 'pinned', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("pinned" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _pinnedAtMeta = - const VerificationMeta('pinnedAt'); + 'pinned', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("pinned" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _pinnedAtMeta = const VerificationMeta('pinnedAt'); @override late final GeneratedColumn pinnedAt = GeneratedColumn( - 'pinned_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _pinExpiresMeta = - const VerificationMeta('pinExpires'); + 'pinned_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _pinExpiresMeta = const VerificationMeta('pinExpires'); @override late final GeneratedColumn pinExpires = GeneratedColumn( - 'pin_expires', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _pinnedByUserIdMeta = - const VerificationMeta('pinnedByUserId'); + 'pin_expires', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _pinnedByUserIdMeta = const VerificationMeta('pinnedByUserId'); @override late final GeneratedColumn pinnedByUserId = GeneratedColumn( - 'pinned_by_user_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _channelCidMeta = - const VerificationMeta('channelCid'); + 'pinned_by_user_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); @override late final GeneratedColumn channelCid = GeneratedColumn( - 'channel_cid', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - @override - late final GeneratedColumnWithTypeConverter?, String> - i18n = GeneratedColumn('i18n', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $PinnedMessagesTable.$converteri18n); - @override - late final GeneratedColumnWithTypeConverter?, String> - restrictedVisibility = GeneratedColumn( - 'restricted_visibility', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $PinnedMessagesTable.$converterrestrictedVisibilityn); - @override - late final GeneratedColumnWithTypeConverter?, String> - extraData = GeneratedColumn('extra_data', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $PinnedMessagesTable.$converterextraDatan); + 'channel_cid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + late final GeneratedColumnWithTypeConverter?, String> i18n = GeneratedColumn( + 'i18n', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($PinnedMessagesTable.$converteri18n); + @override + late final GeneratedColumnWithTypeConverter?, String> restrictedVisibility = GeneratedColumn( + 'restricted_visibility', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($PinnedMessagesTable.$converterrestrictedVisibilityn); + @override + late final GeneratedColumnWithTypeConverter?, String> extraData = GeneratedColumn( + 'extra_data', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($PinnedMessagesTable.$converterextraDatan); @override List get $columns => [ - id, - messageText, - attachments, - state, - type, - mentionedUsers, - reactionGroups, - parentId, - quotedMessageId, - pollId, - replyCount, - showInChannel, - shadowed, - command, - localCreatedAt, - remoteCreatedAt, - localUpdatedAt, - remoteUpdatedAt, - localDeletedAt, - remoteDeletedAt, - deletedForMe, - messageTextUpdatedAt, - userId, - channelRole, - pinned, - pinnedAt, - pinExpires, - pinnedByUserId, - channelCid, - i18n, - restrictedVisibility, - extraData - ]; + id, + messageText, + attachments, + state, + type, + mentionedUsers, + reactionGroups, + parentId, + quotedMessageId, + pollId, + replyCount, + showInChannel, + shadowed, + command, + localCreatedAt, + remoteCreatedAt, + localUpdatedAt, + remoteUpdatedAt, + localDeletedAt, + remoteDeletedAt, + deletedForMe, + messageTextUpdatedAt, + userId, + channelRole, + pinned, + pinnedAt, + pinExpires, + pinnedByUserId, + channelCid, + i18n, + restrictedVisibility, + extraData, + ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'pinned_messages'; @override - VerificationContext validateIntegrity( - Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { @@ -3682,138 +3739,111 @@ class $PinnedMessagesTable extends PinnedMessages context.missing(_idMeta); } if (data.containsKey('message_text')) { - context.handle( - _messageTextMeta, - messageText.isAcceptableOrUnknown( - data['message_text']!, _messageTextMeta)); + context.handle(_messageTextMeta, messageText.isAcceptableOrUnknown(data['message_text']!, _messageTextMeta)); } if (data.containsKey('state')) { - context.handle( - _stateMeta, state.isAcceptableOrUnknown(data['state']!, _stateMeta)); + context.handle(_stateMeta, state.isAcceptableOrUnknown(data['state']!, _stateMeta)); } else if (isInserting) { context.missing(_stateMeta); } if (data.containsKey('type')) { - context.handle( - _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + context.handle(_typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); } if (data.containsKey('parent_id')) { - context.handle(_parentIdMeta, - parentId.isAcceptableOrUnknown(data['parent_id']!, _parentIdMeta)); + context.handle(_parentIdMeta, parentId.isAcceptableOrUnknown(data['parent_id']!, _parentIdMeta)); } if (data.containsKey('quoted_message_id')) { context.handle( - _quotedMessageIdMeta, - quotedMessageId.isAcceptableOrUnknown( - data['quoted_message_id']!, _quotedMessageIdMeta)); + _quotedMessageIdMeta, + quotedMessageId.isAcceptableOrUnknown(data['quoted_message_id']!, _quotedMessageIdMeta), + ); } if (data.containsKey('poll_id')) { - context.handle(_pollIdMeta, - pollId.isAcceptableOrUnknown(data['poll_id']!, _pollIdMeta)); + context.handle(_pollIdMeta, pollId.isAcceptableOrUnknown(data['poll_id']!, _pollIdMeta)); } if (data.containsKey('reply_count')) { - context.handle( - _replyCountMeta, - replyCount.isAcceptableOrUnknown( - data['reply_count']!, _replyCountMeta)); + context.handle(_replyCountMeta, replyCount.isAcceptableOrUnknown(data['reply_count']!, _replyCountMeta)); } if (data.containsKey('show_in_channel')) { context.handle( - _showInChannelMeta, - showInChannel.isAcceptableOrUnknown( - data['show_in_channel']!, _showInChannelMeta)); + _showInChannelMeta, + showInChannel.isAcceptableOrUnknown(data['show_in_channel']!, _showInChannelMeta), + ); } if (data.containsKey('shadowed')) { - context.handle(_shadowedMeta, - shadowed.isAcceptableOrUnknown(data['shadowed']!, _shadowedMeta)); + context.handle(_shadowedMeta, shadowed.isAcceptableOrUnknown(data['shadowed']!, _shadowedMeta)); } if (data.containsKey('command')) { - context.handle(_commandMeta, - command.isAcceptableOrUnknown(data['command']!, _commandMeta)); + context.handle(_commandMeta, command.isAcceptableOrUnknown(data['command']!, _commandMeta)); } if (data.containsKey('local_created_at')) { context.handle( - _localCreatedAtMeta, - localCreatedAt.isAcceptableOrUnknown( - data['local_created_at']!, _localCreatedAtMeta)); + _localCreatedAtMeta, + localCreatedAt.isAcceptableOrUnknown(data['local_created_at']!, _localCreatedAtMeta), + ); } if (data.containsKey('remote_created_at')) { context.handle( - _remoteCreatedAtMeta, - remoteCreatedAt.isAcceptableOrUnknown( - data['remote_created_at']!, _remoteCreatedAtMeta)); + _remoteCreatedAtMeta, + remoteCreatedAt.isAcceptableOrUnknown(data['remote_created_at']!, _remoteCreatedAtMeta), + ); } if (data.containsKey('local_updated_at')) { context.handle( - _localUpdatedAtMeta, - localUpdatedAt.isAcceptableOrUnknown( - data['local_updated_at']!, _localUpdatedAtMeta)); + _localUpdatedAtMeta, + localUpdatedAt.isAcceptableOrUnknown(data['local_updated_at']!, _localUpdatedAtMeta), + ); } if (data.containsKey('remote_updated_at')) { context.handle( - _remoteUpdatedAtMeta, - remoteUpdatedAt.isAcceptableOrUnknown( - data['remote_updated_at']!, _remoteUpdatedAtMeta)); + _remoteUpdatedAtMeta, + remoteUpdatedAt.isAcceptableOrUnknown(data['remote_updated_at']!, _remoteUpdatedAtMeta), + ); } if (data.containsKey('local_deleted_at')) { context.handle( - _localDeletedAtMeta, - localDeletedAt.isAcceptableOrUnknown( - data['local_deleted_at']!, _localDeletedAtMeta)); + _localDeletedAtMeta, + localDeletedAt.isAcceptableOrUnknown(data['local_deleted_at']!, _localDeletedAtMeta), + ); } if (data.containsKey('remote_deleted_at')) { context.handle( - _remoteDeletedAtMeta, - remoteDeletedAt.isAcceptableOrUnknown( - data['remote_deleted_at']!, _remoteDeletedAtMeta)); + _remoteDeletedAtMeta, + remoteDeletedAt.isAcceptableOrUnknown(data['remote_deleted_at']!, _remoteDeletedAtMeta), + ); } if (data.containsKey('deleted_for_me')) { - context.handle( - _deletedForMeMeta, - deletedForMe.isAcceptableOrUnknown( - data['deleted_for_me']!, _deletedForMeMeta)); + context.handle(_deletedForMeMeta, deletedForMe.isAcceptableOrUnknown(data['deleted_for_me']!, _deletedForMeMeta)); } if (data.containsKey('message_text_updated_at')) { context.handle( - _messageTextUpdatedAtMeta, - messageTextUpdatedAt.isAcceptableOrUnknown( - data['message_text_updated_at']!, _messageTextUpdatedAtMeta)); + _messageTextUpdatedAtMeta, + messageTextUpdatedAt.isAcceptableOrUnknown(data['message_text_updated_at']!, _messageTextUpdatedAtMeta), + ); } if (data.containsKey('user_id')) { - context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + context.handle(_userIdMeta, userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } if (data.containsKey('channel_role')) { - context.handle( - _channelRoleMeta, - channelRole.isAcceptableOrUnknown( - data['channel_role']!, _channelRoleMeta)); + context.handle(_channelRoleMeta, channelRole.isAcceptableOrUnknown(data['channel_role']!, _channelRoleMeta)); } if (data.containsKey('pinned')) { - context.handle(_pinnedMeta, - pinned.isAcceptableOrUnknown(data['pinned']!, _pinnedMeta)); + context.handle(_pinnedMeta, pinned.isAcceptableOrUnknown(data['pinned']!, _pinnedMeta)); } if (data.containsKey('pinned_at')) { - context.handle(_pinnedAtMeta, - pinnedAt.isAcceptableOrUnknown(data['pinned_at']!, _pinnedAtMeta)); + context.handle(_pinnedAtMeta, pinnedAt.isAcceptableOrUnknown(data['pinned_at']!, _pinnedAtMeta)); } if (data.containsKey('pin_expires')) { - context.handle( - _pinExpiresMeta, - pinExpires.isAcceptableOrUnknown( - data['pin_expires']!, _pinExpiresMeta)); + context.handle(_pinExpiresMeta, pinExpires.isAcceptableOrUnknown(data['pin_expires']!, _pinExpiresMeta)); } if (data.containsKey('pinned_by_user_id')) { context.handle( - _pinnedByUserIdMeta, - pinnedByUserId.isAcceptableOrUnknown( - data['pinned_by_user_id']!, _pinnedByUserIdMeta)); + _pinnedByUserIdMeta, + pinnedByUserId.isAcceptableOrUnknown(data['pinned_by_user_id']!, _pinnedByUserIdMeta), + ); } if (data.containsKey('channel_cid')) { - context.handle( - _channelCidMeta, - channelCid.isAcceptableOrUnknown( - data['channel_cid']!, _channelCidMeta)); + context.handle(_channelCidMeta, channelCid.isAcceptableOrUnknown(data['channel_cid']!, _channelCidMeta)); } else if (isInserting) { context.missing(_channelCidMeta); } @@ -3826,77 +3856,77 @@ class $PinnedMessagesTable extends PinnedMessages PinnedMessageEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return PinnedMessageEntity( - id: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}id'])!, - messageText: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}message_text']), + id: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}id'])!, + messageText: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}message_text']), attachments: $PinnedMessagesTable.$converterattachments.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}attachments'])!), - state: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}state'])!, - type: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}type'])!, + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}attachments'])!, + ), + state: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}state'])!, + type: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}type'])!, mentionedUsers: $PinnedMessagesTable.$convertermentionedUsers.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}mentioned_users'])!), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}mentioned_users'])!, + ), reactionGroups: $PinnedMessagesTable.$converterreactionGroupsn.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}reaction_groups'])), - parentId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}parent_id']), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}reaction_groups']), + ), + parentId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}parent_id']), quotedMessageId: attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}quoted_message_id']), - pollId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}poll_id']), - replyCount: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}reply_count']), - showInChannel: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}show_in_channel']), - shadowed: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}shadowed'])!, - command: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}command']), + DriftSqlType.string, + data['${effectivePrefix}quoted_message_id'], + ), + pollId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}poll_id']), + replyCount: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}reply_count']), + showInChannel: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}show_in_channel']), + shadowed: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}shadowed'])!, + command: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}command']), localCreatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}local_created_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}local_created_at'], + ), remoteCreatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}remote_created_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}remote_created_at'], + ), localUpdatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}local_updated_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}local_updated_at'], + ), remoteUpdatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}remote_updated_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}remote_updated_at'], + ), localDeletedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}local_deleted_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}local_deleted_at'], + ), remoteDeletedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}remote_deleted_at']), - deletedForMe: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}deleted_for_me']), + DriftSqlType.dateTime, + data['${effectivePrefix}remote_deleted_at'], + ), + deletedForMe: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}deleted_for_me']), messageTextUpdatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, - data['${effectivePrefix}message_text_updated_at']), - userId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}user_id']), - channelRole: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_role']), - pinned: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}pinned'])!, - pinnedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}pinned_at']), - pinExpires: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}pin_expires']), + DriftSqlType.dateTime, + data['${effectivePrefix}message_text_updated_at'], + ), + userId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}user_id']), + channelRole: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_role']), + pinned: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}pinned'])!, + pinnedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}pinned_at']), + pinExpires: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}pin_expires']), pinnedByUserId: attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}pinned_by_user_id']), - channelCid: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, - i18n: $PinnedMessagesTable.$converteri18n.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}i18n'])), - restrictedVisibility: $PinnedMessagesTable.$converterrestrictedVisibilityn - .fromSql(attachedDatabase.typeMapping.read(DriftSqlType.string, - data['${effectivePrefix}restricted_visibility'])), + DriftSqlType.string, + data['${effectivePrefix}pinned_by_user_id'], + ), + channelCid: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, + i18n: $PinnedMessagesTable.$converteri18n.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}i18n']), + ), + restrictedVisibility: $PinnedMessagesTable.$converterrestrictedVisibilityn.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}restricted_visibility']), + ), extraData: $PinnedMessagesTable.$converterextraDatan.fromSql( - attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}extra_data']), + ), ); } @@ -3905,29 +3935,24 @@ class $PinnedMessagesTable extends PinnedMessages return $PinnedMessagesTable(attachedDatabase, alias); } - static TypeConverter, String> $converterattachments = - ListConverter(); - static TypeConverter, String> $convertermentionedUsers = - ListConverter(); - static TypeConverter, String> - $converterreactionGroups = ReactionGroupsConverter(); - static TypeConverter?, String?> - $converterreactionGroupsn = - NullAwareTypeConverter.wrap($converterreactionGroups); - static TypeConverter?, String?> $converteri18n = - NullableMapConverter(); - static TypeConverter, String> $converterrestrictedVisibility = - ListConverter(); - static TypeConverter?, String?> $converterrestrictedVisibilityn = - NullAwareTypeConverter.wrap($converterrestrictedVisibility); - static TypeConverter, String> $converterextraData = - MapConverter(); - static TypeConverter?, String?> $converterextraDatan = - NullAwareTypeConverter.wrap($converterextraData); + static TypeConverter, String> $converterattachments = ListConverter(); + static TypeConverter, String> $convertermentionedUsers = ListConverter(); + static TypeConverter, String> $converterreactionGroups = ReactionGroupsConverter(); + static TypeConverter?, String?> $converterreactionGroupsn = NullAwareTypeConverter.wrap( + $converterreactionGroups, + ); + static TypeConverter?, String?> $converteri18n = NullableMapConverter(); + static TypeConverter, String> $converterrestrictedVisibility = ListConverter(); + static TypeConverter?, String?> $converterrestrictedVisibilityn = NullAwareTypeConverter.wrap( + $converterrestrictedVisibility, + ); + static TypeConverter, String> $converterextraData = MapConverter(); + static TypeConverter?, String?> $converterextraDatan = NullAwareTypeConverter.wrap( + $converterextraData, + ); } -class PinnedMessageEntity extends DataClass - implements Insertable { +class PinnedMessageEntity extends DataClass implements Insertable { /// The message id final String id; @@ -4024,39 +4049,40 @@ class PinnedMessageEntity extends DataClass /// Message custom extraData final Map? extraData; - const PinnedMessageEntity( - {required this.id, - this.messageText, - required this.attachments, - required this.state, - required this.type, - required this.mentionedUsers, - this.reactionGroups, - this.parentId, - this.quotedMessageId, - this.pollId, - this.replyCount, - this.showInChannel, - required this.shadowed, - this.command, - this.localCreatedAt, - this.remoteCreatedAt, - this.localUpdatedAt, - this.remoteUpdatedAt, - this.localDeletedAt, - this.remoteDeletedAt, - this.deletedForMe, - this.messageTextUpdatedAt, - this.userId, - this.channelRole, - required this.pinned, - this.pinnedAt, - this.pinExpires, - this.pinnedByUserId, - required this.channelCid, - this.i18n, - this.restrictedVisibility, - this.extraData}); + const PinnedMessageEntity({ + required this.id, + this.messageText, + required this.attachments, + required this.state, + required this.type, + required this.mentionedUsers, + this.reactionGroups, + this.parentId, + this.quotedMessageId, + this.pollId, + this.replyCount, + this.showInChannel, + required this.shadowed, + this.command, + this.localCreatedAt, + this.remoteCreatedAt, + this.localUpdatedAt, + this.remoteUpdatedAt, + this.localDeletedAt, + this.remoteDeletedAt, + this.deletedForMe, + this.messageTextUpdatedAt, + this.userId, + this.channelRole, + required this.pinned, + this.pinnedAt, + this.pinExpires, + this.pinnedByUserId, + required this.channelCid, + this.i18n, + this.restrictedVisibility, + this.extraData, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -4065,18 +4091,15 @@ class PinnedMessageEntity extends DataClass map['message_text'] = Variable(messageText); } { - map['attachments'] = Variable( - $PinnedMessagesTable.$converterattachments.toSql(attachments)); + map['attachments'] = Variable($PinnedMessagesTable.$converterattachments.toSql(attachments)); } map['state'] = Variable(state); map['type'] = Variable(type); { - map['mentioned_users'] = Variable( - $PinnedMessagesTable.$convertermentionedUsers.toSql(mentionedUsers)); + map['mentioned_users'] = Variable($PinnedMessagesTable.$convertermentionedUsers.toSql(mentionedUsers)); } if (!nullToAbsent || reactionGroups != null) { - map['reaction_groups'] = Variable( - $PinnedMessagesTable.$converterreactionGroupsn.toSql(reactionGroups)); + map['reaction_groups'] = Variable($PinnedMessagesTable.$converterreactionGroupsn.toSql(reactionGroups)); } if (!nullToAbsent || parentId != null) { map['parent_id'] = Variable(parentId); @@ -4139,23 +4162,20 @@ class PinnedMessageEntity extends DataClass } map['channel_cid'] = Variable(channelCid); if (!nullToAbsent || i18n != null) { - map['i18n'] = - Variable($PinnedMessagesTable.$converteri18n.toSql(i18n)); + map['i18n'] = Variable($PinnedMessagesTable.$converteri18n.toSql(i18n)); } if (!nullToAbsent || restrictedVisibility != null) { - map['restricted_visibility'] = Variable($PinnedMessagesTable - .$converterrestrictedVisibilityn - .toSql(restrictedVisibility)); + map['restricted_visibility'] = Variable( + $PinnedMessagesTable.$converterrestrictedVisibilityn.toSql(restrictedVisibility), + ); } if (!nullToAbsent || extraData != null) { - map['extra_data'] = Variable( - $PinnedMessagesTable.$converterextraDatan.toSql(extraData)); + map['extra_data'] = Variable($PinnedMessagesTable.$converterextraDatan.toSql(extraData)); } return map; } - factory PinnedMessageEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory PinnedMessageEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return PinnedMessageEntity( id: serializer.fromJson(json['id']), @@ -4164,8 +4184,7 @@ class PinnedMessageEntity extends DataClass state: serializer.fromJson(json['state']), type: serializer.fromJson(json['type']), mentionedUsers: serializer.fromJson>(json['mentionedUsers']), - reactionGroups: serializer - .fromJson?>(json['reactionGroups']), + reactionGroups: serializer.fromJson?>(json['reactionGroups']), parentId: serializer.fromJson(json['parentId']), quotedMessageId: serializer.fromJson(json['quotedMessageId']), pollId: serializer.fromJson(json['pollId']), @@ -4180,8 +4199,7 @@ class PinnedMessageEntity extends DataClass localDeletedAt: serializer.fromJson(json['localDeletedAt']), remoteDeletedAt: serializer.fromJson(json['remoteDeletedAt']), deletedForMe: serializer.fromJson(json['deletedForMe']), - messageTextUpdatedAt: - serializer.fromJson(json['messageTextUpdatedAt']), + messageTextUpdatedAt: serializer.fromJson(json['messageTextUpdatedAt']), userId: serializer.fromJson(json['userId']), channelRole: serializer.fromJson(json['channelRole']), pinned: serializer.fromJson(json['pinned']), @@ -4190,8 +4208,7 @@ class PinnedMessageEntity extends DataClass pinnedByUserId: serializer.fromJson(json['pinnedByUserId']), channelCid: serializer.fromJson(json['channelCid']), i18n: serializer.fromJson?>(json['i18n']), - restrictedVisibility: - serializer.fromJson?>(json['restrictedVisibility']), + restrictedVisibility: serializer.fromJson?>(json['restrictedVisibility']), extraData: serializer.fromJson?>(json['extraData']), ); } @@ -4205,8 +4222,7 @@ class PinnedMessageEntity extends DataClass 'state': serializer.toJson(state), 'type': serializer.toJson(type), 'mentionedUsers': serializer.toJson>(mentionedUsers), - 'reactionGroups': - serializer.toJson?>(reactionGroups), + 'reactionGroups': serializer.toJson?>(reactionGroups), 'parentId': serializer.toJson(parentId), 'quotedMessageId': serializer.toJson(quotedMessageId), 'pollId': serializer.toJson(pollId), @@ -4221,8 +4237,7 @@ class PinnedMessageEntity extends DataClass 'localDeletedAt': serializer.toJson(localDeletedAt), 'remoteDeletedAt': serializer.toJson(remoteDeletedAt), 'deletedForMe': serializer.toJson(deletedForMe), - 'messageTextUpdatedAt': - serializer.toJson(messageTextUpdatedAt), + 'messageTextUpdatedAt': serializer.toJson(messageTextUpdatedAt), 'userId': serializer.toJson(userId), 'channelRole': serializer.toJson(channelRole), 'pinned': serializer.toJson(pinned), @@ -4231,162 +4246,111 @@ class PinnedMessageEntity extends DataClass 'pinnedByUserId': serializer.toJson(pinnedByUserId), 'channelCid': serializer.toJson(channelCid), 'i18n': serializer.toJson?>(i18n), - 'restrictedVisibility': - serializer.toJson?>(restrictedVisibility), + 'restrictedVisibility': serializer.toJson?>(restrictedVisibility), 'extraData': serializer.toJson?>(extraData), }; } - PinnedMessageEntity copyWith( - {String? id, - Value messageText = const Value.absent(), - List? attachments, - String? state, - String? type, - List? mentionedUsers, - Value?> reactionGroups = - const Value.absent(), - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value pollId = const Value.absent(), - Value replyCount = const Value.absent(), - Value showInChannel = const Value.absent(), - bool? shadowed, - Value command = const Value.absent(), - Value localCreatedAt = const Value.absent(), - Value remoteCreatedAt = const Value.absent(), - Value localUpdatedAt = const Value.absent(), - Value remoteUpdatedAt = const Value.absent(), - Value localDeletedAt = const Value.absent(), - Value remoteDeletedAt = const Value.absent(), - Value deletedForMe = const Value.absent(), - Value messageTextUpdatedAt = const Value.absent(), - Value userId = const Value.absent(), - Value channelRole = const Value.absent(), - bool? pinned, - Value pinnedAt = const Value.absent(), - Value pinExpires = const Value.absent(), - Value pinnedByUserId = const Value.absent(), - String? channelCid, - Value?> i18n = const Value.absent(), - Value?> restrictedVisibility = const Value.absent(), - Value?> extraData = const Value.absent()}) => - PinnedMessageEntity( - id: id ?? this.id, - messageText: messageText.present ? messageText.value : this.messageText, - attachments: attachments ?? this.attachments, - state: state ?? this.state, - type: type ?? this.type, - mentionedUsers: mentionedUsers ?? this.mentionedUsers, - reactionGroups: - reactionGroups.present ? reactionGroups.value : this.reactionGroups, - parentId: parentId.present ? parentId.value : this.parentId, - quotedMessageId: quotedMessageId.present - ? quotedMessageId.value - : this.quotedMessageId, - pollId: pollId.present ? pollId.value : this.pollId, - replyCount: replyCount.present ? replyCount.value : this.replyCount, - showInChannel: - showInChannel.present ? showInChannel.value : this.showInChannel, - shadowed: shadowed ?? this.shadowed, - command: command.present ? command.value : this.command, - localCreatedAt: - localCreatedAt.present ? localCreatedAt.value : this.localCreatedAt, - remoteCreatedAt: remoteCreatedAt.present - ? remoteCreatedAt.value - : this.remoteCreatedAt, - localUpdatedAt: - localUpdatedAt.present ? localUpdatedAt.value : this.localUpdatedAt, - remoteUpdatedAt: remoteUpdatedAt.present - ? remoteUpdatedAt.value - : this.remoteUpdatedAt, - localDeletedAt: - localDeletedAt.present ? localDeletedAt.value : this.localDeletedAt, - remoteDeletedAt: remoteDeletedAt.present - ? remoteDeletedAt.value - : this.remoteDeletedAt, - deletedForMe: - deletedForMe.present ? deletedForMe.value : this.deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt.present - ? messageTextUpdatedAt.value - : this.messageTextUpdatedAt, - userId: userId.present ? userId.value : this.userId, - channelRole: channelRole.present ? channelRole.value : this.channelRole, - pinned: pinned ?? this.pinned, - pinnedAt: pinnedAt.present ? pinnedAt.value : this.pinnedAt, - pinExpires: pinExpires.present ? pinExpires.value : this.pinExpires, - pinnedByUserId: - pinnedByUserId.present ? pinnedByUserId.value : this.pinnedByUserId, - channelCid: channelCid ?? this.channelCid, - i18n: i18n.present ? i18n.value : this.i18n, - restrictedVisibility: restrictedVisibility.present - ? restrictedVisibility.value - : this.restrictedVisibility, - extraData: extraData.present ? extraData.value : this.extraData, - ); + PinnedMessageEntity copyWith({ + String? id, + Value messageText = const Value.absent(), + List? attachments, + String? state, + String? type, + List? mentionedUsers, + Value?> reactionGroups = const Value.absent(), + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value pollId = const Value.absent(), + Value replyCount = const Value.absent(), + Value showInChannel = const Value.absent(), + bool? shadowed, + Value command = const Value.absent(), + Value localCreatedAt = const Value.absent(), + Value remoteCreatedAt = const Value.absent(), + Value localUpdatedAt = const Value.absent(), + Value remoteUpdatedAt = const Value.absent(), + Value localDeletedAt = const Value.absent(), + Value remoteDeletedAt = const Value.absent(), + Value deletedForMe = const Value.absent(), + Value messageTextUpdatedAt = const Value.absent(), + Value userId = const Value.absent(), + Value channelRole = const Value.absent(), + bool? pinned, + Value pinnedAt = const Value.absent(), + Value pinExpires = const Value.absent(), + Value pinnedByUserId = const Value.absent(), + String? channelCid, + Value?> i18n = const Value.absent(), + Value?> restrictedVisibility = const Value.absent(), + Value?> extraData = const Value.absent(), + }) => PinnedMessageEntity( + id: id ?? this.id, + messageText: messageText.present ? messageText.value : this.messageText, + attachments: attachments ?? this.attachments, + state: state ?? this.state, + type: type ?? this.type, + mentionedUsers: mentionedUsers ?? this.mentionedUsers, + reactionGroups: reactionGroups.present ? reactionGroups.value : this.reactionGroups, + parentId: parentId.present ? parentId.value : this.parentId, + quotedMessageId: quotedMessageId.present ? quotedMessageId.value : this.quotedMessageId, + pollId: pollId.present ? pollId.value : this.pollId, + replyCount: replyCount.present ? replyCount.value : this.replyCount, + showInChannel: showInChannel.present ? showInChannel.value : this.showInChannel, + shadowed: shadowed ?? this.shadowed, + command: command.present ? command.value : this.command, + localCreatedAt: localCreatedAt.present ? localCreatedAt.value : this.localCreatedAt, + remoteCreatedAt: remoteCreatedAt.present ? remoteCreatedAt.value : this.remoteCreatedAt, + localUpdatedAt: localUpdatedAt.present ? localUpdatedAt.value : this.localUpdatedAt, + remoteUpdatedAt: remoteUpdatedAt.present ? remoteUpdatedAt.value : this.remoteUpdatedAt, + localDeletedAt: localDeletedAt.present ? localDeletedAt.value : this.localDeletedAt, + remoteDeletedAt: remoteDeletedAt.present ? remoteDeletedAt.value : this.remoteDeletedAt, + deletedForMe: deletedForMe.present ? deletedForMe.value : this.deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt.present ? messageTextUpdatedAt.value : this.messageTextUpdatedAt, + userId: userId.present ? userId.value : this.userId, + channelRole: channelRole.present ? channelRole.value : this.channelRole, + pinned: pinned ?? this.pinned, + pinnedAt: pinnedAt.present ? pinnedAt.value : this.pinnedAt, + pinExpires: pinExpires.present ? pinExpires.value : this.pinExpires, + pinnedByUserId: pinnedByUserId.present ? pinnedByUserId.value : this.pinnedByUserId, + channelCid: channelCid ?? this.channelCid, + i18n: i18n.present ? i18n.value : this.i18n, + restrictedVisibility: restrictedVisibility.present ? restrictedVisibility.value : this.restrictedVisibility, + extraData: extraData.present ? extraData.value : this.extraData, + ); PinnedMessageEntity copyWithCompanion(PinnedMessagesCompanion data) { return PinnedMessageEntity( id: data.id.present ? data.id.value : this.id, - messageText: - data.messageText.present ? data.messageText.value : this.messageText, - attachments: - data.attachments.present ? data.attachments.value : this.attachments, + messageText: data.messageText.present ? data.messageText.value : this.messageText, + attachments: data.attachments.present ? data.attachments.value : this.attachments, state: data.state.present ? data.state.value : this.state, type: data.type.present ? data.type.value : this.type, - mentionedUsers: data.mentionedUsers.present - ? data.mentionedUsers.value - : this.mentionedUsers, - reactionGroups: data.reactionGroups.present - ? data.reactionGroups.value - : this.reactionGroups, + mentionedUsers: data.mentionedUsers.present ? data.mentionedUsers.value : this.mentionedUsers, + reactionGroups: data.reactionGroups.present ? data.reactionGroups.value : this.reactionGroups, parentId: data.parentId.present ? data.parentId.value : this.parentId, - quotedMessageId: data.quotedMessageId.present - ? data.quotedMessageId.value - : this.quotedMessageId, + quotedMessageId: data.quotedMessageId.present ? data.quotedMessageId.value : this.quotedMessageId, pollId: data.pollId.present ? data.pollId.value : this.pollId, - replyCount: - data.replyCount.present ? data.replyCount.value : this.replyCount, - showInChannel: data.showInChannel.present - ? data.showInChannel.value - : this.showInChannel, + replyCount: data.replyCount.present ? data.replyCount.value : this.replyCount, + showInChannel: data.showInChannel.present ? data.showInChannel.value : this.showInChannel, shadowed: data.shadowed.present ? data.shadowed.value : this.shadowed, command: data.command.present ? data.command.value : this.command, - localCreatedAt: data.localCreatedAt.present - ? data.localCreatedAt.value - : this.localCreatedAt, - remoteCreatedAt: data.remoteCreatedAt.present - ? data.remoteCreatedAt.value - : this.remoteCreatedAt, - localUpdatedAt: data.localUpdatedAt.present - ? data.localUpdatedAt.value - : this.localUpdatedAt, - remoteUpdatedAt: data.remoteUpdatedAt.present - ? data.remoteUpdatedAt.value - : this.remoteUpdatedAt, - localDeletedAt: data.localDeletedAt.present - ? data.localDeletedAt.value - : this.localDeletedAt, - remoteDeletedAt: data.remoteDeletedAt.present - ? data.remoteDeletedAt.value - : this.remoteDeletedAt, - deletedForMe: data.deletedForMe.present - ? data.deletedForMe.value - : this.deletedForMe, + localCreatedAt: data.localCreatedAt.present ? data.localCreatedAt.value : this.localCreatedAt, + remoteCreatedAt: data.remoteCreatedAt.present ? data.remoteCreatedAt.value : this.remoteCreatedAt, + localUpdatedAt: data.localUpdatedAt.present ? data.localUpdatedAt.value : this.localUpdatedAt, + remoteUpdatedAt: data.remoteUpdatedAt.present ? data.remoteUpdatedAt.value : this.remoteUpdatedAt, + localDeletedAt: data.localDeletedAt.present ? data.localDeletedAt.value : this.localDeletedAt, + remoteDeletedAt: data.remoteDeletedAt.present ? data.remoteDeletedAt.value : this.remoteDeletedAt, + deletedForMe: data.deletedForMe.present ? data.deletedForMe.value : this.deletedForMe, messageTextUpdatedAt: data.messageTextUpdatedAt.present ? data.messageTextUpdatedAt.value : this.messageTextUpdatedAt, userId: data.userId.present ? data.userId.value : this.userId, - channelRole: - data.channelRole.present ? data.channelRole.value : this.channelRole, + channelRole: data.channelRole.present ? data.channelRole.value : this.channelRole, pinned: data.pinned.present ? data.pinned.value : this.pinned, pinnedAt: data.pinnedAt.present ? data.pinnedAt.value : this.pinnedAt, - pinExpires: - data.pinExpires.present ? data.pinExpires.value : this.pinExpires, - pinnedByUserId: data.pinnedByUserId.present - ? data.pinnedByUserId.value - : this.pinnedByUserId, - channelCid: - data.channelCid.present ? data.channelCid.value : this.channelCid, + pinExpires: data.pinExpires.present ? data.pinExpires.value : this.pinExpires, + pinnedByUserId: data.pinnedByUserId.present ? data.pinnedByUserId.value : this.pinnedByUserId, + channelCid: data.channelCid.present ? data.channelCid.value : this.channelCid, i18n: data.i18n.present ? data.i18n.value : this.i18n, restrictedVisibility: data.restrictedVisibility.present ? data.restrictedVisibility.value @@ -4436,39 +4400,39 @@ class PinnedMessageEntity extends DataClass @override int get hashCode => Object.hashAll([ - id, - messageText, - attachments, - state, - type, - mentionedUsers, - reactionGroups, - parentId, - quotedMessageId, - pollId, - replyCount, - showInChannel, - shadowed, - command, - localCreatedAt, - remoteCreatedAt, - localUpdatedAt, - remoteUpdatedAt, - localDeletedAt, - remoteDeletedAt, - deletedForMe, - messageTextUpdatedAt, - userId, - channelRole, - pinned, - pinnedAt, - pinExpires, - pinnedByUserId, - channelCid, - i18n, - restrictedVisibility, - extraData - ]); + id, + messageText, + attachments, + state, + type, + mentionedUsers, + reactionGroups, + parentId, + quotedMessageId, + pollId, + replyCount, + showInChannel, + shadowed, + command, + localCreatedAt, + remoteCreatedAt, + localUpdatedAt, + remoteUpdatedAt, + localDeletedAt, + remoteDeletedAt, + deletedForMe, + messageTextUpdatedAt, + userId, + channelRole, + pinned, + pinnedAt, + pinExpires, + pinnedByUserId, + channelCid, + i18n, + restrictedVisibility, + extraData, + ]); @override bool operator ==(Object other) => identical(this, other) || @@ -4610,11 +4574,11 @@ class PinnedMessagesCompanion extends UpdateCompanion { this.restrictedVisibility = const Value.absent(), this.extraData = const Value.absent(), this.rowid = const Value.absent(), - }) : id = Value(id), - attachments = Value(attachments), - state = Value(state), - mentionedUsers = Value(mentionedUsers), - channelCid = Value(channelCid); + }) : id = Value(id), + attachments = Value(attachments), + state = Value(state), + mentionedUsers = Value(mentionedUsers), + channelCid = Value(channelCid); static Insertable custom({ Expression? id, Expression? messageText, @@ -4672,8 +4636,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { if (localDeletedAt != null) 'local_deleted_at': localDeletedAt, if (remoteDeletedAt != null) 'remote_deleted_at': remoteDeletedAt, if (deletedForMe != null) 'deleted_for_me': deletedForMe, - if (messageTextUpdatedAt != null) - 'message_text_updated_at': messageTextUpdatedAt, + if (messageTextUpdatedAt != null) 'message_text_updated_at': messageTextUpdatedAt, if (userId != null) 'user_id': userId, if (channelRole != null) 'channel_role': channelRole, if (pinned != null) 'pinned': pinned, @@ -4682,47 +4645,47 @@ class PinnedMessagesCompanion extends UpdateCompanion { if (pinnedByUserId != null) 'pinned_by_user_id': pinnedByUserId, if (channelCid != null) 'channel_cid': channelCid, if (i18n != null) 'i18n': i18n, - if (restrictedVisibility != null) - 'restricted_visibility': restrictedVisibility, + if (restrictedVisibility != null) 'restricted_visibility': restrictedVisibility, if (extraData != null) 'extra_data': extraData, if (rowid != null) 'rowid': rowid, }); } - PinnedMessagesCompanion copyWith( - {Value? id, - Value? messageText, - Value>? attachments, - Value? state, - Value? type, - Value>? mentionedUsers, - Value?>? reactionGroups, - Value? parentId, - Value? quotedMessageId, - Value? pollId, - Value? replyCount, - Value? showInChannel, - Value? shadowed, - Value? command, - Value? localCreatedAt, - Value? remoteCreatedAt, - Value? localUpdatedAt, - Value? remoteUpdatedAt, - Value? localDeletedAt, - Value? remoteDeletedAt, - Value? deletedForMe, - Value? messageTextUpdatedAt, - Value? userId, - Value? channelRole, - Value? pinned, - Value? pinnedAt, - Value? pinExpires, - Value? pinnedByUserId, - Value? channelCid, - Value?>? i18n, - Value?>? restrictedVisibility, - Value?>? extraData, - Value? rowid}) { + PinnedMessagesCompanion copyWith({ + Value? id, + Value? messageText, + Value>? attachments, + Value? state, + Value? type, + Value>? mentionedUsers, + Value?>? reactionGroups, + Value? parentId, + Value? quotedMessageId, + Value? pollId, + Value? replyCount, + Value? showInChannel, + Value? shadowed, + Value? command, + Value? localCreatedAt, + Value? remoteCreatedAt, + Value? localUpdatedAt, + Value? remoteUpdatedAt, + Value? localDeletedAt, + Value? remoteDeletedAt, + Value? deletedForMe, + Value? messageTextUpdatedAt, + Value? userId, + Value? channelRole, + Value? pinned, + Value? pinnedAt, + Value? pinExpires, + Value? pinnedByUserId, + Value? channelCid, + Value?>? i18n, + Value?>? restrictedVisibility, + Value?>? extraData, + Value? rowid, + }) { return PinnedMessagesCompanion( id: id ?? this.id, messageText: messageText ?? this.messageText, @@ -4770,8 +4733,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { map['message_text'] = Variable(messageText.value); } if (attachments.present) { - map['attachments'] = Variable( - $PinnedMessagesTable.$converterattachments.toSql(attachments.value)); + map['attachments'] = Variable($PinnedMessagesTable.$converterattachments.toSql(attachments.value)); } if (state.present) { map['state'] = Variable(state.value); @@ -4780,14 +4742,14 @@ class PinnedMessagesCompanion extends UpdateCompanion { map['type'] = Variable(type.value); } if (mentionedUsers.present) { - map['mentioned_users'] = Variable($PinnedMessagesTable - .$convertermentionedUsers - .toSql(mentionedUsers.value)); + map['mentioned_users'] = Variable( + $PinnedMessagesTable.$convertermentionedUsers.toSql(mentionedUsers.value), + ); } if (reactionGroups.present) { - map['reaction_groups'] = Variable($PinnedMessagesTable - .$converterreactionGroupsn - .toSql(reactionGroups.value)); + map['reaction_groups'] = Variable( + $PinnedMessagesTable.$converterreactionGroupsn.toSql(reactionGroups.value), + ); } if (parentId.present) { map['parent_id'] = Variable(parentId.value); @@ -4832,8 +4794,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { map['deleted_for_me'] = Variable(deletedForMe.value); } if (messageTextUpdatedAt.present) { - map['message_text_updated_at'] = - Variable(messageTextUpdatedAt.value); + map['message_text_updated_at'] = Variable(messageTextUpdatedAt.value); } if (userId.present) { map['user_id'] = Variable(userId.value); @@ -4857,17 +4818,15 @@ class PinnedMessagesCompanion extends UpdateCompanion { map['channel_cid'] = Variable(channelCid.value); } if (i18n.present) { - map['i18n'] = Variable( - $PinnedMessagesTable.$converteri18n.toSql(i18n.value)); + map['i18n'] = Variable($PinnedMessagesTable.$converteri18n.toSql(i18n.value)); } if (restrictedVisibility.present) { - map['restricted_visibility'] = Variable($PinnedMessagesTable - .$converterrestrictedVisibilityn - .toSql(restrictedVisibility.value)); + map['restricted_visibility'] = Variable( + $PinnedMessagesTable.$converterrestrictedVisibilityn.toSql(restrictedVisibility.value), + ); } if (extraData.present) { - map['extra_data'] = Variable( - $PinnedMessagesTable.$converterextraDatan.toSql(extraData.value)); + map['extra_data'] = Variable($PinnedMessagesTable.$converterextraDatan.toSql(extraData.value)); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -4924,158 +4883,192 @@ class $PollsTable extends Polls with TableInfo<$PollsTable, PollEntity> { static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); static const VerificationMeta _nameMeta = const VerificationMeta('name'); @override late final GeneratedColumn name = GeneratedColumn( - 'name', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _descriptionMeta = - const VerificationMeta('description'); + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _descriptionMeta = const VerificationMeta('description'); @override late final GeneratedColumn description = GeneratedColumn( - 'description', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - @override - late final GeneratedColumnWithTypeConverter, String> options = - GeneratedColumn('options', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>($PollsTable.$converteroptions); - @override - late final GeneratedColumnWithTypeConverter - votingVisibility = GeneratedColumn( - 'voting_visibility', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: const Constant('public')) - .withConverter( - $PollsTable.$convertervotingVisibility); - static const VerificationMeta _enforceUniqueVoteMeta = - const VerificationMeta('enforceUniqueVote'); + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + late final GeneratedColumnWithTypeConverter, String> options = GeneratedColumn( + 'options', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($PollsTable.$converteroptions); + @override + late final GeneratedColumnWithTypeConverter votingVisibility = GeneratedColumn( + 'voting_visibility', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('public'), + ).withConverter($PollsTable.$convertervotingVisibility); + static const VerificationMeta _enforceUniqueVoteMeta = const VerificationMeta('enforceUniqueVote'); @override late final GeneratedColumn enforceUniqueVote = GeneratedColumn( - 'enforce_unique_vote', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("enforce_unique_vote" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _maxVotesAllowedMeta = - const VerificationMeta('maxVotesAllowed'); + 'enforce_unique_vote', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("enforce_unique_vote" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _maxVotesAllowedMeta = const VerificationMeta('maxVotesAllowed'); @override late final GeneratedColumn maxVotesAllowed = GeneratedColumn( - 'max_votes_allowed', aliasedName, true, - type: DriftSqlType.int, requiredDuringInsert: false); - static const VerificationMeta _allowUserSuggestedOptionsMeta = - const VerificationMeta('allowUserSuggestedOptions'); - @override - late final GeneratedColumn allowUserSuggestedOptions = - GeneratedColumn('allow_user_suggested_options', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("allow_user_suggested_options" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _allowAnswersMeta = - const VerificationMeta('allowAnswers'); + 'max_votes_allowed', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + static const VerificationMeta _allowUserSuggestedOptionsMeta = const VerificationMeta('allowUserSuggestedOptions'); + @override + late final GeneratedColumn allowUserSuggestedOptions = GeneratedColumn( + 'allow_user_suggested_options', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("allow_user_suggested_options" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _allowAnswersMeta = const VerificationMeta('allowAnswers'); @override late final GeneratedColumn allowAnswers = GeneratedColumn( - 'allow_answers', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("allow_answers" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _isClosedMeta = - const VerificationMeta('isClosed'); + 'allow_answers', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("allow_answers" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _isClosedMeta = const VerificationMeta('isClosed'); @override late final GeneratedColumn isClosed = GeneratedColumn( - 'is_closed', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("is_closed" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _answersCountMeta = - const VerificationMeta('answersCount'); + 'is_closed', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("is_closed" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _answersCountMeta = const VerificationMeta('answersCount'); @override late final GeneratedColumn answersCount = GeneratedColumn( - 'answers_count', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(0)); - @override - late final GeneratedColumnWithTypeConverter, String> - voteCountsByOption = GeneratedColumn( - 'vote_counts_by_option', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>( - $PollsTable.$convertervoteCountsByOption); - static const VerificationMeta _voteCountMeta = - const VerificationMeta('voteCount'); + 'answers_count', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0), + ); + @override + late final GeneratedColumnWithTypeConverter, String> voteCountsByOption = GeneratedColumn( + 'vote_counts_by_option', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($PollsTable.$convertervoteCountsByOption); + static const VerificationMeta _voteCountMeta = const VerificationMeta('voteCount'); @override late final GeneratedColumn voteCount = GeneratedColumn( - 'vote_count', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(0)); - static const VerificationMeta _createdByIdMeta = - const VerificationMeta('createdById'); + 'vote_count', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0), + ); + static const VerificationMeta _createdByIdMeta = const VerificationMeta('createdById'); @override late final GeneratedColumn createdById = GeneratedColumn( - 'created_by_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _createdAtMeta = - const VerificationMeta('createdAt'); + 'created_by_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - static const VerificationMeta _updatedAtMeta = - const VerificationMeta('updatedAt'); + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); @override late final GeneratedColumn updatedAt = GeneratedColumn( - 'updated_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - @override - late final GeneratedColumnWithTypeConverter?, String> - extraData = GeneratedColumn('extra_data', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $PollsTable.$converterextraDatan); + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + @override + late final GeneratedColumnWithTypeConverter?, String> extraData = GeneratedColumn( + 'extra_data', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($PollsTable.$converterextraDatan); @override List get $columns => [ - id, - name, - description, - options, - votingVisibility, - enforceUniqueVote, - maxVotesAllowed, - allowUserSuggestedOptions, - allowAnswers, - isClosed, - answersCount, - voteCountsByOption, - voteCount, - createdById, - createdAt, - updatedAt, - extraData - ]; + id, + name, + description, + options, + votingVisibility, + enforceUniqueVote, + maxVotesAllowed, + allowUserSuggestedOptions, + allowAnswers, + isClosed, + answersCount, + voteCountsByOption, + voteCount, + createdById, + createdAt, + updatedAt, + extraData, + ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'polls'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { @@ -5084,69 +5077,54 @@ class $PollsTable extends Polls with TableInfo<$PollsTable, PollEntity> { context.missing(_idMeta); } if (data.containsKey('name')) { - context.handle( - _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); + context.handle(_nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); } else if (isInserting) { context.missing(_nameMeta); } if (data.containsKey('description')) { - context.handle( - _descriptionMeta, - description.isAcceptableOrUnknown( - data['description']!, _descriptionMeta)); + context.handle(_descriptionMeta, description.isAcceptableOrUnknown(data['description']!, _descriptionMeta)); } if (data.containsKey('enforce_unique_vote')) { context.handle( - _enforceUniqueVoteMeta, - enforceUniqueVote.isAcceptableOrUnknown( - data['enforce_unique_vote']!, _enforceUniqueVoteMeta)); + _enforceUniqueVoteMeta, + enforceUniqueVote.isAcceptableOrUnknown(data['enforce_unique_vote']!, _enforceUniqueVoteMeta), + ); } if (data.containsKey('max_votes_allowed')) { context.handle( - _maxVotesAllowedMeta, - maxVotesAllowed.isAcceptableOrUnknown( - data['max_votes_allowed']!, _maxVotesAllowedMeta)); + _maxVotesAllowedMeta, + maxVotesAllowed.isAcceptableOrUnknown(data['max_votes_allowed']!, _maxVotesAllowedMeta), + ); } if (data.containsKey('allow_user_suggested_options')) { context.handle( + _allowUserSuggestedOptionsMeta, + allowUserSuggestedOptions.isAcceptableOrUnknown( + data['allow_user_suggested_options']!, _allowUserSuggestedOptionsMeta, - allowUserSuggestedOptions.isAcceptableOrUnknown( - data['allow_user_suggested_options']!, - _allowUserSuggestedOptionsMeta)); + ), + ); } if (data.containsKey('allow_answers')) { - context.handle( - _allowAnswersMeta, - allowAnswers.isAcceptableOrUnknown( - data['allow_answers']!, _allowAnswersMeta)); + context.handle(_allowAnswersMeta, allowAnswers.isAcceptableOrUnknown(data['allow_answers']!, _allowAnswersMeta)); } if (data.containsKey('is_closed')) { - context.handle(_isClosedMeta, - isClosed.isAcceptableOrUnknown(data['is_closed']!, _isClosedMeta)); + context.handle(_isClosedMeta, isClosed.isAcceptableOrUnknown(data['is_closed']!, _isClosedMeta)); } if (data.containsKey('answers_count')) { - context.handle( - _answersCountMeta, - answersCount.isAcceptableOrUnknown( - data['answers_count']!, _answersCountMeta)); + context.handle(_answersCountMeta, answersCount.isAcceptableOrUnknown(data['answers_count']!, _answersCountMeta)); } if (data.containsKey('vote_count')) { - context.handle(_voteCountMeta, - voteCount.isAcceptableOrUnknown(data['vote_count']!, _voteCountMeta)); + context.handle(_voteCountMeta, voteCount.isAcceptableOrUnknown(data['vote_count']!, _voteCountMeta)); } if (data.containsKey('created_by_id')) { - context.handle( - _createdByIdMeta, - createdById.isAcceptableOrUnknown( - data['created_by_id']!, _createdByIdMeta)); + context.handle(_createdByIdMeta, createdById.isAcceptableOrUnknown(data['created_by_id']!, _createdByIdMeta)); } if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { - context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } return context; } @@ -5157,45 +5135,37 @@ class $PollsTable extends Polls with TableInfo<$PollsTable, PollEntity> { PollEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return PollEntity( - id: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}id'])!, - name: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}name'])!, - description: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}description']), - options: $PollsTable.$converteroptions.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}options'])!), + id: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}id'])!, + name: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}name'])!, + description: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}description']), + options: $PollsTable.$converteroptions.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}options'])!, + ), votingVisibility: $PollsTable.$convertervotingVisibility.fromSql( - attachedDatabase.typeMapping.read(DriftSqlType.string, - data['${effectivePrefix}voting_visibility'])!), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}voting_visibility'])!, + ), enforceUniqueVote: attachedDatabase.typeMapping.read( - DriftSqlType.bool, data['${effectivePrefix}enforce_unique_vote'])!, - maxVotesAllowed: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}max_votes_allowed']), + DriftSqlType.bool, + data['${effectivePrefix}enforce_unique_vote'], + )!, + maxVotesAllowed: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}max_votes_allowed']), allowUserSuggestedOptions: attachedDatabase.typeMapping.read( - DriftSqlType.bool, - data['${effectivePrefix}allow_user_suggested_options'])!, - allowAnswers: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}allow_answers'])!, - isClosed: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}is_closed'])!, - answersCount: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}answers_count'])!, + DriftSqlType.bool, + data['${effectivePrefix}allow_user_suggested_options'], + )!, + allowAnswers: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}allow_answers'])!, + isClosed: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}is_closed'])!, + answersCount: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}answers_count'])!, voteCountsByOption: $PollsTable.$convertervoteCountsByOption.fromSql( - attachedDatabase.typeMapping.read(DriftSqlType.string, - data['${effectivePrefix}vote_counts_by_option'])!), - voteCount: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}vote_count'])!, - createdById: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}created_by_id']), - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, - updatedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, - extraData: $PollsTable.$converterextraDatan.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}vote_counts_by_option'])!, + ), + voteCount: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}vote_count'])!, + createdById: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}created_by_id']), + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + extraData: $PollsTable.$converterextraDatan.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}extra_data']), + ), ); } @@ -5204,16 +5174,13 @@ class $PollsTable extends Polls with TableInfo<$PollsTable, PollEntity> { return $PollsTable(attachedDatabase, alias); } - static TypeConverter, String> $converteroptions = - ListConverter(); - static TypeConverter $convertervotingVisibility = - const VotingVisibilityConverter(); - static TypeConverter, String> $convertervoteCountsByOption = - MapConverter(); - static TypeConverter, String> $converterextraData = - MapConverter(); - static TypeConverter?, String?> $converterextraDatan = - NullAwareTypeConverter.wrap($converterextraData); + static TypeConverter, String> $converteroptions = ListConverter(); + static TypeConverter $convertervotingVisibility = const VotingVisibilityConverter(); + static TypeConverter, String> $convertervoteCountsByOption = MapConverter(); + static TypeConverter, String> $converterextraData = MapConverter(); + static TypeConverter?, String?> $converterextraDatan = NullAwareTypeConverter.wrap( + $converterextraData, + ); } class PollEntity extends DataClass implements Insertable { @@ -5275,24 +5242,25 @@ class PollEntity extends DataClass implements Insertable { /// Map of custom poll extraData final Map? extraData; - const PollEntity( - {required this.id, - required this.name, - this.description, - required this.options, - required this.votingVisibility, - required this.enforceUniqueVote, - this.maxVotesAllowed, - required this.allowUserSuggestedOptions, - required this.allowAnswers, - required this.isClosed, - required this.answersCount, - required this.voteCountsByOption, - required this.voteCount, - this.createdById, - required this.createdAt, - required this.updatedAt, - this.extraData}); + const PollEntity({ + required this.id, + required this.name, + this.description, + required this.options, + required this.votingVisibility, + required this.enforceUniqueVote, + this.maxVotesAllowed, + required this.allowUserSuggestedOptions, + required this.allowAnswers, + required this.isClosed, + required this.answersCount, + required this.voteCountsByOption, + required this.voteCount, + this.createdById, + required this.createdAt, + required this.updatedAt, + this.extraData, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -5302,25 +5270,23 @@ class PollEntity extends DataClass implements Insertable { map['description'] = Variable(description); } { - map['options'] = - Variable($PollsTable.$converteroptions.toSql(options)); + map['options'] = Variable($PollsTable.$converteroptions.toSql(options)); } { - map['voting_visibility'] = Variable( - $PollsTable.$convertervotingVisibility.toSql(votingVisibility)); + map['voting_visibility'] = Variable($PollsTable.$convertervotingVisibility.toSql(votingVisibility)); } map['enforce_unique_vote'] = Variable(enforceUniqueVote); if (!nullToAbsent || maxVotesAllowed != null) { map['max_votes_allowed'] = Variable(maxVotesAllowed); } - map['allow_user_suggested_options'] = - Variable(allowUserSuggestedOptions); + map['allow_user_suggested_options'] = Variable(allowUserSuggestedOptions); map['allow_answers'] = Variable(allowAnswers); map['is_closed'] = Variable(isClosed); map['answers_count'] = Variable(answersCount); { map['vote_counts_by_option'] = Variable( - $PollsTable.$convertervoteCountsByOption.toSql(voteCountsByOption)); + $PollsTable.$convertervoteCountsByOption.toSql(voteCountsByOption), + ); } map['vote_count'] = Variable(voteCount); if (!nullToAbsent || createdById != null) { @@ -5329,31 +5295,26 @@ class PollEntity extends DataClass implements Insertable { map['created_at'] = Variable(createdAt); map['updated_at'] = Variable(updatedAt); if (!nullToAbsent || extraData != null) { - map['extra_data'] = - Variable($PollsTable.$converterextraDatan.toSql(extraData)); + map['extra_data'] = Variable($PollsTable.$converterextraDatan.toSql(extraData)); } return map; } - factory PollEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory PollEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return PollEntity( id: serializer.fromJson(json['id']), name: serializer.fromJson(json['name']), description: serializer.fromJson(json['description']), options: serializer.fromJson>(json['options']), - votingVisibility: - serializer.fromJson(json['votingVisibility']), + votingVisibility: serializer.fromJson(json['votingVisibility']), enforceUniqueVote: serializer.fromJson(json['enforceUniqueVote']), maxVotesAllowed: serializer.fromJson(json['maxVotesAllowed']), - allowUserSuggestedOptions: - serializer.fromJson(json['allowUserSuggestedOptions']), + allowUserSuggestedOptions: serializer.fromJson(json['allowUserSuggestedOptions']), allowAnswers: serializer.fromJson(json['allowAnswers']), isClosed: serializer.fromJson(json['isClosed']), answersCount: serializer.fromJson(json['answersCount']), - voteCountsByOption: - serializer.fromJson>(json['voteCountsByOption']), + voteCountsByOption: serializer.fromJson>(json['voteCountsByOption']), voteCount: serializer.fromJson(json['voteCount']), createdById: serializer.fromJson(json['createdById']), createdAt: serializer.fromJson(json['createdAt']), @@ -5372,13 +5333,11 @@ class PollEntity extends DataClass implements Insertable { 'votingVisibility': serializer.toJson(votingVisibility), 'enforceUniqueVote': serializer.toJson(enforceUniqueVote), 'maxVotesAllowed': serializer.toJson(maxVotesAllowed), - 'allowUserSuggestedOptions': - serializer.toJson(allowUserSuggestedOptions), + 'allowUserSuggestedOptions': serializer.toJson(allowUserSuggestedOptions), 'allowAnswers': serializer.toJson(allowAnswers), 'isClosed': serializer.toJson(isClosed), 'answersCount': serializer.toJson(answersCount), - 'voteCountsByOption': - serializer.toJson>(voteCountsByOption), + 'voteCountsByOption': serializer.toJson>(voteCountsByOption), 'voteCount': serializer.toJson(voteCount), 'createdById': serializer.toJson(createdById), 'createdAt': serializer.toJson(createdAt), @@ -5387,78 +5346,61 @@ class PollEntity extends DataClass implements Insertable { }; } - PollEntity copyWith( - {String? id, - String? name, - Value description = const Value.absent(), - List? options, - VotingVisibility? votingVisibility, - bool? enforceUniqueVote, - Value maxVotesAllowed = const Value.absent(), - bool? allowUserSuggestedOptions, - bool? allowAnswers, - bool? isClosed, - int? answersCount, - Map? voteCountsByOption, - int? voteCount, - Value createdById = const Value.absent(), - DateTime? createdAt, - DateTime? updatedAt, - Value?> extraData = const Value.absent()}) => - PollEntity( - id: id ?? this.id, - name: name ?? this.name, - description: description.present ? description.value : this.description, - options: options ?? this.options, - votingVisibility: votingVisibility ?? this.votingVisibility, - enforceUniqueVote: enforceUniqueVote ?? this.enforceUniqueVote, - maxVotesAllowed: maxVotesAllowed.present - ? maxVotesAllowed.value - : this.maxVotesAllowed, - allowUserSuggestedOptions: - allowUserSuggestedOptions ?? this.allowUserSuggestedOptions, - allowAnswers: allowAnswers ?? this.allowAnswers, - isClosed: isClosed ?? this.isClosed, - answersCount: answersCount ?? this.answersCount, - voteCountsByOption: voteCountsByOption ?? this.voteCountsByOption, - voteCount: voteCount ?? this.voteCount, - createdById: createdById.present ? createdById.value : this.createdById, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - extraData: extraData.present ? extraData.value : this.extraData, - ); + PollEntity copyWith({ + String? id, + String? name, + Value description = const Value.absent(), + List? options, + VotingVisibility? votingVisibility, + bool? enforceUniqueVote, + Value maxVotesAllowed = const Value.absent(), + bool? allowUserSuggestedOptions, + bool? allowAnswers, + bool? isClosed, + int? answersCount, + Map? voteCountsByOption, + int? voteCount, + Value createdById = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + Value?> extraData = const Value.absent(), + }) => PollEntity( + id: id ?? this.id, + name: name ?? this.name, + description: description.present ? description.value : this.description, + options: options ?? this.options, + votingVisibility: votingVisibility ?? this.votingVisibility, + enforceUniqueVote: enforceUniqueVote ?? this.enforceUniqueVote, + maxVotesAllowed: maxVotesAllowed.present ? maxVotesAllowed.value : this.maxVotesAllowed, + allowUserSuggestedOptions: allowUserSuggestedOptions ?? this.allowUserSuggestedOptions, + allowAnswers: allowAnswers ?? this.allowAnswers, + isClosed: isClosed ?? this.isClosed, + answersCount: answersCount ?? this.answersCount, + voteCountsByOption: voteCountsByOption ?? this.voteCountsByOption, + voteCount: voteCount ?? this.voteCount, + createdById: createdById.present ? createdById.value : this.createdById, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + extraData: extraData.present ? extraData.value : this.extraData, + ); PollEntity copyWithCompanion(PollsCompanion data) { return PollEntity( id: data.id.present ? data.id.value : this.id, name: data.name.present ? data.name.value : this.name, - description: - data.description.present ? data.description.value : this.description, + description: data.description.present ? data.description.value : this.description, options: data.options.present ? data.options.value : this.options, - votingVisibility: data.votingVisibility.present - ? data.votingVisibility.value - : this.votingVisibility, - enforceUniqueVote: data.enforceUniqueVote.present - ? data.enforceUniqueVote.value - : this.enforceUniqueVote, - maxVotesAllowed: data.maxVotesAllowed.present - ? data.maxVotesAllowed.value - : this.maxVotesAllowed, + votingVisibility: data.votingVisibility.present ? data.votingVisibility.value : this.votingVisibility, + enforceUniqueVote: data.enforceUniqueVote.present ? data.enforceUniqueVote.value : this.enforceUniqueVote, + maxVotesAllowed: data.maxVotesAllowed.present ? data.maxVotesAllowed.value : this.maxVotesAllowed, allowUserSuggestedOptions: data.allowUserSuggestedOptions.present ? data.allowUserSuggestedOptions.value : this.allowUserSuggestedOptions, - allowAnswers: data.allowAnswers.present - ? data.allowAnswers.value - : this.allowAnswers, + allowAnswers: data.allowAnswers.present ? data.allowAnswers.value : this.allowAnswers, isClosed: data.isClosed.present ? data.isClosed.value : this.isClosed, - answersCount: data.answersCount.present - ? data.answersCount.value - : this.answersCount, - voteCountsByOption: data.voteCountsByOption.present - ? data.voteCountsByOption.value - : this.voteCountsByOption, + answersCount: data.answersCount.present ? data.answersCount.value : this.answersCount, + voteCountsByOption: data.voteCountsByOption.present ? data.voteCountsByOption.value : this.voteCountsByOption, voteCount: data.voteCount.present ? data.voteCount.value : this.voteCount, - createdById: - data.createdById.present ? data.createdById.value : this.createdById, + createdById: data.createdById.present ? data.createdById.value : this.createdById, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, extraData: data.extraData.present ? data.extraData.value : this.extraData, @@ -5491,23 +5433,24 @@ class PollEntity extends DataClass implements Insertable { @override int get hashCode => Object.hash( - id, - name, - description, - options, - votingVisibility, - enforceUniqueVote, - maxVotesAllowed, - allowUserSuggestedOptions, - allowAnswers, - isClosed, - answersCount, - voteCountsByOption, - voteCount, - createdById, - createdAt, - updatedAt, - extraData); + id, + name, + description, + options, + votingVisibility, + enforceUniqueVote, + maxVotesAllowed, + allowUserSuggestedOptions, + allowAnswers, + isClosed, + answersCount, + voteCountsByOption, + voteCount, + createdById, + createdAt, + updatedAt, + extraData, + ); @override bool operator ==(Object other) => identical(this, other) || @@ -5589,10 +5532,10 @@ class PollsCompanion extends UpdateCompanion { this.updatedAt = const Value.absent(), this.extraData = const Value.absent(), this.rowid = const Value.absent(), - }) : id = Value(id), - name = Value(name), - options = Value(options), - voteCountsByOption = Value(voteCountsByOption); + }) : id = Value(id), + name = Value(name), + options = Value(options), + voteCountsByOption = Value(voteCountsByOption); static Insertable custom({ Expression? id, Expression? name, @@ -5621,13 +5564,11 @@ class PollsCompanion extends UpdateCompanion { if (votingVisibility != null) 'voting_visibility': votingVisibility, if (enforceUniqueVote != null) 'enforce_unique_vote': enforceUniqueVote, if (maxVotesAllowed != null) 'max_votes_allowed': maxVotesAllowed, - if (allowUserSuggestedOptions != null) - 'allow_user_suggested_options': allowUserSuggestedOptions, + if (allowUserSuggestedOptions != null) 'allow_user_suggested_options': allowUserSuggestedOptions, if (allowAnswers != null) 'allow_answers': allowAnswers, if (isClosed != null) 'is_closed': isClosed, if (answersCount != null) 'answers_count': answersCount, - if (voteCountsByOption != null) - 'vote_counts_by_option': voteCountsByOption, + if (voteCountsByOption != null) 'vote_counts_by_option': voteCountsByOption, if (voteCount != null) 'vote_count': voteCount, if (createdById != null) 'created_by_id': createdById, if (createdAt != null) 'created_at': createdAt, @@ -5637,25 +5578,26 @@ class PollsCompanion extends UpdateCompanion { }); } - PollsCompanion copyWith( - {Value? id, - Value? name, - Value? description, - Value>? options, - Value? votingVisibility, - Value? enforceUniqueVote, - Value? maxVotesAllowed, - Value? allowUserSuggestedOptions, - Value? allowAnswers, - Value? isClosed, - Value? answersCount, - Value>? voteCountsByOption, - Value? voteCount, - Value? createdById, - Value? createdAt, - Value? updatedAt, - Value?>? extraData, - Value? rowid}) { + PollsCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value>? options, + Value? votingVisibility, + Value? enforceUniqueVote, + Value? maxVotesAllowed, + Value? allowUserSuggestedOptions, + Value? allowAnswers, + Value? isClosed, + Value? answersCount, + Value>? voteCountsByOption, + Value? voteCount, + Value? createdById, + Value? createdAt, + Value? updatedAt, + Value?>? extraData, + Value? rowid, + }) { return PollsCompanion( id: id ?? this.id, name: name ?? this.name, @@ -5664,8 +5606,7 @@ class PollsCompanion extends UpdateCompanion { votingVisibility: votingVisibility ?? this.votingVisibility, enforceUniqueVote: enforceUniqueVote ?? this.enforceUniqueVote, maxVotesAllowed: maxVotesAllowed ?? this.maxVotesAllowed, - allowUserSuggestedOptions: - allowUserSuggestedOptions ?? this.allowUserSuggestedOptions, + allowUserSuggestedOptions: allowUserSuggestedOptions ?? this.allowUserSuggestedOptions, allowAnswers: allowAnswers ?? this.allowAnswers, isClosed: isClosed ?? this.isClosed, answersCount: answersCount ?? this.answersCount, @@ -5692,12 +5633,10 @@ class PollsCompanion extends UpdateCompanion { map['description'] = Variable(description.value); } if (options.present) { - map['options'] = - Variable($PollsTable.$converteroptions.toSql(options.value)); + map['options'] = Variable($PollsTable.$converteroptions.toSql(options.value)); } if (votingVisibility.present) { - map['voting_visibility'] = Variable( - $PollsTable.$convertervotingVisibility.toSql(votingVisibility.value)); + map['voting_visibility'] = Variable($PollsTable.$convertervotingVisibility.toSql(votingVisibility.value)); } if (enforceUniqueVote.present) { map['enforce_unique_vote'] = Variable(enforceUniqueVote.value); @@ -5706,8 +5645,7 @@ class PollsCompanion extends UpdateCompanion { map['max_votes_allowed'] = Variable(maxVotesAllowed.value); } if (allowUserSuggestedOptions.present) { - map['allow_user_suggested_options'] = - Variable(allowUserSuggestedOptions.value); + map['allow_user_suggested_options'] = Variable(allowUserSuggestedOptions.value); } if (allowAnswers.present) { map['allow_answers'] = Variable(allowAnswers.value); @@ -5719,9 +5657,9 @@ class PollsCompanion extends UpdateCompanion { map['answers_count'] = Variable(answersCount.value); } if (voteCountsByOption.present) { - map['vote_counts_by_option'] = Variable($PollsTable - .$convertervoteCountsByOption - .toSql(voteCountsByOption.value)); + map['vote_counts_by_option'] = Variable( + $PollsTable.$convertervoteCountsByOption.toSql(voteCountsByOption.value), + ); } if (voteCount.present) { map['vote_count'] = Variable(voteCount.value); @@ -5736,8 +5674,7 @@ class PollsCompanion extends UpdateCompanion { map['updated_at'] = Variable(updatedAt.value); } if (extraData.present) { - map['extra_data'] = Variable( - $PollsTable.$converterextraDatan.toSql(extraData.value)); + map['extra_data'] = Variable($PollsTable.$converterextraDatan.toSql(extraData.value)); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -5771,8 +5708,7 @@ class PollsCompanion extends UpdateCompanion { } } -class $PollVotesTable extends PollVotes - with TableInfo<$PollVotesTable, PollVoteEntity> { +class $PollVotesTable extends PollVotes with TableInfo<$PollVotesTable, PollVoteEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; @@ -5780,90 +5716,100 @@ class $PollVotesTable extends PollVotes static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); static const VerificationMeta _pollIdMeta = const VerificationMeta('pollId'); @override late final GeneratedColumn pollId = GeneratedColumn( - 'poll_id', aliasedName, true, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES polls (id) ON DELETE CASCADE')); - static const VerificationMeta _optionIdMeta = - const VerificationMeta('optionId'); + 'poll_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES polls (id) ON DELETE CASCADE'), + ); + static const VerificationMeta _optionIdMeta = const VerificationMeta('optionId'); @override late final GeneratedColumn optionId = GeneratedColumn( - 'option_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _answerTextMeta = - const VerificationMeta('answerText'); + 'option_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _answerTextMeta = const VerificationMeta('answerText'); @override late final GeneratedColumn answerText = GeneratedColumn( - 'answer_text', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _createdAtMeta = - const VerificationMeta('createdAt'); + 'answer_text', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - static const VerificationMeta _updatedAtMeta = - const VerificationMeta('updatedAt'); + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); @override late final GeneratedColumn updatedAt = GeneratedColumn( - 'updated_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); @override late final GeneratedColumn userId = GeneratedColumn( - 'user_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'user_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); @override - List get $columns => - [id, pollId, optionId, answerText, createdAt, updatedAt, userId]; + List get $columns => [id, pollId, optionId, answerText, createdAt, updatedAt, userId]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'poll_votes'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } if (data.containsKey('poll_id')) { - context.handle(_pollIdMeta, - pollId.isAcceptableOrUnknown(data['poll_id']!, _pollIdMeta)); + context.handle(_pollIdMeta, pollId.isAcceptableOrUnknown(data['poll_id']!, _pollIdMeta)); } if (data.containsKey('option_id')) { - context.handle(_optionIdMeta, - optionId.isAcceptableOrUnknown(data['option_id']!, _optionIdMeta)); + context.handle(_optionIdMeta, optionId.isAcceptableOrUnknown(data['option_id']!, _optionIdMeta)); } if (data.containsKey('answer_text')) { - context.handle( - _answerTextMeta, - answerText.isAcceptableOrUnknown( - data['answer_text']!, _answerTextMeta)); + context.handle(_answerTextMeta, answerText.isAcceptableOrUnknown(data['answer_text']!, _answerTextMeta)); } if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { - context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } if (data.containsKey('user_id')) { - context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + context.handle(_userIdMeta, userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } return context; } @@ -5874,20 +5820,13 @@ class $PollVotesTable extends PollVotes PollVoteEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return PollVoteEntity( - id: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}id']), - pollId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}poll_id']), - optionId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}option_id']), - answerText: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}answer_text']), - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, - updatedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, - userId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}user_id']), + id: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}id']), + pollId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}poll_id']), + optionId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}option_id']), + answerText: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}answer_text']), + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + userId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}user_id']), ); } @@ -5924,14 +5863,15 @@ class PollVoteEntity extends DataClass implements Insertable { /// /// Nullable if the poll is anonymous. final String? userId; - const PollVoteEntity( - {this.id, - this.pollId, - this.optionId, - this.answerText, - required this.createdAt, - required this.updatedAt, - this.userId}); + const PollVoteEntity({ + this.id, + this.pollId, + this.optionId, + this.answerText, + required this.createdAt, + required this.updatedAt, + this.userId, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -5955,8 +5895,7 @@ class PollVoteEntity extends DataClass implements Insertable { return map; } - factory PollVoteEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory PollVoteEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return PollVoteEntity( id: serializer.fromJson(json['id']), @@ -5982,30 +5921,29 @@ class PollVoteEntity extends DataClass implements Insertable { }; } - PollVoteEntity copyWith( - {Value id = const Value.absent(), - Value pollId = const Value.absent(), - Value optionId = const Value.absent(), - Value answerText = const Value.absent(), - DateTime? createdAt, - DateTime? updatedAt, - Value userId = const Value.absent()}) => - PollVoteEntity( - id: id.present ? id.value : this.id, - pollId: pollId.present ? pollId.value : this.pollId, - optionId: optionId.present ? optionId.value : this.optionId, - answerText: answerText.present ? answerText.value : this.answerText, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - userId: userId.present ? userId.value : this.userId, - ); + PollVoteEntity copyWith({ + Value id = const Value.absent(), + Value pollId = const Value.absent(), + Value optionId = const Value.absent(), + Value answerText = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + Value userId = const Value.absent(), + }) => PollVoteEntity( + id: id.present ? id.value : this.id, + pollId: pollId.present ? pollId.value : this.pollId, + optionId: optionId.present ? optionId.value : this.optionId, + answerText: answerText.present ? answerText.value : this.answerText, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + userId: userId.present ? userId.value : this.userId, + ); PollVoteEntity copyWithCompanion(PollVotesCompanion data) { return PollVoteEntity( id: data.id.present ? data.id.value : this.id, pollId: data.pollId.present ? data.pollId.value : this.pollId, optionId: data.optionId.present ? data.optionId.value : this.optionId, - answerText: - data.answerText.present ? data.answerText.value : this.answerText, + answerText: data.answerText.present ? data.answerText.value : this.answerText, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, userId: data.userId.present ? data.userId.value : this.userId, @@ -6027,8 +5965,7 @@ class PollVoteEntity extends DataClass implements Insertable { } @override - int get hashCode => Object.hash( - id, pollId, optionId, answerText, createdAt, updatedAt, userId); + int get hashCode => Object.hash(id, pollId, optionId, answerText, createdAt, updatedAt, userId); @override bool operator ==(Object other) => identical(this, other) || @@ -6093,15 +6030,16 @@ class PollVotesCompanion extends UpdateCompanion { }); } - PollVotesCompanion copyWith( - {Value? id, - Value? pollId, - Value? optionId, - Value? answerText, - Value? createdAt, - Value? updatedAt, - Value? userId, - Value? rowid}) { + PollVotesCompanion copyWith({ + Value? id, + Value? pollId, + Value? optionId, + Value? answerText, + Value? createdAt, + Value? updatedAt, + Value? userId, + Value? rowid, + }) { return PollVotesCompanion( id: id ?? this.id, pollId: pollId ?? this.pollId, @@ -6169,108 +6107,111 @@ class $PinnedMessageReactionsTable extends PinnedMessageReactions static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); @override late final GeneratedColumn userId = GeneratedColumn( - 'user_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _messageIdMeta = - const VerificationMeta('messageId'); + 'user_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _messageIdMeta = const VerificationMeta('messageId'); @override late final GeneratedColumn messageId = GeneratedColumn( - 'message_id', aliasedName, true, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES pinned_messages (id) ON DELETE CASCADE')); + 'message_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES pinned_messages (id) ON DELETE CASCADE'), + ); static const VerificationMeta _typeMeta = const VerificationMeta('type'); @override late final GeneratedColumn type = GeneratedColumn( - 'type', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _emojiCodeMeta = - const VerificationMeta('emojiCode'); + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _emojiCodeMeta = const VerificationMeta('emojiCode'); @override late final GeneratedColumn emojiCode = GeneratedColumn( - 'emoji_code', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _createdAtMeta = - const VerificationMeta('createdAt'); + 'emoji_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - static const VerificationMeta _updatedAtMeta = - const VerificationMeta('updatedAt'); + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); @override late final GeneratedColumn updatedAt = GeneratedColumn( - 'updated_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); static const VerificationMeta _scoreMeta = const VerificationMeta('score'); @override late final GeneratedColumn score = GeneratedColumn( - 'score', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(0)); - @override - late final GeneratedColumnWithTypeConverter?, String> - extraData = GeneratedColumn('extra_data', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $PinnedMessageReactionsTable.$converterextraDatan); - @override - List get $columns => [ - userId, - messageId, - type, - emojiCode, - createdAt, - updatedAt, - score, - extraData - ]; + 'score', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0), + ); + @override + late final GeneratedColumnWithTypeConverter?, String> extraData = GeneratedColumn( + 'extra_data', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($PinnedMessageReactionsTable.$converterextraDatan); + @override + List get $columns => [userId, messageId, type, emojiCode, createdAt, updatedAt, score, extraData]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'pinned_message_reactions'; @override - VerificationContext validateIntegrity( - Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('user_id')) { - context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + context.handle(_userIdMeta, userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } if (data.containsKey('message_id')) { - context.handle(_messageIdMeta, - messageId.isAcceptableOrUnknown(data['message_id']!, _messageIdMeta)); + context.handle(_messageIdMeta, messageId.isAcceptableOrUnknown(data['message_id']!, _messageIdMeta)); } if (data.containsKey('type')) { - context.handle( - _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + context.handle(_typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); } else if (isInserting) { context.missing(_typeMeta); } if (data.containsKey('emoji_code')) { - context.handle(_emojiCodeMeta, - emojiCode.isAcceptableOrUnknown(data['emoji_code']!, _emojiCodeMeta)); + context.handle(_emojiCodeMeta, emojiCode.isAcceptableOrUnknown(data['emoji_code']!, _emojiCodeMeta)); } if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { - context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } if (data.containsKey('score')) { - context.handle( - _scoreMeta, score.isAcceptableOrUnknown(data['score']!, _scoreMeta)); + context.handle(_scoreMeta, score.isAcceptableOrUnknown(data['score']!, _scoreMeta)); } return context; } @@ -6278,27 +6219,19 @@ class $PinnedMessageReactionsTable extends PinnedMessageReactions @override Set get $primaryKey => {messageId, type, userId}; @override - PinnedMessageReactionEntity map(Map data, - {String? tablePrefix}) { + PinnedMessageReactionEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return PinnedMessageReactionEntity( - userId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}user_id']), - messageId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}message_id']), - type: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}type'])!, - emojiCode: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}emoji_code']), - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, - updatedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, - score: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}score'])!, + userId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}user_id']), + messageId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}message_id']), + type: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}type'])!, + emojiCode: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}emoji_code']), + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + score: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}score'])!, extraData: $PinnedMessageReactionsTable.$converterextraDatan.fromSql( - attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}extra_data']), + ), ); } @@ -6307,14 +6240,13 @@ class $PinnedMessageReactionsTable extends PinnedMessageReactions return $PinnedMessageReactionsTable(attachedDatabase, alias); } - static TypeConverter, String> $converterextraData = - MapConverter(); - static TypeConverter?, String?> $converterextraDatan = - NullAwareTypeConverter.wrap($converterextraData); + static TypeConverter, String> $converterextraData = MapConverter(); + static TypeConverter?, String?> $converterextraDatan = NullAwareTypeConverter.wrap( + $converterextraData, + ); } -class PinnedMessageReactionEntity extends DataClass - implements Insertable { +class PinnedMessageReactionEntity extends DataClass implements Insertable { /// The id of the user that sent the reaction final String? userId; @@ -6338,15 +6270,16 @@ class PinnedMessageReactionEntity extends DataClass /// Reaction custom extraData final Map? extraData; - const PinnedMessageReactionEntity( - {this.userId, - this.messageId, - required this.type, - this.emojiCode, - required this.createdAt, - required this.updatedAt, - required this.score, - this.extraData}); + const PinnedMessageReactionEntity({ + this.userId, + this.messageId, + required this.type, + this.emojiCode, + required this.createdAt, + required this.updatedAt, + required this.score, + this.extraData, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -6364,14 +6297,12 @@ class PinnedMessageReactionEntity extends DataClass map['updated_at'] = Variable(updatedAt); map['score'] = Variable(score); if (!nullToAbsent || extraData != null) { - map['extra_data'] = Variable( - $PinnedMessageReactionsTable.$converterextraDatan.toSql(extraData)); + map['extra_data'] = Variable($PinnedMessageReactionsTable.$converterextraDatan.toSql(extraData)); } return map; } - factory PinnedMessageReactionEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory PinnedMessageReactionEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return PinnedMessageReactionEntity( userId: serializer.fromJson(json['userId']), @@ -6399,27 +6330,26 @@ class PinnedMessageReactionEntity extends DataClass }; } - PinnedMessageReactionEntity copyWith( - {Value userId = const Value.absent(), - Value messageId = const Value.absent(), - String? type, - Value emojiCode = const Value.absent(), - DateTime? createdAt, - DateTime? updatedAt, - int? score, - Value?> extraData = const Value.absent()}) => - PinnedMessageReactionEntity( - userId: userId.present ? userId.value : this.userId, - messageId: messageId.present ? messageId.value : this.messageId, - type: type ?? this.type, - emojiCode: emojiCode.present ? emojiCode.value : this.emojiCode, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - score: score ?? this.score, - extraData: extraData.present ? extraData.value : this.extraData, - ); - PinnedMessageReactionEntity copyWithCompanion( - PinnedMessageReactionsCompanion data) { + PinnedMessageReactionEntity copyWith({ + Value userId = const Value.absent(), + Value messageId = const Value.absent(), + String? type, + Value emojiCode = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + int? score, + Value?> extraData = const Value.absent(), + }) => PinnedMessageReactionEntity( + userId: userId.present ? userId.value : this.userId, + messageId: messageId.present ? messageId.value : this.messageId, + type: type ?? this.type, + emojiCode: emojiCode.present ? emojiCode.value : this.emojiCode, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + score: score ?? this.score, + extraData: extraData.present ? extraData.value : this.extraData, + ); + PinnedMessageReactionEntity copyWithCompanion(PinnedMessageReactionsCompanion data) { return PinnedMessageReactionEntity( userId: data.userId.present ? data.userId.value : this.userId, messageId: data.messageId.present ? data.messageId.value : this.messageId, @@ -6448,8 +6378,7 @@ class PinnedMessageReactionEntity extends DataClass } @override - int get hashCode => Object.hash(userId, messageId, type, emojiCode, createdAt, - updatedAt, score, extraData); + int get hashCode => Object.hash(userId, messageId, type, emojiCode, createdAt, updatedAt, score, extraData); @override bool operator ==(Object other) => identical(this, other) || @@ -6464,8 +6393,7 @@ class PinnedMessageReactionEntity extends DataClass other.extraData == this.extraData); } -class PinnedMessageReactionsCompanion - extends UpdateCompanion { +class PinnedMessageReactionsCompanion extends UpdateCompanion { final Value userId; final Value messageId; final Value type; @@ -6521,16 +6449,17 @@ class PinnedMessageReactionsCompanion }); } - PinnedMessageReactionsCompanion copyWith( - {Value? userId, - Value? messageId, - Value? type, - Value? emojiCode, - Value? createdAt, - Value? updatedAt, - Value? score, - Value?>? extraData, - Value? rowid}) { + PinnedMessageReactionsCompanion copyWith({ + Value? userId, + Value? messageId, + Value? type, + Value? emojiCode, + Value? createdAt, + Value? updatedAt, + Value? score, + Value?>? extraData, + Value? rowid, + }) { return PinnedMessageReactionsCompanion( userId: userId ?? this.userId, messageId: messageId ?? this.messageId, @@ -6569,9 +6498,7 @@ class PinnedMessageReactionsCompanion map['score'] = Variable(score.value); } if (extraData.present) { - map['extra_data'] = Variable($PinnedMessageReactionsTable - .$converterextraDatan - .toSql(extraData.value)); + map['extra_data'] = Variable($PinnedMessageReactionsTable.$converterextraDatan.toSql(extraData.value)); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -6596,8 +6523,7 @@ class PinnedMessageReactionsCompanion } } -class $ReactionsTable extends Reactions - with TableInfo<$ReactionsTable, ReactionEntity> { +class $ReactionsTable extends Reactions with TableInfo<$ReactionsTable, ReactionEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; @@ -6605,107 +6531,111 @@ class $ReactionsTable extends Reactions static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); @override late final GeneratedColumn userId = GeneratedColumn( - 'user_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _messageIdMeta = - const VerificationMeta('messageId'); + 'user_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _messageIdMeta = const VerificationMeta('messageId'); @override late final GeneratedColumn messageId = GeneratedColumn( - 'message_id', aliasedName, true, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES messages (id) ON DELETE CASCADE')); + 'message_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES messages (id) ON DELETE CASCADE'), + ); static const VerificationMeta _typeMeta = const VerificationMeta('type'); @override late final GeneratedColumn type = GeneratedColumn( - 'type', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _emojiCodeMeta = - const VerificationMeta('emojiCode'); + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _emojiCodeMeta = const VerificationMeta('emojiCode'); @override late final GeneratedColumn emojiCode = GeneratedColumn( - 'emoji_code', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _createdAtMeta = - const VerificationMeta('createdAt'); + 'emoji_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - static const VerificationMeta _updatedAtMeta = - const VerificationMeta('updatedAt'); + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); @override late final GeneratedColumn updatedAt = GeneratedColumn( - 'updated_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); static const VerificationMeta _scoreMeta = const VerificationMeta('score'); @override late final GeneratedColumn score = GeneratedColumn( - 'score', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(0)); - @override - late final GeneratedColumnWithTypeConverter?, String> - extraData = GeneratedColumn('extra_data', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $ReactionsTable.$converterextraDatan); - @override - List get $columns => [ - userId, - messageId, - type, - emojiCode, - createdAt, - updatedAt, - score, - extraData - ]; + 'score', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0), + ); + @override + late final GeneratedColumnWithTypeConverter?, String> extraData = GeneratedColumn( + 'extra_data', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($ReactionsTable.$converterextraDatan); + @override + List get $columns => [userId, messageId, type, emojiCode, createdAt, updatedAt, score, extraData]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'reactions'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('user_id')) { - context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + context.handle(_userIdMeta, userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } if (data.containsKey('message_id')) { - context.handle(_messageIdMeta, - messageId.isAcceptableOrUnknown(data['message_id']!, _messageIdMeta)); + context.handle(_messageIdMeta, messageId.isAcceptableOrUnknown(data['message_id']!, _messageIdMeta)); } if (data.containsKey('type')) { - context.handle( - _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + context.handle(_typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); } else if (isInserting) { context.missing(_typeMeta); } if (data.containsKey('emoji_code')) { - context.handle(_emojiCodeMeta, - emojiCode.isAcceptableOrUnknown(data['emoji_code']!, _emojiCodeMeta)); + context.handle(_emojiCodeMeta, emojiCode.isAcceptableOrUnknown(data['emoji_code']!, _emojiCodeMeta)); } if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { - context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } if (data.containsKey('score')) { - context.handle( - _scoreMeta, score.isAcceptableOrUnknown(data['score']!, _scoreMeta)); + context.handle(_scoreMeta, score.isAcceptableOrUnknown(data['score']!, _scoreMeta)); } return context; } @@ -6716,23 +6646,16 @@ class $ReactionsTable extends Reactions ReactionEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return ReactionEntity( - userId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}user_id']), - messageId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}message_id']), - type: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}type'])!, - emojiCode: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}emoji_code']), - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, - updatedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, - score: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}score'])!, - extraData: $ReactionsTable.$converterextraDatan.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])), + userId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}user_id']), + messageId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}message_id']), + type: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}type'])!, + emojiCode: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}emoji_code']), + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + score: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}score'])!, + extraData: $ReactionsTable.$converterextraDatan.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}extra_data']), + ), ); } @@ -6741,10 +6664,10 @@ class $ReactionsTable extends Reactions return $ReactionsTable(attachedDatabase, alias); } - static TypeConverter, String> $converterextraData = - MapConverter(); - static TypeConverter?, String?> $converterextraDatan = - NullAwareTypeConverter.wrap($converterextraData); + static TypeConverter, String> $converterextraData = MapConverter(); + static TypeConverter?, String?> $converterextraDatan = NullAwareTypeConverter.wrap( + $converterextraData, + ); } class ReactionEntity extends DataClass implements Insertable { @@ -6771,15 +6694,16 @@ class ReactionEntity extends DataClass implements Insertable { /// Reaction custom extraData final Map? extraData; - const ReactionEntity( - {this.userId, - this.messageId, - required this.type, - this.emojiCode, - required this.createdAt, - required this.updatedAt, - required this.score, - this.extraData}); + const ReactionEntity({ + this.userId, + this.messageId, + required this.type, + this.emojiCode, + required this.createdAt, + required this.updatedAt, + required this.score, + this.extraData, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -6797,14 +6721,12 @@ class ReactionEntity extends DataClass implements Insertable { map['updated_at'] = Variable(updatedAt); map['score'] = Variable(score); if (!nullToAbsent || extraData != null) { - map['extra_data'] = Variable( - $ReactionsTable.$converterextraDatan.toSql(extraData)); + map['extra_data'] = Variable($ReactionsTable.$converterextraDatan.toSql(extraData)); } return map; } - factory ReactionEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory ReactionEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return ReactionEntity( userId: serializer.fromJson(json['userId']), @@ -6832,25 +6754,25 @@ class ReactionEntity extends DataClass implements Insertable { }; } - ReactionEntity copyWith( - {Value userId = const Value.absent(), - Value messageId = const Value.absent(), - String? type, - Value emojiCode = const Value.absent(), - DateTime? createdAt, - DateTime? updatedAt, - int? score, - Value?> extraData = const Value.absent()}) => - ReactionEntity( - userId: userId.present ? userId.value : this.userId, - messageId: messageId.present ? messageId.value : this.messageId, - type: type ?? this.type, - emojiCode: emojiCode.present ? emojiCode.value : this.emojiCode, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - score: score ?? this.score, - extraData: extraData.present ? extraData.value : this.extraData, - ); + ReactionEntity copyWith({ + Value userId = const Value.absent(), + Value messageId = const Value.absent(), + String? type, + Value emojiCode = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + int? score, + Value?> extraData = const Value.absent(), + }) => ReactionEntity( + userId: userId.present ? userId.value : this.userId, + messageId: messageId.present ? messageId.value : this.messageId, + type: type ?? this.type, + emojiCode: emojiCode.present ? emojiCode.value : this.emojiCode, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + score: score ?? this.score, + extraData: extraData.present ? extraData.value : this.extraData, + ); ReactionEntity copyWithCompanion(ReactionsCompanion data) { return ReactionEntity( userId: data.userId.present ? data.userId.value : this.userId, @@ -6880,8 +6802,7 @@ class ReactionEntity extends DataClass implements Insertable { } @override - int get hashCode => Object.hash(userId, messageId, type, emojiCode, createdAt, - updatedAt, score, extraData); + int get hashCode => Object.hash(userId, messageId, type, emojiCode, createdAt, updatedAt, score, extraData); @override bool operator ==(Object other) => identical(this, other) || @@ -6952,16 +6873,17 @@ class ReactionsCompanion extends UpdateCompanion { }); } - ReactionsCompanion copyWith( - {Value? userId, - Value? messageId, - Value? type, - Value? emojiCode, - Value? createdAt, - Value? updatedAt, - Value? score, - Value?>? extraData, - Value? rowid}) { + ReactionsCompanion copyWith({ + Value? userId, + Value? messageId, + Value? type, + Value? emojiCode, + Value? createdAt, + Value? updatedAt, + Value? score, + Value?>? extraData, + Value? rowid, + }) { return ReactionsCompanion( userId: userId ?? this.userId, messageId: messageId ?? this.messageId, @@ -7000,8 +6922,7 @@ class ReactionsCompanion extends UpdateCompanion { map['score'] = Variable(score.value); } if (extraData.present) { - map['extra_data'] = Variable( - $ReactionsTable.$converterextraDatan.toSql(extraData.value)); + map['extra_data'] = Variable($ReactionsTable.$converterextraDatan.toSql(extraData.value)); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -7034,94 +6955,125 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); static const VerificationMeta _roleMeta = const VerificationMeta('role'); @override late final GeneratedColumn role = GeneratedColumn( - 'role', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _languageMeta = - const VerificationMeta('language'); + 'role', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _languageMeta = const VerificationMeta('language'); @override late final GeneratedColumn language = GeneratedColumn( - 'language', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _createdAtMeta = - const VerificationMeta('createdAt'); + 'language', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _updatedAtMeta = - const VerificationMeta('updatedAt'); + 'created_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); @override late final GeneratedColumn updatedAt = GeneratedColumn( - 'updated_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _lastActiveMeta = - const VerificationMeta('lastActive'); + 'updated_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _lastActiveMeta = const VerificationMeta('lastActive'); @override late final GeneratedColumn lastActive = GeneratedColumn( - 'last_active', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); + 'last_active', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); static const VerificationMeta _onlineMeta = const VerificationMeta('online'); @override late final GeneratedColumn online = GeneratedColumn( - 'online', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("online" IN (0, 1))'), - defaultValue: const Constant(false)); + 'online', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("online" IN (0, 1))'), + defaultValue: const Constant(false), + ); static const VerificationMeta _bannedMeta = const VerificationMeta('banned'); @override late final GeneratedColumn banned = GeneratedColumn( - 'banned', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("banned" IN (0, 1))'), - defaultValue: const Constant(false)); - @override - late final GeneratedColumnWithTypeConverter?, String> - teamsRole = GeneratedColumn('teams_role', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $UsersTable.$converterteamsRolen); - static const VerificationMeta _avgResponseTimeMeta = - const VerificationMeta('avgResponseTime'); + 'banned', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("banned" IN (0, 1))'), + defaultValue: const Constant(false), + ); + @override + late final GeneratedColumnWithTypeConverter?, String> teamsRole = GeneratedColumn( + 'teams_role', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($UsersTable.$converterteamsRolen); + static const VerificationMeta _avgResponseTimeMeta = const VerificationMeta('avgResponseTime'); @override late final GeneratedColumn avgResponseTime = GeneratedColumn( - 'avg_response_time', aliasedName, true, - type: DriftSqlType.int, requiredDuringInsert: false); - @override - late final GeneratedColumnWithTypeConverter, String> - extraData = GeneratedColumn('extra_data', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>($UsersTable.$converterextraData); + 'avg_response_time', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + late final GeneratedColumnWithTypeConverter, String> extraData = GeneratedColumn( + 'extra_data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($UsersTable.$converterextraData); @override List get $columns => [ - id, - role, - language, - createdAt, - updatedAt, - lastActive, - online, - banned, - teamsRole, - avgResponseTime, - extraData - ]; + id, + role, + language, + createdAt, + updatedAt, + lastActive, + online, + banned, + teamsRole, + avgResponseTime, + extraData, + ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'users'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { @@ -7130,40 +7082,31 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { context.missing(_idMeta); } if (data.containsKey('role')) { - context.handle( - _roleMeta, role.isAcceptableOrUnknown(data['role']!, _roleMeta)); + context.handle(_roleMeta, role.isAcceptableOrUnknown(data['role']!, _roleMeta)); } if (data.containsKey('language')) { - context.handle(_languageMeta, - language.isAcceptableOrUnknown(data['language']!, _languageMeta)); + context.handle(_languageMeta, language.isAcceptableOrUnknown(data['language']!, _languageMeta)); } if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { - context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } if (data.containsKey('last_active')) { - context.handle( - _lastActiveMeta, - lastActive.isAcceptableOrUnknown( - data['last_active']!, _lastActiveMeta)); + context.handle(_lastActiveMeta, lastActive.isAcceptableOrUnknown(data['last_active']!, _lastActiveMeta)); } if (data.containsKey('online')) { - context.handle(_onlineMeta, - online.isAcceptableOrUnknown(data['online']!, _onlineMeta)); + context.handle(_onlineMeta, online.isAcceptableOrUnknown(data['online']!, _onlineMeta)); } if (data.containsKey('banned')) { - context.handle(_bannedMeta, - banned.isAcceptableOrUnknown(data['banned']!, _bannedMeta)); + context.handle(_bannedMeta, banned.isAcceptableOrUnknown(data['banned']!, _bannedMeta)); } if (data.containsKey('avg_response_time')) { context.handle( - _avgResponseTimeMeta, - avgResponseTime.isAcceptableOrUnknown( - data['avg_response_time']!, _avgResponseTimeMeta)); + _avgResponseTimeMeta, + avgResponseTime.isAcceptableOrUnknown(data['avg_response_time']!, _avgResponseTimeMeta), + ); } return context; } @@ -7174,30 +7117,21 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { UserEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return UserEntity( - id: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}id'])!, - role: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}role']), - language: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}language']), - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at']), - updatedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at']), - lastActive: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}last_active']), - online: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}online'])!, - banned: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}banned'])!, - teamsRole: $UsersTable.$converterteamsRolen.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}teams_role'])), - avgResponseTime: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}avg_response_time']), - extraData: $UsersTable.$converterextraData.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])!), + id: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}id'])!, + role: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}role']), + language: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}language']), + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at']), + updatedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at']), + lastActive: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}last_active']), + online: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}online'])!, + banned: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}banned'])!, + teamsRole: $UsersTable.$converterteamsRolen.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}teams_role']), + ), + avgResponseTime: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}avg_response_time']), + extraData: $UsersTable.$converterextraData.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}extra_data'])!, + ), ); } @@ -7206,12 +7140,11 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { return $UsersTable(attachedDatabase, alias); } - static TypeConverter, String> $converterteamsRole = - MapConverter(); - static TypeConverter?, String?> $converterteamsRolen = - NullAwareTypeConverter.wrap($converterteamsRole); - static TypeConverter, String> $converterextraData = - MapConverter(); + static TypeConverter, String> $converterteamsRole = MapConverter(); + static TypeConverter?, String?> $converterteamsRolen = NullAwareTypeConverter.wrap( + $converterteamsRole, + ); + static TypeConverter, String> $converterextraData = MapConverter(); } class UserEntity extends DataClass implements Insertable { @@ -7249,18 +7182,19 @@ class UserEntity extends DataClass implements Insertable { /// Map of custom user extraData final Map extraData; - const UserEntity( - {required this.id, - this.role, - this.language, - this.createdAt, - this.updatedAt, - this.lastActive, - required this.online, - required this.banned, - this.teamsRole, - this.avgResponseTime, - required this.extraData}); + const UserEntity({ + required this.id, + this.role, + this.language, + this.createdAt, + this.updatedAt, + this.lastActive, + required this.online, + required this.banned, + this.teamsRole, + this.avgResponseTime, + required this.extraData, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -7283,21 +7217,18 @@ class UserEntity extends DataClass implements Insertable { map['online'] = Variable(online); map['banned'] = Variable(banned); if (!nullToAbsent || teamsRole != null) { - map['teams_role'] = - Variable($UsersTable.$converterteamsRolen.toSql(teamsRole)); + map['teams_role'] = Variable($UsersTable.$converterteamsRolen.toSql(teamsRole)); } if (!nullToAbsent || avgResponseTime != null) { map['avg_response_time'] = Variable(avgResponseTime); } { - map['extra_data'] = - Variable($UsersTable.$converterextraData.toSql(extraData)); + map['extra_data'] = Variable($UsersTable.$converterextraData.toSql(extraData)); } return map; } - factory UserEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory UserEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return UserEntity( id: serializer.fromJson(json['id']), @@ -7331,33 +7262,31 @@ class UserEntity extends DataClass implements Insertable { }; } - UserEntity copyWith( - {String? id, - Value role = const Value.absent(), - Value language = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value lastActive = const Value.absent(), - bool? online, - bool? banned, - Value?> teamsRole = const Value.absent(), - Value avgResponseTime = const Value.absent(), - Map? extraData}) => - UserEntity( - id: id ?? this.id, - role: role.present ? role.value : this.role, - language: language.present ? language.value : this.language, - createdAt: createdAt.present ? createdAt.value : this.createdAt, - updatedAt: updatedAt.present ? updatedAt.value : this.updatedAt, - lastActive: lastActive.present ? lastActive.value : this.lastActive, - online: online ?? this.online, - banned: banned ?? this.banned, - teamsRole: teamsRole.present ? teamsRole.value : this.teamsRole, - avgResponseTime: avgResponseTime.present - ? avgResponseTime.value - : this.avgResponseTime, - extraData: extraData ?? this.extraData, - ); + UserEntity copyWith({ + String? id, + Value role = const Value.absent(), + Value language = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value lastActive = const Value.absent(), + bool? online, + bool? banned, + Value?> teamsRole = const Value.absent(), + Value avgResponseTime = const Value.absent(), + Map? extraData, + }) => UserEntity( + id: id ?? this.id, + role: role.present ? role.value : this.role, + language: language.present ? language.value : this.language, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + updatedAt: updatedAt.present ? updatedAt.value : this.updatedAt, + lastActive: lastActive.present ? lastActive.value : this.lastActive, + online: online ?? this.online, + banned: banned ?? this.banned, + teamsRole: teamsRole.present ? teamsRole.value : this.teamsRole, + avgResponseTime: avgResponseTime.present ? avgResponseTime.value : this.avgResponseTime, + extraData: extraData ?? this.extraData, + ); UserEntity copyWithCompanion(UsersCompanion data) { return UserEntity( id: data.id.present ? data.id.value : this.id, @@ -7365,14 +7294,11 @@ class UserEntity extends DataClass implements Insertable { language: data.language.present ? data.language.value : this.language, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, - lastActive: - data.lastActive.present ? data.lastActive.value : this.lastActive, + lastActive: data.lastActive.present ? data.lastActive.value : this.lastActive, online: data.online.present ? data.online.value : this.online, banned: data.banned.present ? data.banned.value : this.banned, teamsRole: data.teamsRole.present ? data.teamsRole.value : this.teamsRole, - avgResponseTime: data.avgResponseTime.present - ? data.avgResponseTime.value - : this.avgResponseTime, + avgResponseTime: data.avgResponseTime.present ? data.avgResponseTime.value : this.avgResponseTime, extraData: data.extraData.present ? data.extraData.value : this.extraData, ); } @@ -7396,8 +7322,19 @@ class UserEntity extends DataClass implements Insertable { } @override - int get hashCode => Object.hash(id, role, language, createdAt, updatedAt, - lastActive, online, banned, teamsRole, avgResponseTime, extraData); + int get hashCode => Object.hash( + id, + role, + language, + createdAt, + updatedAt, + lastActive, + online, + banned, + teamsRole, + avgResponseTime, + extraData, + ); @override bool operator ==(Object other) => identical(this, other) || @@ -7455,8 +7392,8 @@ class UsersCompanion extends UpdateCompanion { this.avgResponseTime = const Value.absent(), required Map extraData, this.rowid = const Value.absent(), - }) : id = Value(id), - extraData = Value(extraData); + }) : id = Value(id), + extraData = Value(extraData); static Insertable custom({ Expression? id, Expression? role, @@ -7487,19 +7424,20 @@ class UsersCompanion extends UpdateCompanion { }); } - UsersCompanion copyWith( - {Value? id, - Value? role, - Value? language, - Value? createdAt, - Value? updatedAt, - Value? lastActive, - Value? online, - Value? banned, - Value?>? teamsRole, - Value? avgResponseTime, - Value>? extraData, - Value? rowid}) { + UsersCompanion copyWith({ + Value? id, + Value? role, + Value? language, + Value? createdAt, + Value? updatedAt, + Value? lastActive, + Value? online, + Value? banned, + Value?>? teamsRole, + Value? avgResponseTime, + Value>? extraData, + Value? rowid, + }) { return UsersCompanion( id: id ?? this.id, role: role ?? this.role, @@ -7544,15 +7482,13 @@ class UsersCompanion extends UpdateCompanion { map['banned'] = Variable(banned.value); } if (teamsRole.present) { - map['teams_role'] = Variable( - $UsersTable.$converterteamsRolen.toSql(teamsRole.value)); + map['teams_role'] = Variable($UsersTable.$converterteamsRolen.toSql(teamsRole.value)); } if (avgResponseTime.present) { map['avg_response_time'] = Variable(avgResponseTime.value); } if (extraData.present) { - map['extra_data'] = Variable( - $UsersTable.$converterextraData.toSql(extraData.value)); + map['extra_data'] = Variable($UsersTable.$converterextraData.toSql(extraData.value)); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -7580,8 +7516,7 @@ class UsersCompanion extends UpdateCompanion { } } -class $MembersTable extends Members - with TableInfo<$MembersTable, MemberEntity> { +class $MembersTable extends Members with TableInfo<$MembersTable, MemberEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; @@ -7589,215 +7524,224 @@ class $MembersTable extends Members static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); @override late final GeneratedColumn userId = GeneratedColumn( - 'user_id', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _channelCidMeta = - const VerificationMeta('channelCid'); + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); @override late final GeneratedColumn channelCid = GeneratedColumn( - 'channel_cid', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES channels (cid) ON DELETE CASCADE')); - static const VerificationMeta _channelRoleMeta = - const VerificationMeta('channelRole'); + 'channel_cid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES channels (cid) ON DELETE CASCADE'), + ); + static const VerificationMeta _channelRoleMeta = const VerificationMeta('channelRole'); @override late final GeneratedColumn channelRole = GeneratedColumn( - 'channel_role', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _inviteAcceptedAtMeta = - const VerificationMeta('inviteAcceptedAt'); - @override - late final GeneratedColumn inviteAcceptedAt = - GeneratedColumn('invite_accepted_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _inviteRejectedAtMeta = - const VerificationMeta('inviteRejectedAt'); - @override - late final GeneratedColumn inviteRejectedAt = - GeneratedColumn('invite_rejected_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _invitedMeta = - const VerificationMeta('invited'); + 'channel_role', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _inviteAcceptedAtMeta = const VerificationMeta('inviteAcceptedAt'); + @override + late final GeneratedColumn inviteAcceptedAt = GeneratedColumn( + 'invite_accepted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _inviteRejectedAtMeta = const VerificationMeta('inviteRejectedAt'); + @override + late final GeneratedColumn inviteRejectedAt = GeneratedColumn( + 'invite_rejected_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _invitedMeta = const VerificationMeta('invited'); @override late final GeneratedColumn invited = GeneratedColumn( - 'invited', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("invited" IN (0, 1))'), - defaultValue: const Constant(false)); + 'invited', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("invited" IN (0, 1))'), + defaultValue: const Constant(false), + ); static const VerificationMeta _bannedMeta = const VerificationMeta('banned'); @override late final GeneratedColumn banned = GeneratedColumn( - 'banned', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("banned" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _shadowBannedMeta = - const VerificationMeta('shadowBanned'); + 'banned', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("banned" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _shadowBannedMeta = const VerificationMeta('shadowBanned'); @override late final GeneratedColumn shadowBanned = GeneratedColumn( - 'shadow_banned', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("shadow_banned" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _pinnedAtMeta = - const VerificationMeta('pinnedAt'); + 'shadow_banned', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("shadow_banned" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _pinnedAtMeta = const VerificationMeta('pinnedAt'); @override late final GeneratedColumn pinnedAt = GeneratedColumn( - 'pinned_at', aliasedName, true, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: const Constant(null)); - static const VerificationMeta _archivedAtMeta = - const VerificationMeta('archivedAt'); + 'pinned_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const Constant(null), + ); + static const VerificationMeta _archivedAtMeta = const VerificationMeta('archivedAt'); @override late final GeneratedColumn archivedAt = GeneratedColumn( - 'archived_at', aliasedName, true, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: const Constant(null)); - static const VerificationMeta _isModeratorMeta = - const VerificationMeta('isModerator'); + 'archived_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const Constant(null), + ); + static const VerificationMeta _isModeratorMeta = const VerificationMeta('isModerator'); @override late final GeneratedColumn isModerator = GeneratedColumn( - 'is_moderator', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("is_moderator" IN (0, 1))'), - defaultValue: const Constant(false)); - @override - late final GeneratedColumnWithTypeConverter?, String> - extraData = GeneratedColumn('extra_data', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $MembersTable.$converterextraDatan); - static const VerificationMeta _createdAtMeta = - const VerificationMeta('createdAt'); + 'is_moderator', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("is_moderator" IN (0, 1))'), + defaultValue: const Constant(false), + ); + @override + late final GeneratedColumnWithTypeConverter?, String> extraData = GeneratedColumn( + 'extra_data', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($MembersTable.$converterextraDatan); + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - static const VerificationMeta _updatedAtMeta = - const VerificationMeta('updatedAt'); + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); @override late final GeneratedColumn updatedAt = GeneratedColumn( - 'updated_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - @override - late final GeneratedColumnWithTypeConverter, String> - deletedMessages = GeneratedColumn( - 'deleted_messages', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>($MembersTable.$converterdeletedMessages); + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + @override + late final GeneratedColumnWithTypeConverter, String> deletedMessages = GeneratedColumn( + 'deleted_messages', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($MembersTable.$converterdeletedMessages); @override List get $columns => [ - userId, - channelCid, - channelRole, - inviteAcceptedAt, - inviteRejectedAt, - invited, - banned, - shadowBanned, - pinnedAt, - archivedAt, - isModerator, - extraData, - createdAt, - updatedAt, - deletedMessages - ]; + userId, + channelCid, + channelRole, + inviteAcceptedAt, + inviteRejectedAt, + invited, + banned, + shadowBanned, + pinnedAt, + archivedAt, + isModerator, + extraData, + createdAt, + updatedAt, + deletedMessages, + ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'members'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('user_id')) { - context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + context.handle(_userIdMeta, userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } else if (isInserting) { context.missing(_userIdMeta); } if (data.containsKey('channel_cid')) { - context.handle( - _channelCidMeta, - channelCid.isAcceptableOrUnknown( - data['channel_cid']!, _channelCidMeta)); + context.handle(_channelCidMeta, channelCid.isAcceptableOrUnknown(data['channel_cid']!, _channelCidMeta)); } else if (isInserting) { context.missing(_channelCidMeta); } if (data.containsKey('channel_role')) { - context.handle( - _channelRoleMeta, - channelRole.isAcceptableOrUnknown( - data['channel_role']!, _channelRoleMeta)); + context.handle(_channelRoleMeta, channelRole.isAcceptableOrUnknown(data['channel_role']!, _channelRoleMeta)); } if (data.containsKey('invite_accepted_at')) { context.handle( - _inviteAcceptedAtMeta, - inviteAcceptedAt.isAcceptableOrUnknown( - data['invite_accepted_at']!, _inviteAcceptedAtMeta)); + _inviteAcceptedAtMeta, + inviteAcceptedAt.isAcceptableOrUnknown(data['invite_accepted_at']!, _inviteAcceptedAtMeta), + ); } if (data.containsKey('invite_rejected_at')) { context.handle( - _inviteRejectedAtMeta, - inviteRejectedAt.isAcceptableOrUnknown( - data['invite_rejected_at']!, _inviteRejectedAtMeta)); + _inviteRejectedAtMeta, + inviteRejectedAt.isAcceptableOrUnknown(data['invite_rejected_at']!, _inviteRejectedAtMeta), + ); } if (data.containsKey('invited')) { - context.handle(_invitedMeta, - invited.isAcceptableOrUnknown(data['invited']!, _invitedMeta)); + context.handle(_invitedMeta, invited.isAcceptableOrUnknown(data['invited']!, _invitedMeta)); } if (data.containsKey('banned')) { - context.handle(_bannedMeta, - banned.isAcceptableOrUnknown(data['banned']!, _bannedMeta)); + context.handle(_bannedMeta, banned.isAcceptableOrUnknown(data['banned']!, _bannedMeta)); } if (data.containsKey('shadow_banned')) { - context.handle( - _shadowBannedMeta, - shadowBanned.isAcceptableOrUnknown( - data['shadow_banned']!, _shadowBannedMeta)); + context.handle(_shadowBannedMeta, shadowBanned.isAcceptableOrUnknown(data['shadow_banned']!, _shadowBannedMeta)); } if (data.containsKey('pinned_at')) { - context.handle(_pinnedAtMeta, - pinnedAt.isAcceptableOrUnknown(data['pinned_at']!, _pinnedAtMeta)); + context.handle(_pinnedAtMeta, pinnedAt.isAcceptableOrUnknown(data['pinned_at']!, _pinnedAtMeta)); } if (data.containsKey('archived_at')) { - context.handle( - _archivedAtMeta, - archivedAt.isAcceptableOrUnknown( - data['archived_at']!, _archivedAtMeta)); + context.handle(_archivedAtMeta, archivedAt.isAcceptableOrUnknown(data['archived_at']!, _archivedAtMeta)); } if (data.containsKey('is_moderator')) { - context.handle( - _isModeratorMeta, - isModerator.isAcceptableOrUnknown( - data['is_moderator']!, _isModeratorMeta)); + context.handle(_isModeratorMeta, isModerator.isAcceptableOrUnknown(data['is_moderator']!, _isModeratorMeta)); } if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { - context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } return context; } @@ -7808,38 +7752,31 @@ class $MembersTable extends Members MemberEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return MemberEntity( - userId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}user_id'])!, - channelCid: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, - channelRole: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_role']), + userId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}user_id'])!, + channelCid: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, + channelRole: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_role']), inviteAcceptedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}invite_accepted_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}invite_accepted_at'], + ), inviteRejectedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}invite_rejected_at']), - invited: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}invited'])!, - banned: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}banned'])!, - shadowBanned: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}shadow_banned'])!, - pinnedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}pinned_at']), - archivedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}archived_at']), - isModerator: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}is_moderator'])!, - extraData: $MembersTable.$converterextraDatan.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])), - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, - updatedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + DriftSqlType.dateTime, + data['${effectivePrefix}invite_rejected_at'], + ), + invited: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}invited'])!, + banned: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}banned'])!, + shadowBanned: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}shadow_banned'])!, + pinnedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}pinned_at']), + archivedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}archived_at']), + isModerator: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}is_moderator'])!, + extraData: $MembersTable.$converterextraDatan.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}extra_data']), + ), + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, deletedMessages: $MembersTable.$converterdeletedMessages.fromSql( - attachedDatabase.typeMapping.read(DriftSqlType.string, - data['${effectivePrefix}deleted_messages'])!), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}deleted_messages'])!, + ), ); } @@ -7848,12 +7785,11 @@ class $MembersTable extends Members return $MembersTable(attachedDatabase, alias); } - static TypeConverter, String> $converterextraData = - MapConverter(); - static TypeConverter?, String?> $converterextraDatan = - NullAwareTypeConverter.wrap($converterextraData); - static TypeConverter, String> $converterdeletedMessages = - ListConverter(); + static TypeConverter, String> $converterextraData = MapConverter(); + static TypeConverter?, String?> $converterextraDatan = NullAwareTypeConverter.wrap( + $converterextraData, + ); + static TypeConverter, String> $converterdeletedMessages = ListConverter(); } class MemberEntity extends DataClass implements Insertable { @@ -7904,22 +7840,23 @@ class MemberEntity extends DataClass implements Insertable { /// These messages are now marked deleted for this member, but are still /// kept as regular to other channel members. final List deletedMessages; - const MemberEntity( - {required this.userId, - required this.channelCid, - this.channelRole, - this.inviteAcceptedAt, - this.inviteRejectedAt, - required this.invited, - required this.banned, - required this.shadowBanned, - this.pinnedAt, - this.archivedAt, - required this.isModerator, - this.extraData, - required this.createdAt, - required this.updatedAt, - required this.deletedMessages}); + const MemberEntity({ + required this.userId, + required this.channelCid, + this.channelRole, + this.inviteAcceptedAt, + this.inviteRejectedAt, + required this.invited, + required this.banned, + required this.shadowBanned, + this.pinnedAt, + this.archivedAt, + required this.isModerator, + this.extraData, + required this.createdAt, + required this.updatedAt, + required this.deletedMessages, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -7945,29 +7882,24 @@ class MemberEntity extends DataClass implements Insertable { } map['is_moderator'] = Variable(isModerator); if (!nullToAbsent || extraData != null) { - map['extra_data'] = - Variable($MembersTable.$converterextraDatan.toSql(extraData)); + map['extra_data'] = Variable($MembersTable.$converterextraDatan.toSql(extraData)); } map['created_at'] = Variable(createdAt); map['updated_at'] = Variable(updatedAt); { - map['deleted_messages'] = Variable( - $MembersTable.$converterdeletedMessages.toSql(deletedMessages)); + map['deleted_messages'] = Variable($MembersTable.$converterdeletedMessages.toSql(deletedMessages)); } return map; } - factory MemberEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory MemberEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return MemberEntity( userId: serializer.fromJson(json['userId']), channelCid: serializer.fromJson(json['channelCid']), channelRole: serializer.fromJson(json['channelRole']), - inviteAcceptedAt: - serializer.fromJson(json['inviteAcceptedAt']), - inviteRejectedAt: - serializer.fromJson(json['inviteRejectedAt']), + inviteAcceptedAt: serializer.fromJson(json['inviteAcceptedAt']), + inviteRejectedAt: serializer.fromJson(json['inviteRejectedAt']), invited: serializer.fromJson(json['invited']), banned: serializer.fromJson(json['banned']), shadowBanned: serializer.fromJson(json['shadowBanned']), @@ -7977,8 +7909,7 @@ class MemberEntity extends DataClass implements Insertable { extraData: serializer.fromJson?>(json['extraData']), createdAt: serializer.fromJson(json['createdAt']), updatedAt: serializer.fromJson(json['updatedAt']), - deletedMessages: - serializer.fromJson>(json['deletedMessages']), + deletedMessages: serializer.fromJson>(json['deletedMessages']), ); } @override @@ -8003,72 +7934,56 @@ class MemberEntity extends DataClass implements Insertable { }; } - MemberEntity copyWith( - {String? userId, - String? channelCid, - Value channelRole = const Value.absent(), - Value inviteAcceptedAt = const Value.absent(), - Value inviteRejectedAt = const Value.absent(), - bool? invited, - bool? banned, - bool? shadowBanned, - Value pinnedAt = const Value.absent(), - Value archivedAt = const Value.absent(), - bool? isModerator, - Value?> extraData = const Value.absent(), - DateTime? createdAt, - DateTime? updatedAt, - List? deletedMessages}) => - MemberEntity( - userId: userId ?? this.userId, - channelCid: channelCid ?? this.channelCid, - channelRole: channelRole.present ? channelRole.value : this.channelRole, - inviteAcceptedAt: inviteAcceptedAt.present - ? inviteAcceptedAt.value - : this.inviteAcceptedAt, - inviteRejectedAt: inviteRejectedAt.present - ? inviteRejectedAt.value - : this.inviteRejectedAt, - invited: invited ?? this.invited, - banned: banned ?? this.banned, - shadowBanned: shadowBanned ?? this.shadowBanned, - pinnedAt: pinnedAt.present ? pinnedAt.value : this.pinnedAt, - archivedAt: archivedAt.present ? archivedAt.value : this.archivedAt, - isModerator: isModerator ?? this.isModerator, - extraData: extraData.present ? extraData.value : this.extraData, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - deletedMessages: deletedMessages ?? this.deletedMessages, - ); + MemberEntity copyWith({ + String? userId, + String? channelCid, + Value channelRole = const Value.absent(), + Value inviteAcceptedAt = const Value.absent(), + Value inviteRejectedAt = const Value.absent(), + bool? invited, + bool? banned, + bool? shadowBanned, + Value pinnedAt = const Value.absent(), + Value archivedAt = const Value.absent(), + bool? isModerator, + Value?> extraData = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + List? deletedMessages, + }) => MemberEntity( + userId: userId ?? this.userId, + channelCid: channelCid ?? this.channelCid, + channelRole: channelRole.present ? channelRole.value : this.channelRole, + inviteAcceptedAt: inviteAcceptedAt.present ? inviteAcceptedAt.value : this.inviteAcceptedAt, + inviteRejectedAt: inviteRejectedAt.present ? inviteRejectedAt.value : this.inviteRejectedAt, + invited: invited ?? this.invited, + banned: banned ?? this.banned, + shadowBanned: shadowBanned ?? this.shadowBanned, + pinnedAt: pinnedAt.present ? pinnedAt.value : this.pinnedAt, + archivedAt: archivedAt.present ? archivedAt.value : this.archivedAt, + isModerator: isModerator ?? this.isModerator, + extraData: extraData.present ? extraData.value : this.extraData, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedMessages: deletedMessages ?? this.deletedMessages, + ); MemberEntity copyWithCompanion(MembersCompanion data) { return MemberEntity( userId: data.userId.present ? data.userId.value : this.userId, - channelCid: - data.channelCid.present ? data.channelCid.value : this.channelCid, - channelRole: - data.channelRole.present ? data.channelRole.value : this.channelRole, - inviteAcceptedAt: data.inviteAcceptedAt.present - ? data.inviteAcceptedAt.value - : this.inviteAcceptedAt, - inviteRejectedAt: data.inviteRejectedAt.present - ? data.inviteRejectedAt.value - : this.inviteRejectedAt, + channelCid: data.channelCid.present ? data.channelCid.value : this.channelCid, + channelRole: data.channelRole.present ? data.channelRole.value : this.channelRole, + inviteAcceptedAt: data.inviteAcceptedAt.present ? data.inviteAcceptedAt.value : this.inviteAcceptedAt, + inviteRejectedAt: data.inviteRejectedAt.present ? data.inviteRejectedAt.value : this.inviteRejectedAt, invited: data.invited.present ? data.invited.value : this.invited, banned: data.banned.present ? data.banned.value : this.banned, - shadowBanned: data.shadowBanned.present - ? data.shadowBanned.value - : this.shadowBanned, + shadowBanned: data.shadowBanned.present ? data.shadowBanned.value : this.shadowBanned, pinnedAt: data.pinnedAt.present ? data.pinnedAt.value : this.pinnedAt, - archivedAt: - data.archivedAt.present ? data.archivedAt.value : this.archivedAt, - isModerator: - data.isModerator.present ? data.isModerator.value : this.isModerator, + archivedAt: data.archivedAt.present ? data.archivedAt.value : this.archivedAt, + isModerator: data.isModerator.present ? data.isModerator.value : this.isModerator, extraData: data.extraData.present ? data.extraData.value : this.extraData, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, - deletedMessages: data.deletedMessages.present - ? data.deletedMessages.value - : this.deletedMessages, + deletedMessages: data.deletedMessages.present ? data.deletedMessages.value : this.deletedMessages, ); } @@ -8096,21 +8011,22 @@ class MemberEntity extends DataClass implements Insertable { @override int get hashCode => Object.hash( - userId, - channelCid, - channelRole, - inviteAcceptedAt, - inviteRejectedAt, - invited, - banned, - shadowBanned, - pinnedAt, - archivedAt, - isModerator, - extraData, - createdAt, - updatedAt, - deletedMessages); + userId, + channelCid, + channelRole, + inviteAcceptedAt, + inviteRejectedAt, + invited, + banned, + shadowBanned, + pinnedAt, + archivedAt, + isModerator, + extraData, + createdAt, + updatedAt, + deletedMessages, + ); @override bool operator ==(Object other) => identical(this, other) || @@ -8184,9 +8100,9 @@ class MembersCompanion extends UpdateCompanion { this.updatedAt = const Value.absent(), required List deletedMessages, this.rowid = const Value.absent(), - }) : userId = Value(userId), - channelCid = Value(channelCid), - deletedMessages = Value(deletedMessages); + }) : userId = Value(userId), + channelCid = Value(channelCid), + deletedMessages = Value(deletedMessages); static Insertable custom({ Expression? userId, Expression? channelCid, @@ -8225,23 +8141,24 @@ class MembersCompanion extends UpdateCompanion { }); } - MembersCompanion copyWith( - {Value? userId, - Value? channelCid, - Value? channelRole, - Value? inviteAcceptedAt, - Value? inviteRejectedAt, - Value? invited, - Value? banned, - Value? shadowBanned, - Value? pinnedAt, - Value? archivedAt, - Value? isModerator, - Value?>? extraData, - Value? createdAt, - Value? updatedAt, - Value>? deletedMessages, - Value? rowid}) { + MembersCompanion copyWith({ + Value? userId, + Value? channelCid, + Value? channelRole, + Value? inviteAcceptedAt, + Value? inviteRejectedAt, + Value? invited, + Value? banned, + Value? shadowBanned, + Value? pinnedAt, + Value? archivedAt, + Value? isModerator, + Value?>? extraData, + Value? createdAt, + Value? updatedAt, + Value>? deletedMessages, + Value? rowid, + }) { return MembersCompanion( userId: userId ?? this.userId, channelCid: channelCid ?? this.channelCid, @@ -8299,8 +8216,7 @@ class MembersCompanion extends UpdateCompanion { map['is_moderator'] = Variable(isModerator.value); } if (extraData.present) { - map['extra_data'] = Variable( - $MembersTable.$converterextraDatan.toSql(extraData.value)); + map['extra_data'] = Variable($MembersTable.$converterextraDatan.toSql(extraData.value)); } if (createdAt.present) { map['created_at'] = Variable(createdAt.value); @@ -8309,8 +8225,7 @@ class MembersCompanion extends UpdateCompanion { map['updated_at'] = Variable(updatedAt.value); } if (deletedMessages.present) { - map['deleted_messages'] = Variable( - $MembersTable.$converterdeletedMessages.toSql(deletedMessages.value)); + map['deleted_messages'] = Variable($MembersTable.$converterdeletedMessages.toSql(deletedMessages.value)); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -8347,115 +8262,128 @@ class $ReadsTable extends Reads with TableInfo<$ReadsTable, ReadEntity> { final GeneratedDatabase attachedDatabase; final String? _alias; $ReadsTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _lastReadMeta = - const VerificationMeta('lastRead'); + static const VerificationMeta _lastReadMeta = const VerificationMeta('lastRead'); @override late final GeneratedColumn lastRead = GeneratedColumn( - 'last_read', aliasedName, false, - type: DriftSqlType.dateTime, requiredDuringInsert: true); + 'last_read', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); @override late final GeneratedColumn userId = GeneratedColumn( - 'user_id', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _channelCidMeta = - const VerificationMeta('channelCid'); + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); @override late final GeneratedColumn channelCid = GeneratedColumn( - 'channel_cid', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES channels (cid) ON DELETE CASCADE')); - static const VerificationMeta _unreadMessagesMeta = - const VerificationMeta('unreadMessages'); + 'channel_cid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES channels (cid) ON DELETE CASCADE'), + ); + static const VerificationMeta _unreadMessagesMeta = const VerificationMeta('unreadMessages'); @override late final GeneratedColumn unreadMessages = GeneratedColumn( - 'unread_messages', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(0)); - static const VerificationMeta _lastReadMessageIdMeta = - const VerificationMeta('lastReadMessageId'); - @override - late final GeneratedColumn lastReadMessageId = - GeneratedColumn('last_read_message_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _lastDeliveredAtMeta = - const VerificationMeta('lastDeliveredAt'); - @override - late final GeneratedColumn lastDeliveredAt = - GeneratedColumn('last_delivered_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _lastDeliveredMessageIdMeta = - const VerificationMeta('lastDeliveredMessageId'); - @override - late final GeneratedColumn lastDeliveredMessageId = - GeneratedColumn('last_delivered_message_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'unread_messages', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0), + ); + static const VerificationMeta _lastReadMessageIdMeta = const VerificationMeta('lastReadMessageId'); + @override + late final GeneratedColumn lastReadMessageId = GeneratedColumn( + 'last_read_message_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _lastDeliveredAtMeta = const VerificationMeta('lastDeliveredAt'); + @override + late final GeneratedColumn lastDeliveredAt = GeneratedColumn( + 'last_delivered_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _lastDeliveredMessageIdMeta = const VerificationMeta('lastDeliveredMessageId'); + @override + late final GeneratedColumn lastDeliveredMessageId = GeneratedColumn( + 'last_delivered_message_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); @override List get $columns => [ - lastRead, - userId, - channelCid, - unreadMessages, - lastReadMessageId, - lastDeliveredAt, - lastDeliveredMessageId - ]; + lastRead, + userId, + channelCid, + unreadMessages, + lastReadMessageId, + lastDeliveredAt, + lastDeliveredMessageId, + ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'reads'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('last_read')) { - context.handle(_lastReadMeta, - lastRead.isAcceptableOrUnknown(data['last_read']!, _lastReadMeta)); + context.handle(_lastReadMeta, lastRead.isAcceptableOrUnknown(data['last_read']!, _lastReadMeta)); } else if (isInserting) { context.missing(_lastReadMeta); } if (data.containsKey('user_id')) { - context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + context.handle(_userIdMeta, userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } else if (isInserting) { context.missing(_userIdMeta); } if (data.containsKey('channel_cid')) { - context.handle( - _channelCidMeta, - channelCid.isAcceptableOrUnknown( - data['channel_cid']!, _channelCidMeta)); + context.handle(_channelCidMeta, channelCid.isAcceptableOrUnknown(data['channel_cid']!, _channelCidMeta)); } else if (isInserting) { context.missing(_channelCidMeta); } if (data.containsKey('unread_messages')) { context.handle( - _unreadMessagesMeta, - unreadMessages.isAcceptableOrUnknown( - data['unread_messages']!, _unreadMessagesMeta)); + _unreadMessagesMeta, + unreadMessages.isAcceptableOrUnknown(data['unread_messages']!, _unreadMessagesMeta), + ); } if (data.containsKey('last_read_message_id')) { context.handle( - _lastReadMessageIdMeta, - lastReadMessageId.isAcceptableOrUnknown( - data['last_read_message_id']!, _lastReadMessageIdMeta)); + _lastReadMessageIdMeta, + lastReadMessageId.isAcceptableOrUnknown(data['last_read_message_id']!, _lastReadMessageIdMeta), + ); } if (data.containsKey('last_delivered_at')) { context.handle( - _lastDeliveredAtMeta, - lastDeliveredAt.isAcceptableOrUnknown( - data['last_delivered_at']!, _lastDeliveredAtMeta)); + _lastDeliveredAtMeta, + lastDeliveredAt.isAcceptableOrUnknown(data['last_delivered_at']!, _lastDeliveredAtMeta), + ); } if (data.containsKey('last_delivered_message_id')) { context.handle( - _lastDeliveredMessageIdMeta, - lastDeliveredMessageId.isAcceptableOrUnknown( - data['last_delivered_message_id']!, _lastDeliveredMessageIdMeta)); + _lastDeliveredMessageIdMeta, + lastDeliveredMessageId.isAcceptableOrUnknown(data['last_delivered_message_id']!, _lastDeliveredMessageIdMeta), + ); } return context; } @@ -8466,21 +8394,22 @@ class $ReadsTable extends Reads with TableInfo<$ReadsTable, ReadEntity> { ReadEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return ReadEntity( - lastRead: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}last_read'])!, - userId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}user_id'])!, - channelCid: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, - unreadMessages: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}unread_messages'])!, + lastRead: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}last_read'])!, + userId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}user_id'])!, + channelCid: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, + unreadMessages: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}unread_messages'])!, lastReadMessageId: attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}last_read_message_id']), + DriftSqlType.string, + data['${effectivePrefix}last_read_message_id'], + ), lastDeliveredAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}last_delivered_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}last_delivered_at'], + ), lastDeliveredMessageId: attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}last_delivered_message_id']), + DriftSqlType.string, + data['${effectivePrefix}last_delivered_message_id'], + ), ); } @@ -8511,14 +8440,15 @@ class ReadEntity extends DataClass implements Insertable { /// Id of the last delivered message final String? lastDeliveredMessageId; - const ReadEntity( - {required this.lastRead, - required this.userId, - required this.channelCid, - required this.unreadMessages, - this.lastReadMessageId, - this.lastDeliveredAt, - this.lastDeliveredMessageId}); + const ReadEntity({ + required this.lastRead, + required this.userId, + required this.channelCid, + required this.unreadMessages, + this.lastReadMessageId, + this.lastDeliveredAt, + this.lastDeliveredMessageId, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -8533,25 +8463,21 @@ class ReadEntity extends DataClass implements Insertable { map['last_delivered_at'] = Variable(lastDeliveredAt); } if (!nullToAbsent || lastDeliveredMessageId != null) { - map['last_delivered_message_id'] = - Variable(lastDeliveredMessageId); + map['last_delivered_message_id'] = Variable(lastDeliveredMessageId); } return map; } - factory ReadEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory ReadEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return ReadEntity( lastRead: serializer.fromJson(json['lastRead']), userId: serializer.fromJson(json['userId']), channelCid: serializer.fromJson(json['channelCid']), unreadMessages: serializer.fromJson(json['unreadMessages']), - lastReadMessageId: - serializer.fromJson(json['lastReadMessageId']), + lastReadMessageId: serializer.fromJson(json['lastReadMessageId']), lastDeliveredAt: serializer.fromJson(json['lastDeliveredAt']), - lastDeliveredMessageId: - serializer.fromJson(json['lastDeliveredMessageId']), + lastDeliveredMessageId: serializer.fromJson(json['lastDeliveredMessageId']), ); } @override @@ -8564,49 +8490,35 @@ class ReadEntity extends DataClass implements Insertable { 'unreadMessages': serializer.toJson(unreadMessages), 'lastReadMessageId': serializer.toJson(lastReadMessageId), 'lastDeliveredAt': serializer.toJson(lastDeliveredAt), - 'lastDeliveredMessageId': - serializer.toJson(lastDeliveredMessageId), + 'lastDeliveredMessageId': serializer.toJson(lastDeliveredMessageId), }; } - ReadEntity copyWith( - {DateTime? lastRead, - String? userId, - String? channelCid, - int? unreadMessages, - Value lastReadMessageId = const Value.absent(), - Value lastDeliveredAt = const Value.absent(), - Value lastDeliveredMessageId = const Value.absent()}) => - ReadEntity( - lastRead: lastRead ?? this.lastRead, - userId: userId ?? this.userId, - channelCid: channelCid ?? this.channelCid, - unreadMessages: unreadMessages ?? this.unreadMessages, - lastReadMessageId: lastReadMessageId.present - ? lastReadMessageId.value - : this.lastReadMessageId, - lastDeliveredAt: lastDeliveredAt.present - ? lastDeliveredAt.value - : this.lastDeliveredAt, - lastDeliveredMessageId: lastDeliveredMessageId.present - ? lastDeliveredMessageId.value - : this.lastDeliveredMessageId, - ); + ReadEntity copyWith({ + DateTime? lastRead, + String? userId, + String? channelCid, + int? unreadMessages, + Value lastReadMessageId = const Value.absent(), + Value lastDeliveredAt = const Value.absent(), + Value lastDeliveredMessageId = const Value.absent(), + }) => ReadEntity( + lastRead: lastRead ?? this.lastRead, + userId: userId ?? this.userId, + channelCid: channelCid ?? this.channelCid, + unreadMessages: unreadMessages ?? this.unreadMessages, + lastReadMessageId: lastReadMessageId.present ? lastReadMessageId.value : this.lastReadMessageId, + lastDeliveredAt: lastDeliveredAt.present ? lastDeliveredAt.value : this.lastDeliveredAt, + lastDeliveredMessageId: lastDeliveredMessageId.present ? lastDeliveredMessageId.value : this.lastDeliveredMessageId, + ); ReadEntity copyWithCompanion(ReadsCompanion data) { return ReadEntity( lastRead: data.lastRead.present ? data.lastRead.value : this.lastRead, userId: data.userId.present ? data.userId.value : this.userId, - channelCid: - data.channelCid.present ? data.channelCid.value : this.channelCid, - unreadMessages: data.unreadMessages.present - ? data.unreadMessages.value - : this.unreadMessages, - lastReadMessageId: data.lastReadMessageId.present - ? data.lastReadMessageId.value - : this.lastReadMessageId, - lastDeliveredAt: data.lastDeliveredAt.present - ? data.lastDeliveredAt.value - : this.lastDeliveredAt, + channelCid: data.channelCid.present ? data.channelCid.value : this.channelCid, + unreadMessages: data.unreadMessages.present ? data.unreadMessages.value : this.unreadMessages, + lastReadMessageId: data.lastReadMessageId.present ? data.lastReadMessageId.value : this.lastReadMessageId, + lastDeliveredAt: data.lastDeliveredAt.present ? data.lastDeliveredAt.value : this.lastDeliveredAt, lastDeliveredMessageId: data.lastDeliveredMessageId.present ? data.lastDeliveredMessageId.value : this.lastDeliveredMessageId, @@ -8628,8 +8540,15 @@ class ReadEntity extends DataClass implements Insertable { } @override - int get hashCode => Object.hash(lastRead, userId, channelCid, unreadMessages, - lastReadMessageId, lastDeliveredAt, lastDeliveredMessageId); + int get hashCode => Object.hash( + lastRead, + userId, + channelCid, + unreadMessages, + lastReadMessageId, + lastDeliveredAt, + lastDeliveredMessageId, + ); @override bool operator ==(Object other) => identical(this, other) || @@ -8671,9 +8590,9 @@ class ReadsCompanion extends UpdateCompanion { this.lastDeliveredAt = const Value.absent(), this.lastDeliveredMessageId = const Value.absent(), this.rowid = const Value.absent(), - }) : lastRead = Value(lastRead), - userId = Value(userId), - channelCid = Value(channelCid); + }) : lastRead = Value(lastRead), + userId = Value(userId), + channelCid = Value(channelCid); static Insertable custom({ Expression? lastRead, Expression? userId, @@ -8691,21 +8610,21 @@ class ReadsCompanion extends UpdateCompanion { if (unreadMessages != null) 'unread_messages': unreadMessages, if (lastReadMessageId != null) 'last_read_message_id': lastReadMessageId, if (lastDeliveredAt != null) 'last_delivered_at': lastDeliveredAt, - if (lastDeliveredMessageId != null) - 'last_delivered_message_id': lastDeliveredMessageId, + if (lastDeliveredMessageId != null) 'last_delivered_message_id': lastDeliveredMessageId, if (rowid != null) 'rowid': rowid, }); } - ReadsCompanion copyWith( - {Value? lastRead, - Value? userId, - Value? channelCid, - Value? unreadMessages, - Value? lastReadMessageId, - Value? lastDeliveredAt, - Value? lastDeliveredMessageId, - Value? rowid}) { + ReadsCompanion copyWith({ + Value? lastRead, + Value? userId, + Value? channelCid, + Value? unreadMessages, + Value? lastReadMessageId, + Value? lastDeliveredAt, + Value? lastDeliveredMessageId, + Value? rowid, + }) { return ReadsCompanion( lastRead: lastRead ?? this.lastRead, userId: userId ?? this.userId, @@ -8713,8 +8632,7 @@ class ReadsCompanion extends UpdateCompanion { unreadMessages: unreadMessages ?? this.unreadMessages, lastReadMessageId: lastReadMessageId ?? this.lastReadMessageId, lastDeliveredAt: lastDeliveredAt ?? this.lastDeliveredAt, - lastDeliveredMessageId: - lastDeliveredMessageId ?? this.lastDeliveredMessageId, + lastDeliveredMessageId: lastDeliveredMessageId ?? this.lastDeliveredMessageId, rowid: rowid ?? this.rowid, ); } @@ -8741,8 +8659,7 @@ class ReadsCompanion extends UpdateCompanion { map['last_delivered_at'] = Variable(lastDeliveredAt.value); } if (lastDeliveredMessageId.present) { - map['last_delivered_message_id'] = - Variable(lastDeliveredMessageId.value); + map['last_delivered_message_id'] = Variable(lastDeliveredMessageId.value); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -8766,24 +8683,29 @@ class ReadsCompanion extends UpdateCompanion { } } -class $ChannelQueriesTable extends ChannelQueries - with TableInfo<$ChannelQueriesTable, ChannelQueryEntity> { +class $ChannelQueriesTable extends ChannelQueries with TableInfo<$ChannelQueriesTable, ChannelQueryEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; $ChannelQueriesTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _queryHashMeta = - const VerificationMeta('queryHash'); + static const VerificationMeta _queryHashMeta = const VerificationMeta('queryHash'); @override late final GeneratedColumn queryHash = GeneratedColumn( - 'query_hash', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _channelCidMeta = - const VerificationMeta('channelCid'); + 'query_hash', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); @override late final GeneratedColumn channelCid = GeneratedColumn( - 'channel_cid', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + 'channel_cid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); @override List get $columns => [queryHash, channelCid]; @override @@ -8792,21 +8714,16 @@ class $ChannelQueriesTable extends ChannelQueries String get actualTableName => $name; static const String $name = 'channel_queries'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('query_hash')) { - context.handle(_queryHashMeta, - queryHash.isAcceptableOrUnknown(data['query_hash']!, _queryHashMeta)); + context.handle(_queryHashMeta, queryHash.isAcceptableOrUnknown(data['query_hash']!, _queryHashMeta)); } else if (isInserting) { context.missing(_queryHashMeta); } if (data.containsKey('channel_cid')) { - context.handle( - _channelCidMeta, - channelCid.isAcceptableOrUnknown( - data['channel_cid']!, _channelCidMeta)); + context.handle(_channelCidMeta, channelCid.isAcceptableOrUnknown(data['channel_cid']!, _channelCidMeta)); } else if (isInserting) { context.missing(_channelCidMeta); } @@ -8819,10 +8736,8 @@ class $ChannelQueriesTable extends ChannelQueries ChannelQueryEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return ChannelQueryEntity( - queryHash: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}query_hash'])!, - channelCid: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, + queryHash: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}query_hash'])!, + channelCid: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, ); } @@ -8832,8 +8747,7 @@ class $ChannelQueriesTable extends ChannelQueries } } -class ChannelQueryEntity extends DataClass - implements Insertable { +class ChannelQueryEntity extends DataClass implements Insertable { /// The unique hash of this query final String queryHash; @@ -8848,8 +8762,7 @@ class ChannelQueryEntity extends DataClass return map; } - factory ChannelQueryEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory ChannelQueryEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return ChannelQueryEntity( queryHash: serializer.fromJson(json['queryHash']), @@ -8865,16 +8778,14 @@ class ChannelQueryEntity extends DataClass }; } - ChannelQueryEntity copyWith({String? queryHash, String? channelCid}) => - ChannelQueryEntity( - queryHash: queryHash ?? this.queryHash, - channelCid: channelCid ?? this.channelCid, - ); + ChannelQueryEntity copyWith({String? queryHash, String? channelCid}) => ChannelQueryEntity( + queryHash: queryHash ?? this.queryHash, + channelCid: channelCid ?? this.channelCid, + ); ChannelQueryEntity copyWithCompanion(ChannelQueriesCompanion data) { return ChannelQueryEntity( queryHash: data.queryHash.present ? data.queryHash.value : this.queryHash, - channelCid: - data.channelCid.present ? data.channelCid.value : this.channelCid, + channelCid: data.channelCid.present ? data.channelCid.value : this.channelCid, ); } @@ -8892,9 +8803,7 @@ class ChannelQueryEntity extends DataClass @override bool operator ==(Object other) => identical(this, other) || - (other is ChannelQueryEntity && - other.queryHash == this.queryHash && - other.channelCid == this.channelCid); + (other is ChannelQueryEntity && other.queryHash == this.queryHash && other.channelCid == this.channelCid); } class ChannelQueriesCompanion extends UpdateCompanion { @@ -8910,8 +8819,8 @@ class ChannelQueriesCompanion extends UpdateCompanion { required String queryHash, required String channelCid, this.rowid = const Value.absent(), - }) : queryHash = Value(queryHash), - channelCid = Value(channelCid); + }) : queryHash = Value(queryHash), + channelCid = Value(channelCid); static Insertable custom({ Expression? queryHash, Expression? channelCid, @@ -8924,10 +8833,7 @@ class ChannelQueriesCompanion extends UpdateCompanion { }); } - ChannelQueriesCompanion copyWith( - {Value? queryHash, - Value? channelCid, - Value? rowid}) { + ChannelQueriesCompanion copyWith({Value? queryHash, Value? channelCid, Value? rowid}) { return ChannelQueriesCompanion( queryHash: queryHash ?? this.queryHash, channelCid: channelCid ?? this.channelCid, @@ -8961,8 +8867,7 @@ class ChannelQueriesCompanion extends UpdateCompanion { } } -class $ConnectionEventsTable extends ConnectionEvents - with TableInfo<$ConnectionEventsTable, ConnectionEventEntity> { +class $ConnectionEventsTable extends ConnectionEvents with TableInfo<$ConnectionEventsTable, ConnectionEventEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; @@ -8970,96 +8875,101 @@ class $ConnectionEventsTable extends ConnectionEvents static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - type: DriftSqlType.int, requiredDuringInsert: false); + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); static const VerificationMeta _typeMeta = const VerificationMeta('type'); @override late final GeneratedColumn type = GeneratedColumn( - 'type', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - @override - late final GeneratedColumnWithTypeConverter?, String> - ownUser = GeneratedColumn('own_user', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $ConnectionEventsTable.$converterownUsern); - static const VerificationMeta _totalUnreadCountMeta = - const VerificationMeta('totalUnreadCount'); + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + late final GeneratedColumnWithTypeConverter?, String> ownUser = GeneratedColumn( + 'own_user', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($ConnectionEventsTable.$converterownUsern); + static const VerificationMeta _totalUnreadCountMeta = const VerificationMeta('totalUnreadCount'); @override late final GeneratedColumn totalUnreadCount = GeneratedColumn( - 'total_unread_count', aliasedName, true, - type: DriftSqlType.int, requiredDuringInsert: false); - static const VerificationMeta _unreadChannelsMeta = - const VerificationMeta('unreadChannels'); + 'total_unread_count', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + static const VerificationMeta _unreadChannelsMeta = const VerificationMeta('unreadChannels'); @override late final GeneratedColumn unreadChannels = GeneratedColumn( - 'unread_channels', aliasedName, true, - type: DriftSqlType.int, requiredDuringInsert: false); - static const VerificationMeta _lastEventAtMeta = - const VerificationMeta('lastEventAt'); + 'unread_channels', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + static const VerificationMeta _lastEventAtMeta = const VerificationMeta('lastEventAt'); @override late final GeneratedColumn lastEventAt = GeneratedColumn( - 'last_event_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _lastSyncAtMeta = - const VerificationMeta('lastSyncAt'); + 'last_event_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _lastSyncAtMeta = const VerificationMeta('lastSyncAt'); @override late final GeneratedColumn lastSyncAt = GeneratedColumn( - 'last_sync_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); + 'last_sync_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); @override - List get $columns => [ - id, - type, - ownUser, - totalUnreadCount, - unreadChannels, - lastEventAt, - lastSyncAt - ]; + List get $columns => [id, type, ownUser, totalUnreadCount, unreadChannels, lastEventAt, lastSyncAt]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'connection_events'; @override - VerificationContext validateIntegrity( - Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } if (data.containsKey('type')) { - context.handle( - _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + context.handle(_typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); } else if (isInserting) { context.missing(_typeMeta); } if (data.containsKey('total_unread_count')) { context.handle( - _totalUnreadCountMeta, - totalUnreadCount.isAcceptableOrUnknown( - data['total_unread_count']!, _totalUnreadCountMeta)); + _totalUnreadCountMeta, + totalUnreadCount.isAcceptableOrUnknown(data['total_unread_count']!, _totalUnreadCountMeta), + ); } if (data.containsKey('unread_channels')) { context.handle( - _unreadChannelsMeta, - unreadChannels.isAcceptableOrUnknown( - data['unread_channels']!, _unreadChannelsMeta)); + _unreadChannelsMeta, + unreadChannels.isAcceptableOrUnknown(data['unread_channels']!, _unreadChannelsMeta), + ); } if (data.containsKey('last_event_at')) { - context.handle( - _lastEventAtMeta, - lastEventAt.isAcceptableOrUnknown( - data['last_event_at']!, _lastEventAtMeta)); + context.handle(_lastEventAtMeta, lastEventAt.isAcceptableOrUnknown(data['last_event_at']!, _lastEventAtMeta)); } if (data.containsKey('last_sync_at')) { - context.handle( - _lastSyncAtMeta, - lastSyncAt.isAcceptableOrUnknown( - data['last_sync_at']!, _lastSyncAtMeta)); + context.handle(_lastSyncAtMeta, lastSyncAt.isAcceptableOrUnknown(data['last_sync_at']!, _lastSyncAtMeta)); } return context; } @@ -9070,21 +8980,18 @@ class $ConnectionEventsTable extends ConnectionEvents ConnectionEventEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return ConnectionEventEntity( - id: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}id'])!, - type: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}type'])!, + id: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}id'])!, + type: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}type'])!, ownUser: $ConnectionEventsTable.$converterownUsern.fromSql( - attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}own_user'])), - totalUnreadCount: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}total_unread_count']), - unreadChannels: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}unread_channels']), - lastEventAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}last_event_at']), - lastSyncAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}last_sync_at']), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}own_user']), + ), + totalUnreadCount: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}total_unread_count'], + ), + unreadChannels: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}unread_channels']), + lastEventAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}last_event_at']), + lastSyncAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}last_sync_at']), ); } @@ -9093,14 +9000,13 @@ class $ConnectionEventsTable extends ConnectionEvents return $ConnectionEventsTable(attachedDatabase, alias); } - static TypeConverter, String> $converterownUser = - MapConverter(); - static TypeConverter?, String?> $converterownUsern = - NullAwareTypeConverter.wrap($converterownUser); + static TypeConverter, String> $converterownUser = MapConverter(); + static TypeConverter?, String?> $converterownUsern = NullAwareTypeConverter.wrap( + $converterownUser, + ); } -class ConnectionEventEntity extends DataClass - implements Insertable { +class ConnectionEventEntity extends DataClass implements Insertable { /// event id final int id; @@ -9121,22 +9027,22 @@ class ConnectionEventEntity extends DataClass /// DateTime of the last sync final DateTime? lastSyncAt; - const ConnectionEventEntity( - {required this.id, - required this.type, - this.ownUser, - this.totalUnreadCount, - this.unreadChannels, - this.lastEventAt, - this.lastSyncAt}); + const ConnectionEventEntity({ + required this.id, + required this.type, + this.ownUser, + this.totalUnreadCount, + this.unreadChannels, + this.lastEventAt, + this.lastSyncAt, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; map['id'] = Variable(id); map['type'] = Variable(type); if (!nullToAbsent || ownUser != null) { - map['own_user'] = Variable( - $ConnectionEventsTable.$converterownUsern.toSql(ownUser)); + map['own_user'] = Variable($ConnectionEventsTable.$converterownUsern.toSql(ownUser)); } if (!nullToAbsent || totalUnreadCount != null) { map['total_unread_count'] = Variable(totalUnreadCount); @@ -9153,8 +9059,7 @@ class ConnectionEventEntity extends DataClass return map; } - factory ConnectionEventEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory ConnectionEventEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return ConnectionEventEntity( id: serializer.fromJson(json['id']), @@ -9180,41 +9085,32 @@ class ConnectionEventEntity extends DataClass }; } - ConnectionEventEntity copyWith( - {int? id, - String? type, - Value?> ownUser = const Value.absent(), - Value totalUnreadCount = const Value.absent(), - Value unreadChannels = const Value.absent(), - Value lastEventAt = const Value.absent(), - Value lastSyncAt = const Value.absent()}) => - ConnectionEventEntity( - id: id ?? this.id, - type: type ?? this.type, - ownUser: ownUser.present ? ownUser.value : this.ownUser, - totalUnreadCount: totalUnreadCount.present - ? totalUnreadCount.value - : this.totalUnreadCount, - unreadChannels: - unreadChannels.present ? unreadChannels.value : this.unreadChannels, - lastEventAt: lastEventAt.present ? lastEventAt.value : this.lastEventAt, - lastSyncAt: lastSyncAt.present ? lastSyncAt.value : this.lastSyncAt, - ); + ConnectionEventEntity copyWith({ + int? id, + String? type, + Value?> ownUser = const Value.absent(), + Value totalUnreadCount = const Value.absent(), + Value unreadChannels = const Value.absent(), + Value lastEventAt = const Value.absent(), + Value lastSyncAt = const Value.absent(), + }) => ConnectionEventEntity( + id: id ?? this.id, + type: type ?? this.type, + ownUser: ownUser.present ? ownUser.value : this.ownUser, + totalUnreadCount: totalUnreadCount.present ? totalUnreadCount.value : this.totalUnreadCount, + unreadChannels: unreadChannels.present ? unreadChannels.value : this.unreadChannels, + lastEventAt: lastEventAt.present ? lastEventAt.value : this.lastEventAt, + lastSyncAt: lastSyncAt.present ? lastSyncAt.value : this.lastSyncAt, + ); ConnectionEventEntity copyWithCompanion(ConnectionEventsCompanion data) { return ConnectionEventEntity( id: data.id.present ? data.id.value : this.id, type: data.type.present ? data.type.value : this.type, ownUser: data.ownUser.present ? data.ownUser.value : this.ownUser, - totalUnreadCount: data.totalUnreadCount.present - ? data.totalUnreadCount.value - : this.totalUnreadCount, - unreadChannels: data.unreadChannels.present - ? data.unreadChannels.value - : this.unreadChannels, - lastEventAt: - data.lastEventAt.present ? data.lastEventAt.value : this.lastEventAt, - lastSyncAt: - data.lastSyncAt.present ? data.lastSyncAt.value : this.lastSyncAt, + totalUnreadCount: data.totalUnreadCount.present ? data.totalUnreadCount.value : this.totalUnreadCount, + unreadChannels: data.unreadChannels.present ? data.unreadChannels.value : this.unreadChannels, + lastEventAt: data.lastEventAt.present ? data.lastEventAt.value : this.lastEventAt, + lastSyncAt: data.lastSyncAt.present ? data.lastSyncAt.value : this.lastSyncAt, ); } @@ -9233,8 +9129,7 @@ class ConnectionEventEntity extends DataClass } @override - int get hashCode => Object.hash(id, type, ownUser, totalUnreadCount, - unreadChannels, lastEventAt, lastSyncAt); + int get hashCode => Object.hash(id, type, ownUser, totalUnreadCount, unreadChannels, lastEventAt, lastSyncAt); @override bool operator ==(Object other) => identical(this, other) || @@ -9294,14 +9189,15 @@ class ConnectionEventsCompanion extends UpdateCompanion { }); } - ConnectionEventsCompanion copyWith( - {Value? id, - Value? type, - Value?>? ownUser, - Value? totalUnreadCount, - Value? unreadChannels, - Value? lastEventAt, - Value? lastSyncAt}) { + ConnectionEventsCompanion copyWith({ + Value? id, + Value? type, + Value?>? ownUser, + Value? totalUnreadCount, + Value? unreadChannels, + Value? lastEventAt, + Value? lastSyncAt, + }) { return ConnectionEventsCompanion( id: id ?? this.id, type: type ?? this.type, @@ -9323,8 +9219,7 @@ class ConnectionEventsCompanion extends UpdateCompanion { map['type'] = Variable(type.value); } if (ownUser.present) { - map['own_user'] = Variable( - $ConnectionEventsTable.$converterownUsern.toSql(ownUser.value)); + map['own_user'] = Variable($ConnectionEventsTable.$converterownUsern.toSql(ownUser.value)); } if (totalUnreadCount.present) { map['total_unread_count'] = Variable(totalUnreadCount.value); @@ -9366,251 +9261,235 @@ abstract class _$DriftChatDatabase extends GeneratedDatabase { late final $PinnedMessagesTable pinnedMessages = $PinnedMessagesTable(this); late final $PollsTable polls = $PollsTable(this); late final $PollVotesTable pollVotes = $PollVotesTable(this); - late final $PinnedMessageReactionsTable pinnedMessageReactions = - $PinnedMessageReactionsTable(this); + late final $PinnedMessageReactionsTable pinnedMessageReactions = $PinnedMessageReactionsTable(this); late final $ReactionsTable reactions = $ReactionsTable(this); late final $UsersTable users = $UsersTable(this); late final $MembersTable members = $MembersTable(this); late final $ReadsTable reads = $ReadsTable(this); late final $ChannelQueriesTable channelQueries = $ChannelQueriesTable(this); - late final $ConnectionEventsTable connectionEvents = - $ConnectionEventsTable(this); + late final $ConnectionEventsTable connectionEvents = $ConnectionEventsTable(this); late final UserDao userDao = UserDao(this as DriftChatDatabase); late final ChannelDao channelDao = ChannelDao(this as DriftChatDatabase); late final MessageDao messageDao = MessageDao(this as DriftChatDatabase); - late final DraftMessageDao draftMessageDao = - DraftMessageDao(this as DriftChatDatabase); + late final DraftMessageDao draftMessageDao = DraftMessageDao(this as DriftChatDatabase); late final LocationDao locationDao = LocationDao(this as DriftChatDatabase); - late final PinnedMessageDao pinnedMessageDao = - PinnedMessageDao(this as DriftChatDatabase); - late final PinnedMessageReactionDao pinnedMessageReactionDao = - PinnedMessageReactionDao(this as DriftChatDatabase); + late final PinnedMessageDao pinnedMessageDao = PinnedMessageDao(this as DriftChatDatabase); + late final PinnedMessageReactionDao pinnedMessageReactionDao = PinnedMessageReactionDao(this as DriftChatDatabase); late final MemberDao memberDao = MemberDao(this as DriftChatDatabase); late final PollDao pollDao = PollDao(this as DriftChatDatabase); late final PollVoteDao pollVoteDao = PollVoteDao(this as DriftChatDatabase); late final ReactionDao reactionDao = ReactionDao(this as DriftChatDatabase); late final ReadDao readDao = ReadDao(this as DriftChatDatabase); - late final ChannelQueryDao channelQueryDao = - ChannelQueryDao(this as DriftChatDatabase); - late final ConnectionEventDao connectionEventDao = - ConnectionEventDao(this as DriftChatDatabase); + late final ChannelQueryDao channelQueryDao = ChannelQueryDao(this as DriftChatDatabase); + late final ConnectionEventDao connectionEventDao = ConnectionEventDao(this as DriftChatDatabase); @override - Iterable> get allTables => - allSchemaEntities.whereType>(); + Iterable> get allTables => allSchemaEntities.whereType>(); @override List get allSchemaEntities => [ - channels, - messages, - draftMessages, - locations, - pinnedMessages, - polls, - pollVotes, - pinnedMessageReactions, - reactions, - users, - members, - reads, - channelQueries, - connectionEvents - ]; + channels, + messages, + draftMessages, + locations, + pinnedMessages, + polls, + pollVotes, + pinnedMessageReactions, + reactions, + users, + members, + reads, + channelQueries, + connectionEvents, + ]; @override StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules( - [ - WritePropagation( - on: TableUpdateQuery.onTableName('channels', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('messages', kind: UpdateKind.delete), - ], - ), - WritePropagation( - on: TableUpdateQuery.onTableName('messages', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('draft_messages', kind: UpdateKind.delete), - ], - ), - WritePropagation( - on: TableUpdateQuery.onTableName('channels', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('draft_messages', kind: UpdateKind.delete), - ], - ), - WritePropagation( - on: TableUpdateQuery.onTableName('channels', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('locations', kind: UpdateKind.delete), - ], - ), - WritePropagation( - on: TableUpdateQuery.onTableName('messages', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('locations', kind: UpdateKind.delete), - ], - ), - WritePropagation( - on: TableUpdateQuery.onTableName('polls', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('poll_votes', kind: UpdateKind.delete), - ], - ), - WritePropagation( - on: TableUpdateQuery.onTableName('pinned_messages', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('pinned_message_reactions', kind: UpdateKind.delete), - ], - ), - WritePropagation( - on: TableUpdateQuery.onTableName('messages', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('reactions', kind: UpdateKind.delete), - ], - ), - WritePropagation( - on: TableUpdateQuery.onTableName('channels', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('members', kind: UpdateKind.delete), - ], - ), - WritePropagation( - on: TableUpdateQuery.onTableName('channels', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('reads', kind: UpdateKind.delete), - ], - ), + [ + WritePropagation( + on: TableUpdateQuery.onTableName('channels', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('messages', kind: UpdateKind.delete), ], - ); + ), + WritePropagation( + on: TableUpdateQuery.onTableName('messages', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('draft_messages', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('channels', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('draft_messages', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('channels', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('locations', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('messages', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('locations', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('polls', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('poll_votes', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('pinned_messages', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('pinned_message_reactions', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('messages', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('reactions', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('channels', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('members', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('channels', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('reads', kind: UpdateKind.delete), + ], + ), + ], + ); } -typedef $$ChannelsTableCreateCompanionBuilder = ChannelsCompanion Function({ - required String id, - required String type, - required String cid, - Value?> ownCapabilities, - required Map config, - Value frozen, - Value lastMessageAt, - Value createdAt, - Value updatedAt, - Value deletedAt, - Value memberCount, - Value messageCount, - Value createdById, - Value?> filterTags, - Value?> extraData, - Value rowid, -}); -typedef $$ChannelsTableUpdateCompanionBuilder = ChannelsCompanion Function({ - Value id, - Value type, - Value cid, - Value?> ownCapabilities, - Value> config, - Value frozen, - Value lastMessageAt, - Value createdAt, - Value updatedAt, - Value deletedAt, - Value memberCount, - Value messageCount, - Value createdById, - Value?> filterTags, - Value?> extraData, - Value rowid, -}); - -final class $$ChannelsTableReferences - extends BaseReferences<_$DriftChatDatabase, $ChannelsTable, ChannelEntity> { +typedef $$ChannelsTableCreateCompanionBuilder = + ChannelsCompanion Function({ + required String id, + required String type, + required String cid, + Value?> ownCapabilities, + required Map config, + Value frozen, + Value lastMessageAt, + Value createdAt, + Value updatedAt, + Value deletedAt, + Value memberCount, + Value messageCount, + Value createdById, + Value?> filterTags, + Value?> extraData, + Value rowid, + }); +typedef $$ChannelsTableUpdateCompanionBuilder = + ChannelsCompanion Function({ + Value id, + Value type, + Value cid, + Value?> ownCapabilities, + Value> config, + Value frozen, + Value lastMessageAt, + Value createdAt, + Value updatedAt, + Value deletedAt, + Value memberCount, + Value messageCount, + Value createdById, + Value?> filterTags, + Value?> extraData, + Value rowid, + }); + +final class $$ChannelsTableReferences extends BaseReferences<_$DriftChatDatabase, $ChannelsTable, ChannelEntity> { $$ChannelsTableReferences(super.$_db, super.$_table, super.$_typedResult); - static MultiTypedResultKey<$MessagesTable, List> - _messagesRefsTable(_$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.messages, - aliasName: $_aliasNameGenerator( - db.channels.cid, db.messages.channelCid)); + static MultiTypedResultKey<$MessagesTable, List> _messagesRefsTable(_$DriftChatDatabase db) => + MultiTypedResultKey.fromTable( + db.messages, + aliasName: $_aliasNameGenerator(db.channels.cid, db.messages.channelCid), + ); $$MessagesTableProcessedTableManager get messagesRefs { - final manager = $$MessagesTableTableManager($_db, $_db.messages).filter( - (f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); + final manager = $$MessagesTableTableManager( + $_db, + $_db.messages, + ).filter((f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); final cache = $_typedResult.readTableOrNull(_messagesRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } - static MultiTypedResultKey<$DraftMessagesTable, List> - _draftMessagesRefsTable(_$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.draftMessages, - aliasName: $_aliasNameGenerator( - db.channels.cid, db.draftMessages.channelCid)); + static MultiTypedResultKey<$DraftMessagesTable, List> _draftMessagesRefsTable( + _$DriftChatDatabase db, + ) => MultiTypedResultKey.fromTable( + db.draftMessages, + aliasName: $_aliasNameGenerator(db.channels.cid, db.draftMessages.channelCid), + ); $$DraftMessagesTableProcessedTableManager get draftMessagesRefs { - final manager = $$DraftMessagesTableTableManager($_db, $_db.draftMessages) - .filter( - (f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); + final manager = $$DraftMessagesTableTableManager( + $_db, + $_db.draftMessages, + ).filter((f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); final cache = $_typedResult.readTableOrNull(_draftMessagesRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } - static MultiTypedResultKey<$LocationsTable, List> - _locationsRefsTable(_$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.locations, - aliasName: $_aliasNameGenerator( - db.channels.cid, db.locations.channelCid)); + static MultiTypedResultKey<$LocationsTable, List> _locationsRefsTable(_$DriftChatDatabase db) => + MultiTypedResultKey.fromTable( + db.locations, + aliasName: $_aliasNameGenerator(db.channels.cid, db.locations.channelCid), + ); $$LocationsTableProcessedTableManager get locationsRefs { - final manager = $$LocationsTableTableManager($_db, $_db.locations).filter( - (f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); + final manager = $$LocationsTableTableManager( + $_db, + $_db.locations, + ).filter((f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); final cache = $_typedResult.readTableOrNull(_locationsRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } - static MultiTypedResultKey<$MembersTable, List> - _membersRefsTable(_$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.members, - aliasName: - $_aliasNameGenerator(db.channels.cid, db.members.channelCid)); + static MultiTypedResultKey<$MembersTable, List> _membersRefsTable(_$DriftChatDatabase db) => + MultiTypedResultKey.fromTable( + db.members, + aliasName: $_aliasNameGenerator(db.channels.cid, db.members.channelCid), + ); $$MembersTableProcessedTableManager get membersRefs { - final manager = $$MembersTableTableManager($_db, $_db.members).filter( - (f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); + final manager = $$MembersTableTableManager( + $_db, + $_db.members, + ).filter((f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); final cache = $_typedResult.readTableOrNull(_membersRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } - static MultiTypedResultKey<$ReadsTable, List> _readsRefsTable( - _$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.reads, - aliasName: - $_aliasNameGenerator(db.channels.cid, db.reads.channelCid)); + static MultiTypedResultKey<$ReadsTable, List> _readsRefsTable(_$DriftChatDatabase db) => + MultiTypedResultKey.fromTable(db.reads, aliasName: $_aliasNameGenerator(db.channels.cid, db.reads.channelCid)); $$ReadsTableProcessedTableManager get readsRefs { - final manager = $$ReadsTableTableManager($_db, $_db.reads).filter( - (f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); + final manager = $$ReadsTableTableManager( + $_db, + $_db.reads, + ).filter((f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); final cache = $_typedResult.readTableOrNull(_readsRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } } -class $$ChannelsTableFilterComposer - extends Composer<_$DriftChatDatabase, $ChannelsTable> { +class $$ChannelsTableFilterComposer extends Composer<_$DriftChatDatabase, $ChannelsTable> { $$ChannelsTableFilterComposer({ required super.$db, required super.$table, @@ -9618,169 +9497,140 @@ class $$ChannelsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnFilters(column)); + ColumnFilters get type => $composableBuilder(column: $table.type, builder: (column) => ColumnFilters(column)); - ColumnFilters get cid => $composableBuilder( - column: $table.cid, builder: (column) => ColumnFilters(column)); + ColumnFilters get cid => $composableBuilder(column: $table.cid, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, List, String> - get ownCapabilities => $composableBuilder( - column: $table.ownCapabilities, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, List, String> get ownCapabilities => + $composableBuilder(column: $table.ownCapabilities, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnWithTypeConverterFilters, Map, - String> - get config => $composableBuilder( - column: $table.config, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, Map, String> get config => + $composableBuilder(column: $table.config, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get frozen => $composableBuilder( - column: $table.frozen, builder: (column) => ColumnFilters(column)); + ColumnFilters get frozen => + $composableBuilder(column: $table.frozen, builder: (column) => ColumnFilters(column)); - ColumnFilters get lastMessageAt => $composableBuilder( - column: $table.lastMessageAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get lastMessageAt => + $composableBuilder(column: $table.lastMessageAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get deletedAt => $composableBuilder( - column: $table.deletedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get deletedAt => + $composableBuilder(column: $table.deletedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get memberCount => $composableBuilder( - column: $table.memberCount, builder: (column) => ColumnFilters(column)); + ColumnFilters get memberCount => + $composableBuilder(column: $table.memberCount, builder: (column) => ColumnFilters(column)); - ColumnFilters get messageCount => $composableBuilder( - column: $table.messageCount, builder: (column) => ColumnFilters(column)); + ColumnFilters get messageCount => + $composableBuilder(column: $table.messageCount, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdById => $composableBuilder( - column: $table.createdById, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdById => + $composableBuilder(column: $table.createdById, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, List, String> - get filterTags => $composableBuilder( - column: $table.filterTags, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, List, String> get filterTags => + $composableBuilder(column: $table.filterTags, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get extraData => $composableBuilder( - column: $table.extraData, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnWithTypeConverterFilters(column)); - Expression messagesRefs( - Expression Function($$MessagesTableFilterComposer f) f) { + Expression messagesRefs(Expression Function($$MessagesTableFilterComposer f) f) { final $$MessagesTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableFilterComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableFilterComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression draftMessagesRefs( - Expression Function($$DraftMessagesTableFilterComposer f) f) { + Expression draftMessagesRefs(Expression Function($$DraftMessagesTableFilterComposer f) f) { final $$DraftMessagesTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.draftMessages, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$DraftMessagesTableFilterComposer( - $db: $db, - $table: $db.draftMessages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.draftMessages, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$DraftMessagesTableFilterComposer( + $db: $db, + $table: $db.draftMessages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression locationsRefs( - Expression Function($$LocationsTableFilterComposer f) f) { + Expression locationsRefs(Expression Function($$LocationsTableFilterComposer f) f) { final $$LocationsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.locations, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$LocationsTableFilterComposer( - $db: $db, - $table: $db.locations, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.locations, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$LocationsTableFilterComposer( + $db: $db, + $table: $db.locations, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression membersRefs( - Expression Function($$MembersTableFilterComposer f) f) { + Expression membersRefs(Expression Function($$MembersTableFilterComposer f) f) { final $$MembersTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.members, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MembersTableFilterComposer( - $db: $db, - $table: $db.members, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.members, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MembersTableFilterComposer( + $db: $db, + $table: $db.members, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression readsRefs( - Expression Function($$ReadsTableFilterComposer f) f) { + Expression readsRefs(Expression Function($$ReadsTableFilterComposer f) f) { final $$ReadsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.reads, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ReadsTableFilterComposer( - $db: $db, - $table: $db.reads, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.reads, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ReadsTableFilterComposer( + $db: $db, + $table: $db.reads, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } } -class $$ChannelsTableOrderingComposer - extends Composer<_$DriftChatDatabase, $ChannelsTable> { +class $$ChannelsTableOrderingComposer extends Composer<_$DriftChatDatabase, $ChannelsTable> { $$ChannelsTableOrderingComposer({ required super.$db, required super.$table, @@ -9788,57 +9638,52 @@ class $$ChannelsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get type => + $composableBuilder(column: $table.type, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get cid => $composableBuilder( - column: $table.cid, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get cid => + $composableBuilder(column: $table.cid, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get ownCapabilities => $composableBuilder( - column: $table.ownCapabilities, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get ownCapabilities => + $composableBuilder(column: $table.ownCapabilities, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get config => $composableBuilder( - column: $table.config, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get config => + $composableBuilder(column: $table.config, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get frozen => $composableBuilder( - column: $table.frozen, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get frozen => + $composableBuilder(column: $table.frozen, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get lastMessageAt => $composableBuilder( - column: $table.lastMessageAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get lastMessageAt => + $composableBuilder(column: $table.lastMessageAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get deletedAt => $composableBuilder( - column: $table.deletedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get deletedAt => + $composableBuilder(column: $table.deletedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get memberCount => $composableBuilder( - column: $table.memberCount, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get memberCount => + $composableBuilder(column: $table.memberCount, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get messageCount => $composableBuilder( - column: $table.messageCount, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get messageCount => + $composableBuilder(column: $table.messageCount, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdById => $composableBuilder( - column: $table.createdById, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdById => + $composableBuilder(column: $table.createdById, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get filterTags => $composableBuilder( - column: $table.filterTags, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get filterTags => + $composableBuilder(column: $table.filterTags, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnOrderings(column)); } -class $$ChannelsTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $ChannelsTable> { +class $$ChannelsTableAnnotationComposer extends Composer<_$DriftChatDatabase, $ChannelsTable> { $$ChannelsTableAnnotationComposer({ required super.$db, required super.$table, @@ -9846,507 +9691,470 @@ class $$ChannelsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get type => - $composableBuilder(column: $table.type, builder: (column) => column); + GeneratedColumn get type => $composableBuilder(column: $table.type, builder: (column) => column); - GeneratedColumn get cid => - $composableBuilder(column: $table.cid, builder: (column) => column); + GeneratedColumn get cid => $composableBuilder(column: $table.cid, builder: (column) => column); GeneratedColumnWithTypeConverter?, String> get ownCapabilities => - $composableBuilder( - column: $table.ownCapabilities, builder: (column) => column); + $composableBuilder(column: $table.ownCapabilities, builder: (column) => column); GeneratedColumnWithTypeConverter, String> get config => $composableBuilder(column: $table.config, builder: (column) => column); - GeneratedColumn get frozen => - $composableBuilder(column: $table.frozen, builder: (column) => column); + GeneratedColumn get frozen => $composableBuilder(column: $table.frozen, builder: (column) => column); - GeneratedColumn get lastMessageAt => $composableBuilder( - column: $table.lastMessageAt, builder: (column) => column); + GeneratedColumn get lastMessageAt => + $composableBuilder(column: $table.lastMessageAt, builder: (column) => column); - GeneratedColumn get createdAt => - $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - GeneratedColumn get updatedAt => - $composableBuilder(column: $table.updatedAt, builder: (column) => column); + GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); - GeneratedColumn get deletedAt => - $composableBuilder(column: $table.deletedAt, builder: (column) => column); + GeneratedColumn get deletedAt => $composableBuilder(column: $table.deletedAt, builder: (column) => column); - GeneratedColumn get memberCount => $composableBuilder( - column: $table.memberCount, builder: (column) => column); + GeneratedColumn get memberCount => $composableBuilder(column: $table.memberCount, builder: (column) => column); - GeneratedColumn get messageCount => $composableBuilder( - column: $table.messageCount, builder: (column) => column); + GeneratedColumn get messageCount => $composableBuilder(column: $table.messageCount, builder: (column) => column); - GeneratedColumn get createdById => $composableBuilder( - column: $table.createdById, builder: (column) => column); + GeneratedColumn get createdById => + $composableBuilder(column: $table.createdById, builder: (column) => column); GeneratedColumnWithTypeConverter?, String> get filterTags => - $composableBuilder( - column: $table.filterTags, builder: (column) => column); + $composableBuilder(column: $table.filterTags, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => column); - Expression messagesRefs( - Expression Function($$MessagesTableAnnotationComposer a) f) { + Expression messagesRefs(Expression Function($$MessagesTableAnnotationComposer a) f) { final $$MessagesTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableAnnotationComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableAnnotationComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } Expression draftMessagesRefs( - Expression Function($$DraftMessagesTableAnnotationComposer a) f) { + Expression Function($$DraftMessagesTableAnnotationComposer a) f, + ) { final $$DraftMessagesTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.draftMessages, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$DraftMessagesTableAnnotationComposer( - $db: $db, - $table: $db.draftMessages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.draftMessages, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$DraftMessagesTableAnnotationComposer( + $db: $db, + $table: $db.draftMessages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression locationsRefs( - Expression Function($$LocationsTableAnnotationComposer a) f) { + Expression locationsRefs(Expression Function($$LocationsTableAnnotationComposer a) f) { final $$LocationsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.locations, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$LocationsTableAnnotationComposer( - $db: $db, - $table: $db.locations, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.locations, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$LocationsTableAnnotationComposer( + $db: $db, + $table: $db.locations, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression membersRefs( - Expression Function($$MembersTableAnnotationComposer a) f) { + Expression membersRefs(Expression Function($$MembersTableAnnotationComposer a) f) { final $$MembersTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.members, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MembersTableAnnotationComposer( - $db: $db, - $table: $db.members, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.members, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MembersTableAnnotationComposer( + $db: $db, + $table: $db.members, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression readsRefs( - Expression Function($$ReadsTableAnnotationComposer a) f) { + Expression readsRefs(Expression Function($$ReadsTableAnnotationComposer a) f) { final $$ReadsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.reads, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ReadsTableAnnotationComposer( - $db: $db, - $table: $db.reads, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.reads, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ReadsTableAnnotationComposer( + $db: $db, + $table: $db.reads, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } } -class $$ChannelsTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $ChannelsTable, - ChannelEntity, - $$ChannelsTableFilterComposer, - $$ChannelsTableOrderingComposer, - $$ChannelsTableAnnotationComposer, - $$ChannelsTableCreateCompanionBuilder, - $$ChannelsTableUpdateCompanionBuilder, - (ChannelEntity, $$ChannelsTableReferences), - ChannelEntity, - PrefetchHooks Function( - {bool messagesRefs, - bool draftMessagesRefs, - bool locationsRefs, - bool membersRefs, - bool readsRefs})> { +class $$ChannelsTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $ChannelsTable, + ChannelEntity, + $$ChannelsTableFilterComposer, + $$ChannelsTableOrderingComposer, + $$ChannelsTableAnnotationComposer, + $$ChannelsTableCreateCompanionBuilder, + $$ChannelsTableUpdateCompanionBuilder, + (ChannelEntity, $$ChannelsTableReferences), + ChannelEntity, + PrefetchHooks Function({ + bool messagesRefs, + bool draftMessagesRefs, + bool locationsRefs, + bool membersRefs, + bool readsRefs, + }) + > { $$ChannelsTableTableManager(_$DriftChatDatabase db, $ChannelsTable table) - : super(TableManagerState( + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$ChannelsTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$ChannelsTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$ChannelsTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value id = const Value.absent(), - Value type = const Value.absent(), - Value cid = const Value.absent(), - Value?> ownCapabilities = const Value.absent(), - Value> config = const Value.absent(), - Value frozen = const Value.absent(), - Value lastMessageAt = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value deletedAt = const Value.absent(), - Value memberCount = const Value.absent(), - Value messageCount = const Value.absent(), - Value createdById = const Value.absent(), - Value?> filterTags = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - ChannelsCompanion( - id: id, - type: type, - cid: cid, - ownCapabilities: ownCapabilities, - config: config, - frozen: frozen, - lastMessageAt: lastMessageAt, - createdAt: createdAt, - updatedAt: updatedAt, - deletedAt: deletedAt, - memberCount: memberCount, - messageCount: messageCount, - createdById: createdById, - filterTags: filterTags, - extraData: extraData, - rowid: rowid, - ), - createCompanionCallback: ({ - required String id, - required String type, - required String cid, - Value?> ownCapabilities = const Value.absent(), - required Map config, - Value frozen = const Value.absent(), - Value lastMessageAt = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value deletedAt = const Value.absent(), - Value memberCount = const Value.absent(), - Value messageCount = const Value.absent(), - Value createdById = const Value.absent(), - Value?> filterTags = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - ChannelsCompanion.insert( - id: id, - type: type, - cid: cid, - ownCapabilities: ownCapabilities, - config: config, - frozen: frozen, - lastMessageAt: lastMessageAt, - createdAt: createdAt, - updatedAt: updatedAt, - deletedAt: deletedAt, - memberCount: memberCount, - messageCount: messageCount, - createdById: createdById, - filterTags: filterTags, - extraData: extraData, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => - (e.readTable(table), $$ChannelsTableReferences(db, table, e))) - .toList(), - prefetchHooksCallback: ( - {messagesRefs = false, - draftMessagesRefs = false, - locationsRefs = false, - membersRefs = false, - readsRefs = false}) { - return PrefetchHooks( - db: db, - explicitlyWatchedTables: [ - if (messagesRefs) db.messages, - if (draftMessagesRefs) db.draftMessages, - if (locationsRefs) db.locations, - if (membersRefs) db.members, - if (readsRefs) db.reads - ], - addJoins: null, - getPrefetchedDataCallback: (items) async { - return [ - if (messagesRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: - $$ChannelsTableReferences._messagesRefsTable(db), - managerFromTypedResult: (p0) => - $$ChannelsTableReferences(db, table, p0) - .messagesRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems - .where((e) => e.channelCid == item.cid), - typedResults: items), - if (draftMessagesRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: $$ChannelsTableReferences - ._draftMessagesRefsTable(db), - managerFromTypedResult: (p0) => - $$ChannelsTableReferences(db, table, p0) - .draftMessagesRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems - .where((e) => e.channelCid == item.cid), - typedResults: items), - if (locationsRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: - $$ChannelsTableReferences._locationsRefsTable(db), - managerFromTypedResult: (p0) => - $$ChannelsTableReferences(db, table, p0) - .locationsRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems - .where((e) => e.channelCid == item.cid), - typedResults: items), - if (membersRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: - $$ChannelsTableReferences._membersRefsTable(db), - managerFromTypedResult: (p0) => - $$ChannelsTableReferences(db, table, p0) - .membersRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems - .where((e) => e.channelCid == item.cid), - typedResults: items), - if (readsRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: - $$ChannelsTableReferences._readsRefsTable(db), - managerFromTypedResult: (p0) => - $$ChannelsTableReferences(db, table, p0).readsRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems - .where((e) => e.channelCid == item.cid), - typedResults: items) - ]; + createFilteringComposer: () => $$ChannelsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$ChannelsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$ChannelsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value type = const Value.absent(), + Value cid = const Value.absent(), + Value?> ownCapabilities = const Value.absent(), + Value> config = const Value.absent(), + Value frozen = const Value.absent(), + Value lastMessageAt = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value deletedAt = const Value.absent(), + Value memberCount = const Value.absent(), + Value messageCount = const Value.absent(), + Value createdById = const Value.absent(), + Value?> filterTags = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => ChannelsCompanion( + id: id, + type: type, + cid: cid, + ownCapabilities: ownCapabilities, + config: config, + frozen: frozen, + lastMessageAt: lastMessageAt, + createdAt: createdAt, + updatedAt: updatedAt, + deletedAt: deletedAt, + memberCount: memberCount, + messageCount: messageCount, + createdById: createdById, + filterTags: filterTags, + extraData: extraData, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String type, + required String cid, + Value?> ownCapabilities = const Value.absent(), + required Map config, + Value frozen = const Value.absent(), + Value lastMessageAt = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value deletedAt = const Value.absent(), + Value memberCount = const Value.absent(), + Value messageCount = const Value.absent(), + Value createdById = const Value.absent(), + Value?> filterTags = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => ChannelsCompanion.insert( + id: id, + type: type, + cid: cid, + ownCapabilities: ownCapabilities, + config: config, + frozen: frozen, + lastMessageAt: lastMessageAt, + createdAt: createdAt, + updatedAt: updatedAt, + deletedAt: deletedAt, + memberCount: memberCount, + messageCount: messageCount, + createdById: createdById, + filterTags: filterTags, + extraData: extraData, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$ChannelsTableReferences(db, table, e))).toList(), + prefetchHooksCallback: + ({ + messagesRefs = false, + draftMessagesRefs = false, + locationsRefs = false, + membersRefs = false, + readsRefs = false, + }) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [ + if (messagesRefs) db.messages, + if (draftMessagesRefs) db.draftMessages, + if (locationsRefs) db.locations, + if (membersRefs) db.members, + if (readsRefs) db.reads, + ], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (messagesRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$ChannelsTableReferences._messagesRefsTable(db), + managerFromTypedResult: (p0) => $$ChannelsTableReferences(db, table, p0).messagesRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.channelCid == item.cid), + typedResults: items, + ), + if (draftMessagesRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$ChannelsTableReferences._draftMessagesRefsTable(db), + managerFromTypedResult: (p0) => $$ChannelsTableReferences(db, table, p0).draftMessagesRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.channelCid == item.cid), + typedResults: items, + ), + if (locationsRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$ChannelsTableReferences._locationsRefsTable(db), + managerFromTypedResult: (p0) => $$ChannelsTableReferences(db, table, p0).locationsRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.channelCid == item.cid), + typedResults: items, + ), + if (membersRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$ChannelsTableReferences._membersRefsTable(db), + managerFromTypedResult: (p0) => $$ChannelsTableReferences(db, table, p0).membersRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.channelCid == item.cid), + typedResults: items, + ), + if (readsRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$ChannelsTableReferences._readsRefsTable(db), + managerFromTypedResult: (p0) => $$ChannelsTableReferences(db, table, p0).readsRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.channelCid == item.cid), + typedResults: items, + ), + ]; + }, + ); }, - ); - }, - )); + ), + ); } -typedef $$ChannelsTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $ChannelsTable, - ChannelEntity, - $$ChannelsTableFilterComposer, - $$ChannelsTableOrderingComposer, - $$ChannelsTableAnnotationComposer, - $$ChannelsTableCreateCompanionBuilder, - $$ChannelsTableUpdateCompanionBuilder, - (ChannelEntity, $$ChannelsTableReferences), - ChannelEntity, - PrefetchHooks Function( - {bool messagesRefs, +typedef $$ChannelsTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $ChannelsTable, + ChannelEntity, + $$ChannelsTableFilterComposer, + $$ChannelsTableOrderingComposer, + $$ChannelsTableAnnotationComposer, + $$ChannelsTableCreateCompanionBuilder, + $$ChannelsTableUpdateCompanionBuilder, + (ChannelEntity, $$ChannelsTableReferences), + ChannelEntity, + PrefetchHooks Function({ + bool messagesRefs, bool draftMessagesRefs, bool locationsRefs, bool membersRefs, - bool readsRefs})>; -typedef $$MessagesTableCreateCompanionBuilder = MessagesCompanion Function({ - required String id, - Value messageText, - required List attachments, - required String state, - Value type, - required List mentionedUsers, - Value?> reactionGroups, - Value parentId, - Value quotedMessageId, - Value pollId, - Value replyCount, - Value showInChannel, - Value shadowed, - Value command, - Value localCreatedAt, - Value remoteCreatedAt, - Value localUpdatedAt, - Value remoteUpdatedAt, - Value localDeletedAt, - Value remoteDeletedAt, - Value deletedForMe, - Value messageTextUpdatedAt, - Value userId, - Value channelRole, - Value pinned, - Value pinnedAt, - Value pinExpires, - Value pinnedByUserId, - required String channelCid, - Value?> i18n, - Value?> restrictedVisibility, - Value?> extraData, - Value rowid, -}); -typedef $$MessagesTableUpdateCompanionBuilder = MessagesCompanion Function({ - Value id, - Value messageText, - Value> attachments, - Value state, - Value type, - Value> mentionedUsers, - Value?> reactionGroups, - Value parentId, - Value quotedMessageId, - Value pollId, - Value replyCount, - Value showInChannel, - Value shadowed, - Value command, - Value localCreatedAt, - Value remoteCreatedAt, - Value localUpdatedAt, - Value remoteUpdatedAt, - Value localDeletedAt, - Value remoteDeletedAt, - Value deletedForMe, - Value messageTextUpdatedAt, - Value userId, - Value channelRole, - Value pinned, - Value pinnedAt, - Value pinExpires, - Value pinnedByUserId, - Value channelCid, - Value?> i18n, - Value?> restrictedVisibility, - Value?> extraData, - Value rowid, -}); - -final class $$MessagesTableReferences - extends BaseReferences<_$DriftChatDatabase, $MessagesTable, MessageEntity> { + bool readsRefs, + }) + >; +typedef $$MessagesTableCreateCompanionBuilder = + MessagesCompanion Function({ + required String id, + Value messageText, + required List attachments, + required String state, + Value type, + required List mentionedUsers, + Value?> reactionGroups, + Value parentId, + Value quotedMessageId, + Value pollId, + Value replyCount, + Value showInChannel, + Value shadowed, + Value command, + Value localCreatedAt, + Value remoteCreatedAt, + Value localUpdatedAt, + Value remoteUpdatedAt, + Value localDeletedAt, + Value remoteDeletedAt, + Value deletedForMe, + Value messageTextUpdatedAt, + Value userId, + Value channelRole, + Value pinned, + Value pinnedAt, + Value pinExpires, + Value pinnedByUserId, + required String channelCid, + Value?> i18n, + Value?> restrictedVisibility, + Value?> extraData, + Value rowid, + }); +typedef $$MessagesTableUpdateCompanionBuilder = + MessagesCompanion Function({ + Value id, + Value messageText, + Value> attachments, + Value state, + Value type, + Value> mentionedUsers, + Value?> reactionGroups, + Value parentId, + Value quotedMessageId, + Value pollId, + Value replyCount, + Value showInChannel, + Value shadowed, + Value command, + Value localCreatedAt, + Value remoteCreatedAt, + Value localUpdatedAt, + Value remoteUpdatedAt, + Value localDeletedAt, + Value remoteDeletedAt, + Value deletedForMe, + Value messageTextUpdatedAt, + Value userId, + Value channelRole, + Value pinned, + Value pinnedAt, + Value pinExpires, + Value pinnedByUserId, + Value channelCid, + Value?> i18n, + Value?> restrictedVisibility, + Value?> extraData, + Value rowid, + }); + +final class $$MessagesTableReferences extends BaseReferences<_$DriftChatDatabase, $MessagesTable, MessageEntity> { $$MessagesTableReferences(super.$_db, super.$_table, super.$_typedResult); static $ChannelsTable _channelCidTable(_$DriftChatDatabase db) => - db.channels.createAlias( - $_aliasNameGenerator(db.messages.channelCid, db.channels.cid)); + db.channels.createAlias($_aliasNameGenerator(db.messages.channelCid, db.channels.cid)); $$ChannelsTableProcessedTableManager get channelCid { final $_column = $_itemColumn('channel_cid')!; - final manager = $$ChannelsTableTableManager($_db, $_db.channels) - .filter((f) => f.cid.sqlEquals($_column)); + final manager = $$ChannelsTableTableManager($_db, $_db.channels).filter((f) => f.cid.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_channelCidTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } - static MultiTypedResultKey<$DraftMessagesTable, List> - _draftMessagesRefsTable(_$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.draftMessages, - aliasName: $_aliasNameGenerator( - db.messages.id, db.draftMessages.parentId)); + static MultiTypedResultKey<$DraftMessagesTable, List> _draftMessagesRefsTable( + _$DriftChatDatabase db, + ) => MultiTypedResultKey.fromTable( + db.draftMessages, + aliasName: $_aliasNameGenerator(db.messages.id, db.draftMessages.parentId), + ); $$DraftMessagesTableProcessedTableManager get draftMessagesRefs { - final manager = $$DraftMessagesTableTableManager($_db, $_db.draftMessages) - .filter((f) => f.parentId.id.sqlEquals($_itemColumn('id')!)); + final manager = $$DraftMessagesTableTableManager( + $_db, + $_db.draftMessages, + ).filter((f) => f.parentId.id.sqlEquals($_itemColumn('id')!)); final cache = $_typedResult.readTableOrNull(_draftMessagesRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } - static MultiTypedResultKey<$LocationsTable, List> - _locationsRefsTable(_$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.locations, - aliasName: - $_aliasNameGenerator(db.messages.id, db.locations.messageId)); + static MultiTypedResultKey<$LocationsTable, List> _locationsRefsTable(_$DriftChatDatabase db) => + MultiTypedResultKey.fromTable( + db.locations, + aliasName: $_aliasNameGenerator(db.messages.id, db.locations.messageId), + ); $$LocationsTableProcessedTableManager get locationsRefs { - final manager = $$LocationsTableTableManager($_db, $_db.locations) - .filter((f) => f.messageId.id.sqlEquals($_itemColumn('id')!)); + final manager = $$LocationsTableTableManager( + $_db, + $_db.locations, + ).filter((f) => f.messageId.id.sqlEquals($_itemColumn('id')!)); final cache = $_typedResult.readTableOrNull(_locationsRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } - static MultiTypedResultKey<$ReactionsTable, List> - _reactionsRefsTable(_$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.reactions, - aliasName: - $_aliasNameGenerator(db.messages.id, db.reactions.messageId)); + static MultiTypedResultKey<$ReactionsTable, List> _reactionsRefsTable(_$DriftChatDatabase db) => + MultiTypedResultKey.fromTable( + db.reactions, + aliasName: $_aliasNameGenerator(db.messages.id, db.reactions.messageId), + ); $$ReactionsTableProcessedTableManager get reactionsRefs { - final manager = $$ReactionsTableTableManager($_db, $_db.reactions) - .filter((f) => f.messageId.id.sqlEquals($_itemColumn('id')!)); + final manager = $$ReactionsTableTableManager( + $_db, + $_db.reactions, + ).filter((f) => f.messageId.id.sqlEquals($_itemColumn('id')!)); final cache = $_typedResult.readTableOrNull(_reactionsRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } } -class $$MessagesTableFilterComposer - extends Composer<_$DriftChatDatabase, $MessagesTable> { +class $$MessagesTableFilterComposer extends Composer<_$DriftChatDatabase, $MessagesTable> { $$MessagesTableFilterComposer({ required super.$db, required super.$table, @@ -10354,209 +10162,173 @@ class $$MessagesTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get messageText => $composableBuilder( - column: $table.messageText, builder: (column) => ColumnFilters(column)); + ColumnFilters get messageText => + $composableBuilder(column: $table.messageText, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, List, String> - get attachments => $composableBuilder( - column: $table.attachments, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, List, String> get attachments => + $composableBuilder(column: $table.attachments, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get state => $composableBuilder( - column: $table.state, builder: (column) => ColumnFilters(column)); + ColumnFilters get state => + $composableBuilder(column: $table.state, builder: (column) => ColumnFilters(column)); - ColumnFilters get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnFilters(column)); + ColumnFilters get type => $composableBuilder(column: $table.type, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, List, String> - get mentionedUsers => $composableBuilder( - column: $table.mentionedUsers, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, List, String> get mentionedUsers => + $composableBuilder(column: $table.mentionedUsers, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnWithTypeConverterFilters?, - Map, String> - get reactionGroups => $composableBuilder( - column: $table.reactionGroups, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get reactionGroups => + $composableBuilder(column: $table.reactionGroups, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get parentId => $composableBuilder( - column: $table.parentId, builder: (column) => ColumnFilters(column)); + ColumnFilters get parentId => + $composableBuilder(column: $table.parentId, builder: (column) => ColumnFilters(column)); - ColumnFilters get quotedMessageId => $composableBuilder( - column: $table.quotedMessageId, - builder: (column) => ColumnFilters(column)); + ColumnFilters get quotedMessageId => + $composableBuilder(column: $table.quotedMessageId, builder: (column) => ColumnFilters(column)); - ColumnFilters get pollId => $composableBuilder( - column: $table.pollId, builder: (column) => ColumnFilters(column)); + ColumnFilters get pollId => + $composableBuilder(column: $table.pollId, builder: (column) => ColumnFilters(column)); - ColumnFilters get replyCount => $composableBuilder( - column: $table.replyCount, builder: (column) => ColumnFilters(column)); + ColumnFilters get replyCount => + $composableBuilder(column: $table.replyCount, builder: (column) => ColumnFilters(column)); - ColumnFilters get showInChannel => $composableBuilder( - column: $table.showInChannel, builder: (column) => ColumnFilters(column)); + ColumnFilters get showInChannel => + $composableBuilder(column: $table.showInChannel, builder: (column) => ColumnFilters(column)); - ColumnFilters get shadowed => $composableBuilder( - column: $table.shadowed, builder: (column) => ColumnFilters(column)); + ColumnFilters get shadowed => + $composableBuilder(column: $table.shadowed, builder: (column) => ColumnFilters(column)); - ColumnFilters get command => $composableBuilder( - column: $table.command, builder: (column) => ColumnFilters(column)); + ColumnFilters get command => + $composableBuilder(column: $table.command, builder: (column) => ColumnFilters(column)); - ColumnFilters get localCreatedAt => $composableBuilder( - column: $table.localCreatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get localCreatedAt => + $composableBuilder(column: $table.localCreatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get remoteCreatedAt => $composableBuilder( - column: $table.remoteCreatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get remoteCreatedAt => + $composableBuilder(column: $table.remoteCreatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get localUpdatedAt => $composableBuilder( - column: $table.localUpdatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get localUpdatedAt => + $composableBuilder(column: $table.localUpdatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get remoteUpdatedAt => $composableBuilder( - column: $table.remoteUpdatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get remoteUpdatedAt => + $composableBuilder(column: $table.remoteUpdatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get localDeletedAt => $composableBuilder( - column: $table.localDeletedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get localDeletedAt => + $composableBuilder(column: $table.localDeletedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get remoteDeletedAt => $composableBuilder( - column: $table.remoteDeletedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get remoteDeletedAt => + $composableBuilder(column: $table.remoteDeletedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get deletedForMe => $composableBuilder( - column: $table.deletedForMe, builder: (column) => ColumnFilters(column)); + ColumnFilters get deletedForMe => + $composableBuilder(column: $table.deletedForMe, builder: (column) => ColumnFilters(column)); - ColumnFilters get messageTextUpdatedAt => $composableBuilder( - column: $table.messageTextUpdatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get messageTextUpdatedAt => + $composableBuilder(column: $table.messageTextUpdatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnFilters(column)); + ColumnFilters get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnFilters(column)); - ColumnFilters get channelRole => $composableBuilder( - column: $table.channelRole, builder: (column) => ColumnFilters(column)); + ColumnFilters get channelRole => + $composableBuilder(column: $table.channelRole, builder: (column) => ColumnFilters(column)); - ColumnFilters get pinned => $composableBuilder( - column: $table.pinned, builder: (column) => ColumnFilters(column)); + ColumnFilters get pinned => + $composableBuilder(column: $table.pinned, builder: (column) => ColumnFilters(column)); - ColumnFilters get pinnedAt => $composableBuilder( - column: $table.pinnedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get pinnedAt => + $composableBuilder(column: $table.pinnedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get pinExpires => $composableBuilder( - column: $table.pinExpires, builder: (column) => ColumnFilters(column)); + ColumnFilters get pinExpires => + $composableBuilder(column: $table.pinExpires, builder: (column) => ColumnFilters(column)); - ColumnFilters get pinnedByUserId => $composableBuilder( - column: $table.pinnedByUserId, - builder: (column) => ColumnFilters(column)); + ColumnFilters get pinnedByUserId => + $composableBuilder(column: $table.pinnedByUserId, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get i18n => $composableBuilder( - column: $table.i18n, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get i18n => + $composableBuilder(column: $table.i18n, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnWithTypeConverterFilters?, List, String> - get restrictedVisibility => $composableBuilder( - column: $table.restrictedVisibility, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, List, String> get restrictedVisibility => $composableBuilder( + column: $table.restrictedVisibility, + builder: (column) => ColumnWithTypeConverterFilters(column), + ); - ColumnWithTypeConverterFilters?, Map, - String> - get extraData => $composableBuilder( - column: $table.extraData, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnWithTypeConverterFilters(column)); $$ChannelsTableFilterComposer get channelCid { final $$ChannelsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableFilterComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableFilterComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } - Expression draftMessagesRefs( - Expression Function($$DraftMessagesTableFilterComposer f) f) { + Expression draftMessagesRefs(Expression Function($$DraftMessagesTableFilterComposer f) f) { final $$DraftMessagesTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.draftMessages, - getReferencedColumn: (t) => t.parentId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$DraftMessagesTableFilterComposer( - $db: $db, - $table: $db.draftMessages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.draftMessages, + getReferencedColumn: (t) => t.parentId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$DraftMessagesTableFilterComposer( + $db: $db, + $table: $db.draftMessages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression locationsRefs( - Expression Function($$LocationsTableFilterComposer f) f) { + Expression locationsRefs(Expression Function($$LocationsTableFilterComposer f) f) { final $$LocationsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.locations, - getReferencedColumn: (t) => t.messageId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$LocationsTableFilterComposer( - $db: $db, - $table: $db.locations, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.locations, + getReferencedColumn: (t) => t.messageId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$LocationsTableFilterComposer( + $db: $db, + $table: $db.locations, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression reactionsRefs( - Expression Function($$ReactionsTableFilterComposer f) f) { + Expression reactionsRefs(Expression Function($$ReactionsTableFilterComposer f) f) { final $$ReactionsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.reactions, - getReferencedColumn: (t) => t.messageId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ReactionsTableFilterComposer( - $db: $db, - $table: $db.reactions, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.reactions, + getReferencedColumn: (t) => t.messageId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ReactionsTableFilterComposer( + $db: $db, + $table: $db.reactions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } } -class $$MessagesTableOrderingComposer - extends Composer<_$DriftChatDatabase, $MessagesTable> { +class $$MessagesTableOrderingComposer extends Composer<_$DriftChatDatabase, $MessagesTable> { $$MessagesTableOrderingComposer({ required super.$db, required super.$table, @@ -10564,136 +10336,118 @@ class $$MessagesTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get messageText => $composableBuilder( - column: $table.messageText, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get messageText => + $composableBuilder(column: $table.messageText, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get attachments => $composableBuilder( - column: $table.attachments, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get attachments => + $composableBuilder(column: $table.attachments, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get state => $composableBuilder( - column: $table.state, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get state => + $composableBuilder(column: $table.state, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get type => + $composableBuilder(column: $table.type, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get mentionedUsers => $composableBuilder( - column: $table.mentionedUsers, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get mentionedUsers => + $composableBuilder(column: $table.mentionedUsers, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get reactionGroups => $composableBuilder( - column: $table.reactionGroups, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get reactionGroups => + $composableBuilder(column: $table.reactionGroups, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get parentId => $composableBuilder( - column: $table.parentId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get parentId => + $composableBuilder(column: $table.parentId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get quotedMessageId => $composableBuilder( - column: $table.quotedMessageId, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get quotedMessageId => + $composableBuilder(column: $table.quotedMessageId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pollId => $composableBuilder( - column: $table.pollId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pollId => + $composableBuilder(column: $table.pollId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get replyCount => $composableBuilder( - column: $table.replyCount, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get replyCount => + $composableBuilder(column: $table.replyCount, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get showInChannel => $composableBuilder( - column: $table.showInChannel, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get showInChannel => + $composableBuilder(column: $table.showInChannel, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get shadowed => $composableBuilder( - column: $table.shadowed, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get shadowed => + $composableBuilder(column: $table.shadowed, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get command => $composableBuilder( - column: $table.command, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get command => + $composableBuilder(column: $table.command, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get localCreatedAt => $composableBuilder( - column: $table.localCreatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get localCreatedAt => + $composableBuilder(column: $table.localCreatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get remoteCreatedAt => $composableBuilder( - column: $table.remoteCreatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get remoteCreatedAt => + $composableBuilder(column: $table.remoteCreatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get localUpdatedAt => $composableBuilder( - column: $table.localUpdatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get localUpdatedAt => + $composableBuilder(column: $table.localUpdatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get remoteUpdatedAt => $composableBuilder( - column: $table.remoteUpdatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get remoteUpdatedAt => + $composableBuilder(column: $table.remoteUpdatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get localDeletedAt => $composableBuilder( - column: $table.localDeletedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get localDeletedAt => + $composableBuilder(column: $table.localDeletedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get remoteDeletedAt => $composableBuilder( - column: $table.remoteDeletedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get remoteDeletedAt => + $composableBuilder(column: $table.remoteDeletedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get deletedForMe => $composableBuilder( - column: $table.deletedForMe, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get deletedForMe => + $composableBuilder(column: $table.deletedForMe, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get messageTextUpdatedAt => $composableBuilder( - column: $table.messageTextUpdatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get messageTextUpdatedAt => + $composableBuilder(column: $table.messageTextUpdatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get channelRole => $composableBuilder( - column: $table.channelRole, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get channelRole => + $composableBuilder(column: $table.channelRole, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pinned => $composableBuilder( - column: $table.pinned, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pinned => + $composableBuilder(column: $table.pinned, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pinnedAt => $composableBuilder( - column: $table.pinnedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pinnedAt => + $composableBuilder(column: $table.pinnedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pinExpires => $composableBuilder( - column: $table.pinExpires, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pinExpires => + $composableBuilder(column: $table.pinExpires, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pinnedByUserId => $composableBuilder( - column: $table.pinnedByUserId, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pinnedByUserId => + $composableBuilder(column: $table.pinnedByUserId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get i18n => $composableBuilder( - column: $table.i18n, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get i18n => + $composableBuilder(column: $table.i18n, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get restrictedVisibility => $composableBuilder( - column: $table.restrictedVisibility, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get restrictedVisibility => + $composableBuilder(column: $table.restrictedVisibility, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnOrderings(column)); $$ChannelsTableOrderingComposer get channelCid { final $$ChannelsTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableOrderingComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableOrderingComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$MessagesTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $MessagesTable> { +class $$MessagesTableAnnotationComposer extends Composer<_$DriftChatDatabase, $MessagesTable> { $$MessagesTableAnnotationComposer({ required super.$db, required super.$table, @@ -10701,537 +10455,484 @@ class $$MessagesTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get messageText => $composableBuilder( - column: $table.messageText, builder: (column) => column); + GeneratedColumn get messageText => + $composableBuilder(column: $table.messageText, builder: (column) => column); GeneratedColumnWithTypeConverter, String> get attachments => - $composableBuilder( - column: $table.attachments, builder: (column) => column); + $composableBuilder(column: $table.attachments, builder: (column) => column); - GeneratedColumn get state => - $composableBuilder(column: $table.state, builder: (column) => column); + GeneratedColumn get state => $composableBuilder(column: $table.state, builder: (column) => column); - GeneratedColumn get type => - $composableBuilder(column: $table.type, builder: (column) => column); + GeneratedColumn get type => $composableBuilder(column: $table.type, builder: (column) => column); GeneratedColumnWithTypeConverter, String> get mentionedUsers => - $composableBuilder( - column: $table.mentionedUsers, builder: (column) => column); + $composableBuilder(column: $table.mentionedUsers, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get reactionGroups => $composableBuilder( - column: $table.reactionGroups, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get reactionGroups => + $composableBuilder(column: $table.reactionGroups, builder: (column) => column); - GeneratedColumn get parentId => - $composableBuilder(column: $table.parentId, builder: (column) => column); + GeneratedColumn get parentId => $composableBuilder(column: $table.parentId, builder: (column) => column); - GeneratedColumn get quotedMessageId => $composableBuilder( - column: $table.quotedMessageId, builder: (column) => column); + GeneratedColumn get quotedMessageId => + $composableBuilder(column: $table.quotedMessageId, builder: (column) => column); - GeneratedColumn get pollId => - $composableBuilder(column: $table.pollId, builder: (column) => column); + GeneratedColumn get pollId => $composableBuilder(column: $table.pollId, builder: (column) => column); - GeneratedColumn get replyCount => $composableBuilder( - column: $table.replyCount, builder: (column) => column); + GeneratedColumn get replyCount => $composableBuilder(column: $table.replyCount, builder: (column) => column); - GeneratedColumn get showInChannel => $composableBuilder( - column: $table.showInChannel, builder: (column) => column); + GeneratedColumn get showInChannel => + $composableBuilder(column: $table.showInChannel, builder: (column) => column); - GeneratedColumn get shadowed => - $composableBuilder(column: $table.shadowed, builder: (column) => column); + GeneratedColumn get shadowed => $composableBuilder(column: $table.shadowed, builder: (column) => column); - GeneratedColumn get command => - $composableBuilder(column: $table.command, builder: (column) => column); + GeneratedColumn get command => $composableBuilder(column: $table.command, builder: (column) => column); - GeneratedColumn get localCreatedAt => $composableBuilder( - column: $table.localCreatedAt, builder: (column) => column); + GeneratedColumn get localCreatedAt => + $composableBuilder(column: $table.localCreatedAt, builder: (column) => column); - GeneratedColumn get remoteCreatedAt => $composableBuilder( - column: $table.remoteCreatedAt, builder: (column) => column); + GeneratedColumn get remoteCreatedAt => + $composableBuilder(column: $table.remoteCreatedAt, builder: (column) => column); - GeneratedColumn get localUpdatedAt => $composableBuilder( - column: $table.localUpdatedAt, builder: (column) => column); + GeneratedColumn get localUpdatedAt => + $composableBuilder(column: $table.localUpdatedAt, builder: (column) => column); - GeneratedColumn get remoteUpdatedAt => $composableBuilder( - column: $table.remoteUpdatedAt, builder: (column) => column); + GeneratedColumn get remoteUpdatedAt => + $composableBuilder(column: $table.remoteUpdatedAt, builder: (column) => column); - GeneratedColumn get localDeletedAt => $composableBuilder( - column: $table.localDeletedAt, builder: (column) => column); + GeneratedColumn get localDeletedAt => + $composableBuilder(column: $table.localDeletedAt, builder: (column) => column); - GeneratedColumn get remoteDeletedAt => $composableBuilder( - column: $table.remoteDeletedAt, builder: (column) => column); + GeneratedColumn get remoteDeletedAt => + $composableBuilder(column: $table.remoteDeletedAt, builder: (column) => column); - GeneratedColumn get deletedForMe => $composableBuilder( - column: $table.deletedForMe, builder: (column) => column); + GeneratedColumn get deletedForMe => + $composableBuilder(column: $table.deletedForMe, builder: (column) => column); - GeneratedColumn get messageTextUpdatedAt => $composableBuilder( - column: $table.messageTextUpdatedAt, builder: (column) => column); + GeneratedColumn get messageTextUpdatedAt => + $composableBuilder(column: $table.messageTextUpdatedAt, builder: (column) => column); - GeneratedColumn get userId => - $composableBuilder(column: $table.userId, builder: (column) => column); + GeneratedColumn get userId => $composableBuilder(column: $table.userId, builder: (column) => column); - GeneratedColumn get channelRole => $composableBuilder( - column: $table.channelRole, builder: (column) => column); + GeneratedColumn get channelRole => + $composableBuilder(column: $table.channelRole, builder: (column) => column); - GeneratedColumn get pinned => - $composableBuilder(column: $table.pinned, builder: (column) => column); + GeneratedColumn get pinned => $composableBuilder(column: $table.pinned, builder: (column) => column); - GeneratedColumn get pinnedAt => - $composableBuilder(column: $table.pinnedAt, builder: (column) => column); + GeneratedColumn get pinnedAt => $composableBuilder(column: $table.pinnedAt, builder: (column) => column); - GeneratedColumn get pinExpires => $composableBuilder( - column: $table.pinExpires, builder: (column) => column); + GeneratedColumn get pinExpires => + $composableBuilder(column: $table.pinExpires, builder: (column) => column); - GeneratedColumn get pinnedByUserId => $composableBuilder( - column: $table.pinnedByUserId, builder: (column) => column); + GeneratedColumn get pinnedByUserId => + $composableBuilder(column: $table.pinnedByUserId, builder: (column) => column); GeneratedColumnWithTypeConverter?, String> get i18n => $composableBuilder(column: $table.i18n, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get restrictedVisibility => $composableBuilder( - column: $table.restrictedVisibility, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get restrictedVisibility => + $composableBuilder(column: $table.restrictedVisibility, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => column); $$ChannelsTableAnnotationComposer get channelCid { final $$ChannelsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableAnnotationComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableAnnotationComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } Expression draftMessagesRefs( - Expression Function($$DraftMessagesTableAnnotationComposer a) f) { + Expression Function($$DraftMessagesTableAnnotationComposer a) f, + ) { final $$DraftMessagesTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.draftMessages, - getReferencedColumn: (t) => t.parentId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$DraftMessagesTableAnnotationComposer( - $db: $db, - $table: $db.draftMessages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.draftMessages, + getReferencedColumn: (t) => t.parentId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$DraftMessagesTableAnnotationComposer( + $db: $db, + $table: $db.draftMessages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression locationsRefs( - Expression Function($$LocationsTableAnnotationComposer a) f) { + Expression locationsRefs(Expression Function($$LocationsTableAnnotationComposer a) f) { final $$LocationsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.locations, - getReferencedColumn: (t) => t.messageId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$LocationsTableAnnotationComposer( - $db: $db, - $table: $db.locations, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.locations, + getReferencedColumn: (t) => t.messageId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$LocationsTableAnnotationComposer( + $db: $db, + $table: $db.locations, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression reactionsRefs( - Expression Function($$ReactionsTableAnnotationComposer a) f) { + Expression reactionsRefs(Expression Function($$ReactionsTableAnnotationComposer a) f) { final $$ReactionsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.reactions, - getReferencedColumn: (t) => t.messageId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ReactionsTableAnnotationComposer( - $db: $db, - $table: $db.reactions, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.reactions, + getReferencedColumn: (t) => t.messageId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ReactionsTableAnnotationComposer( + $db: $db, + $table: $db.reactions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } } -class $$MessagesTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $MessagesTable, - MessageEntity, - $$MessagesTableFilterComposer, - $$MessagesTableOrderingComposer, - $$MessagesTableAnnotationComposer, - $$MessagesTableCreateCompanionBuilder, - $$MessagesTableUpdateCompanionBuilder, - (MessageEntity, $$MessagesTableReferences), - MessageEntity, - PrefetchHooks Function( - {bool channelCid, - bool draftMessagesRefs, - bool locationsRefs, - bool reactionsRefs})> { +class $$MessagesTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $MessagesTable, + MessageEntity, + $$MessagesTableFilterComposer, + $$MessagesTableOrderingComposer, + $$MessagesTableAnnotationComposer, + $$MessagesTableCreateCompanionBuilder, + $$MessagesTableUpdateCompanionBuilder, + (MessageEntity, $$MessagesTableReferences), + MessageEntity, + PrefetchHooks Function({bool channelCid, bool draftMessagesRefs, bool locationsRefs, bool reactionsRefs}) + > { $$MessagesTableTableManager(_$DriftChatDatabase db, $MessagesTable table) - : super(TableManagerState( + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$MessagesTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$MessagesTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$MessagesTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value id = const Value.absent(), - Value messageText = const Value.absent(), - Value> attachments = const Value.absent(), - Value state = const Value.absent(), - Value type = const Value.absent(), - Value> mentionedUsers = const Value.absent(), - Value?> reactionGroups = - const Value.absent(), - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value pollId = const Value.absent(), - Value replyCount = const Value.absent(), - Value showInChannel = const Value.absent(), - Value shadowed = const Value.absent(), - Value command = const Value.absent(), - Value localCreatedAt = const Value.absent(), - Value remoteCreatedAt = const Value.absent(), - Value localUpdatedAt = const Value.absent(), - Value remoteUpdatedAt = const Value.absent(), - Value localDeletedAt = const Value.absent(), - Value remoteDeletedAt = const Value.absent(), - Value deletedForMe = const Value.absent(), - Value messageTextUpdatedAt = const Value.absent(), - Value userId = const Value.absent(), - Value channelRole = const Value.absent(), - Value pinned = const Value.absent(), - Value pinnedAt = const Value.absent(), - Value pinExpires = const Value.absent(), - Value pinnedByUserId = const Value.absent(), - Value channelCid = const Value.absent(), - Value?> i18n = const Value.absent(), - Value?> restrictedVisibility = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - MessagesCompanion( - id: id, - messageText: messageText, - attachments: attachments, - state: state, - type: type, - mentionedUsers: mentionedUsers, - reactionGroups: reactionGroups, - parentId: parentId, - quotedMessageId: quotedMessageId, - pollId: pollId, - replyCount: replyCount, - showInChannel: showInChannel, - shadowed: shadowed, - command: command, - localCreatedAt: localCreatedAt, - remoteCreatedAt: remoteCreatedAt, - localUpdatedAt: localUpdatedAt, - remoteUpdatedAt: remoteUpdatedAt, - localDeletedAt: localDeletedAt, - remoteDeletedAt: remoteDeletedAt, - deletedForMe: deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt, - userId: userId, - channelRole: channelRole, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedByUserId: pinnedByUserId, - channelCid: channelCid, - i18n: i18n, - restrictedVisibility: restrictedVisibility, - extraData: extraData, - rowid: rowid, - ), - createCompanionCallback: ({ - required String id, - Value messageText = const Value.absent(), - required List attachments, - required String state, - Value type = const Value.absent(), - required List mentionedUsers, - Value?> reactionGroups = - const Value.absent(), - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value pollId = const Value.absent(), - Value replyCount = const Value.absent(), - Value showInChannel = const Value.absent(), - Value shadowed = const Value.absent(), - Value command = const Value.absent(), - Value localCreatedAt = const Value.absent(), - Value remoteCreatedAt = const Value.absent(), - Value localUpdatedAt = const Value.absent(), - Value remoteUpdatedAt = const Value.absent(), - Value localDeletedAt = const Value.absent(), - Value remoteDeletedAt = const Value.absent(), - Value deletedForMe = const Value.absent(), - Value messageTextUpdatedAt = const Value.absent(), - Value userId = const Value.absent(), - Value channelRole = const Value.absent(), - Value pinned = const Value.absent(), - Value pinnedAt = const Value.absent(), - Value pinExpires = const Value.absent(), - Value pinnedByUserId = const Value.absent(), - required String channelCid, - Value?> i18n = const Value.absent(), - Value?> restrictedVisibility = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - MessagesCompanion.insert( - id: id, - messageText: messageText, - attachments: attachments, - state: state, - type: type, - mentionedUsers: mentionedUsers, - reactionGroups: reactionGroups, - parentId: parentId, - quotedMessageId: quotedMessageId, - pollId: pollId, - replyCount: replyCount, - showInChannel: showInChannel, - shadowed: shadowed, - command: command, - localCreatedAt: localCreatedAt, - remoteCreatedAt: remoteCreatedAt, - localUpdatedAt: localUpdatedAt, - remoteUpdatedAt: remoteUpdatedAt, - localDeletedAt: localDeletedAt, - remoteDeletedAt: remoteDeletedAt, - deletedForMe: deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt, - userId: userId, - channelRole: channelRole, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedByUserId: pinnedByUserId, - channelCid: channelCid, - i18n: i18n, - restrictedVisibility: restrictedVisibility, - extraData: extraData, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => - (e.readTable(table), $$MessagesTableReferences(db, table, e))) - .toList(), - prefetchHooksCallback: ( - {channelCid = false, - draftMessagesRefs = false, - locationsRefs = false, - reactionsRefs = false}) { - return PrefetchHooks( - db: db, - explicitlyWatchedTables: [ - if (draftMessagesRefs) db.draftMessages, - if (locationsRefs) db.locations, - if (reactionsRefs) db.reactions - ], - addJoins: < - T extends TableManagerState< - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic>>(state) { - if (channelCid) { - state = state.withJoin( - currentTable: table, - currentColumn: table.channelCid, - referencedTable: - $$MessagesTableReferences._channelCidTable(db), - referencedColumn: - $$MessagesTableReferences._channelCidTable(db).cid, - ) as T; - } - - return state; - }, - getPrefetchedDataCallback: (items) async { - return [ - if (draftMessagesRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: $$MessagesTableReferences - ._draftMessagesRefsTable(db), - managerFromTypedResult: (p0) => - $$MessagesTableReferences(db, table, p0) - .draftMessagesRefs, - referencedItemsForCurrentItem: (item, - referencedItems) => - referencedItems.where((e) => e.parentId == item.id), - typedResults: items), - if (locationsRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: - $$MessagesTableReferences._locationsRefsTable(db), - managerFromTypedResult: (p0) => - $$MessagesTableReferences(db, table, p0) - .locationsRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems - .where((e) => e.messageId == item.id), - typedResults: items), - if (reactionsRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: - $$MessagesTableReferences._reactionsRefsTable(db), - managerFromTypedResult: (p0) => - $$MessagesTableReferences(db, table, p0) - .reactionsRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems - .where((e) => e.messageId == item.id), - typedResults: items) - ]; + createFilteringComposer: () => $$MessagesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$MessagesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$MessagesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value messageText = const Value.absent(), + Value> attachments = const Value.absent(), + Value state = const Value.absent(), + Value type = const Value.absent(), + Value> mentionedUsers = const Value.absent(), + Value?> reactionGroups = const Value.absent(), + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value pollId = const Value.absent(), + Value replyCount = const Value.absent(), + Value showInChannel = const Value.absent(), + Value shadowed = const Value.absent(), + Value command = const Value.absent(), + Value localCreatedAt = const Value.absent(), + Value remoteCreatedAt = const Value.absent(), + Value localUpdatedAt = const Value.absent(), + Value remoteUpdatedAt = const Value.absent(), + Value localDeletedAt = const Value.absent(), + Value remoteDeletedAt = const Value.absent(), + Value deletedForMe = const Value.absent(), + Value messageTextUpdatedAt = const Value.absent(), + Value userId = const Value.absent(), + Value channelRole = const Value.absent(), + Value pinned = const Value.absent(), + Value pinnedAt = const Value.absent(), + Value pinExpires = const Value.absent(), + Value pinnedByUserId = const Value.absent(), + Value channelCid = const Value.absent(), + Value?> i18n = const Value.absent(), + Value?> restrictedVisibility = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => MessagesCompanion( + id: id, + messageText: messageText, + attachments: attachments, + state: state, + type: type, + mentionedUsers: mentionedUsers, + reactionGroups: reactionGroups, + parentId: parentId, + quotedMessageId: quotedMessageId, + pollId: pollId, + replyCount: replyCount, + showInChannel: showInChannel, + shadowed: shadowed, + command: command, + localCreatedAt: localCreatedAt, + remoteCreatedAt: remoteCreatedAt, + localUpdatedAt: localUpdatedAt, + remoteUpdatedAt: remoteUpdatedAt, + localDeletedAt: localDeletedAt, + remoteDeletedAt: remoteDeletedAt, + deletedForMe: deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt, + userId: userId, + channelRole: channelRole, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedByUserId: pinnedByUserId, + channelCid: channelCid, + i18n: i18n, + restrictedVisibility: restrictedVisibility, + extraData: extraData, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + Value messageText = const Value.absent(), + required List attachments, + required String state, + Value type = const Value.absent(), + required List mentionedUsers, + Value?> reactionGroups = const Value.absent(), + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value pollId = const Value.absent(), + Value replyCount = const Value.absent(), + Value showInChannel = const Value.absent(), + Value shadowed = const Value.absent(), + Value command = const Value.absent(), + Value localCreatedAt = const Value.absent(), + Value remoteCreatedAt = const Value.absent(), + Value localUpdatedAt = const Value.absent(), + Value remoteUpdatedAt = const Value.absent(), + Value localDeletedAt = const Value.absent(), + Value remoteDeletedAt = const Value.absent(), + Value deletedForMe = const Value.absent(), + Value messageTextUpdatedAt = const Value.absent(), + Value userId = const Value.absent(), + Value channelRole = const Value.absent(), + Value pinned = const Value.absent(), + Value pinnedAt = const Value.absent(), + Value pinExpires = const Value.absent(), + Value pinnedByUserId = const Value.absent(), + required String channelCid, + Value?> i18n = const Value.absent(), + Value?> restrictedVisibility = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => MessagesCompanion.insert( + id: id, + messageText: messageText, + attachments: attachments, + state: state, + type: type, + mentionedUsers: mentionedUsers, + reactionGroups: reactionGroups, + parentId: parentId, + quotedMessageId: quotedMessageId, + pollId: pollId, + replyCount: replyCount, + showInChannel: showInChannel, + shadowed: shadowed, + command: command, + localCreatedAt: localCreatedAt, + remoteCreatedAt: remoteCreatedAt, + localUpdatedAt: localUpdatedAt, + remoteUpdatedAt: remoteUpdatedAt, + localDeletedAt: localDeletedAt, + remoteDeletedAt: remoteDeletedAt, + deletedForMe: deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt, + userId: userId, + channelRole: channelRole, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedByUserId: pinnedByUserId, + channelCid: channelCid, + i18n: i18n, + restrictedVisibility: restrictedVisibility, + extraData: extraData, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$MessagesTableReferences(db, table, e))).toList(), + prefetchHooksCallback: + ({channelCid = false, draftMessagesRefs = false, locationsRefs = false, reactionsRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [ + if (draftMessagesRefs) db.draftMessages, + if (locationsRefs) db.locations, + if (reactionsRefs) db.reactions, + ], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (channelCid) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.channelCid, + referencedTable: $$MessagesTableReferences._channelCidTable(db), + referencedColumn: $$MessagesTableReferences._channelCidTable(db).cid, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return [ + if (draftMessagesRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$MessagesTableReferences._draftMessagesRefsTable(db), + managerFromTypedResult: (p0) => $$MessagesTableReferences(db, table, p0).draftMessagesRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.parentId == item.id), + typedResults: items, + ), + if (locationsRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$MessagesTableReferences._locationsRefsTable(db), + managerFromTypedResult: (p0) => $$MessagesTableReferences(db, table, p0).locationsRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.messageId == item.id), + typedResults: items, + ), + if (reactionsRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$MessagesTableReferences._reactionsRefsTable(db), + managerFromTypedResult: (p0) => $$MessagesTableReferences(db, table, p0).reactionsRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.messageId == item.id), + typedResults: items, + ), + ]; + }, + ); }, - ); - }, - )); + ), + ); } -typedef $$MessagesTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $MessagesTable, - MessageEntity, - $$MessagesTableFilterComposer, - $$MessagesTableOrderingComposer, - $$MessagesTableAnnotationComposer, - $$MessagesTableCreateCompanionBuilder, - $$MessagesTableUpdateCompanionBuilder, - (MessageEntity, $$MessagesTableReferences), - MessageEntity, - PrefetchHooks Function( - {bool channelCid, - bool draftMessagesRefs, - bool locationsRefs, - bool reactionsRefs})>; -typedef $$DraftMessagesTableCreateCompanionBuilder = DraftMessagesCompanion - Function({ - required String id, - Value messageText, - required List attachments, - Value type, - required List mentionedUsers, - Value parentId, - Value quotedMessageId, - Value pollId, - Value showInChannel, - Value command, - Value silent, - Value createdAt, - required String channelCid, - Value?> extraData, - Value rowid, -}); -typedef $$DraftMessagesTableUpdateCompanionBuilder = DraftMessagesCompanion - Function({ - Value id, - Value messageText, - Value> attachments, - Value type, - Value> mentionedUsers, - Value parentId, - Value quotedMessageId, - Value pollId, - Value showInChannel, - Value command, - Value silent, - Value createdAt, - Value channelCid, - Value?> extraData, - Value rowid, -}); - -final class $$DraftMessagesTableReferences extends BaseReferences< - _$DriftChatDatabase, $DraftMessagesTable, DraftMessageEntity> { - $$DraftMessagesTableReferences( - super.$_db, super.$_table, super.$_typedResult); +typedef $$MessagesTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $MessagesTable, + MessageEntity, + $$MessagesTableFilterComposer, + $$MessagesTableOrderingComposer, + $$MessagesTableAnnotationComposer, + $$MessagesTableCreateCompanionBuilder, + $$MessagesTableUpdateCompanionBuilder, + (MessageEntity, $$MessagesTableReferences), + MessageEntity, + PrefetchHooks Function({bool channelCid, bool draftMessagesRefs, bool locationsRefs, bool reactionsRefs}) + >; +typedef $$DraftMessagesTableCreateCompanionBuilder = + DraftMessagesCompanion Function({ + required String id, + Value messageText, + required List attachments, + Value type, + required List mentionedUsers, + Value parentId, + Value quotedMessageId, + Value pollId, + Value showInChannel, + Value command, + Value silent, + Value createdAt, + required String channelCid, + Value?> extraData, + Value rowid, + }); +typedef $$DraftMessagesTableUpdateCompanionBuilder = + DraftMessagesCompanion Function({ + Value id, + Value messageText, + Value> attachments, + Value type, + Value> mentionedUsers, + Value parentId, + Value quotedMessageId, + Value pollId, + Value showInChannel, + Value command, + Value silent, + Value createdAt, + Value channelCid, + Value?> extraData, + Value rowid, + }); + +final class $$DraftMessagesTableReferences + extends BaseReferences<_$DriftChatDatabase, $DraftMessagesTable, DraftMessageEntity> { + $$DraftMessagesTableReferences(super.$_db, super.$_table, super.$_typedResult); static $MessagesTable _parentIdTable(_$DriftChatDatabase db) => - db.messages.createAlias( - $_aliasNameGenerator(db.draftMessages.parentId, db.messages.id)); + db.messages.createAlias($_aliasNameGenerator(db.draftMessages.parentId, db.messages.id)); $$MessagesTableProcessedTableManager? get parentId { final $_column = $_itemColumn('parent_id'); if ($_column == null) return null; - final manager = $$MessagesTableTableManager($_db, $_db.messages) - .filter((f) => f.id.sqlEquals($_column)); + final manager = $$MessagesTableTableManager($_db, $_db.messages).filter((f) => f.id.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_parentIdTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } static $ChannelsTable _channelCidTable(_$DriftChatDatabase db) => - db.channels.createAlias( - $_aliasNameGenerator(db.draftMessages.channelCid, db.channels.cid)); + db.channels.createAlias($_aliasNameGenerator(db.draftMessages.channelCid, db.channels.cid)); $$ChannelsTableProcessedTableManager get channelCid { final $_column = $_itemColumn('channel_cid')!; - final manager = $$ChannelsTableTableManager($_db, $_db.channels) - .filter((f) => f.cid.sqlEquals($_column)); + final manager = $$ChannelsTableTableManager($_db, $_db.channels).filter((f) => f.cid.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_channelCidTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } } -class $$DraftMessagesTableFilterComposer - extends Composer<_$DriftChatDatabase, $DraftMessagesTable> { +class $$DraftMessagesTableFilterComposer extends Composer<_$DriftChatDatabase, $DraftMessagesTable> { $$DraftMessagesTableFilterComposer({ required super.$db, required super.$table, @@ -11239,93 +10940,78 @@ class $$DraftMessagesTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get messageText => $composableBuilder( - column: $table.messageText, builder: (column) => ColumnFilters(column)); + ColumnFilters get messageText => + $composableBuilder(column: $table.messageText, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, List, String> - get attachments => $composableBuilder( - column: $table.attachments, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, List, String> get attachments => + $composableBuilder(column: $table.attachments, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnFilters(column)); + ColumnFilters get type => $composableBuilder(column: $table.type, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, List, String> - get mentionedUsers => $composableBuilder( - column: $table.mentionedUsers, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, List, String> get mentionedUsers => + $composableBuilder(column: $table.mentionedUsers, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get quotedMessageId => $composableBuilder( - column: $table.quotedMessageId, - builder: (column) => ColumnFilters(column)); + ColumnFilters get quotedMessageId => + $composableBuilder(column: $table.quotedMessageId, builder: (column) => ColumnFilters(column)); - ColumnFilters get pollId => $composableBuilder( - column: $table.pollId, builder: (column) => ColumnFilters(column)); + ColumnFilters get pollId => + $composableBuilder(column: $table.pollId, builder: (column) => ColumnFilters(column)); - ColumnFilters get showInChannel => $composableBuilder( - column: $table.showInChannel, builder: (column) => ColumnFilters(column)); + ColumnFilters get showInChannel => + $composableBuilder(column: $table.showInChannel, builder: (column) => ColumnFilters(column)); - ColumnFilters get command => $composableBuilder( - column: $table.command, builder: (column) => ColumnFilters(column)); + ColumnFilters get command => + $composableBuilder(column: $table.command, builder: (column) => ColumnFilters(column)); - ColumnFilters get silent => $composableBuilder( - column: $table.silent, builder: (column) => ColumnFilters(column)); + ColumnFilters get silent => + $composableBuilder(column: $table.silent, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get extraData => $composableBuilder( - column: $table.extraData, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnWithTypeConverterFilters(column)); $$MessagesTableFilterComposer get parentId { final $$MessagesTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.parentId, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableFilterComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.parentId, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableFilterComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } $$ChannelsTableFilterComposer get channelCid { final $$ChannelsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableFilterComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableFilterComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$DraftMessagesTableOrderingComposer - extends Composer<_$DriftChatDatabase, $DraftMessagesTable> { +class $$DraftMessagesTableOrderingComposer extends Composer<_$DriftChatDatabase, $DraftMessagesTable> { $$DraftMessagesTableOrderingComposer({ required super.$db, required super.$table, @@ -11333,88 +11019,79 @@ class $$DraftMessagesTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get messageText => $composableBuilder( - column: $table.messageText, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get messageText => + $composableBuilder(column: $table.messageText, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get attachments => $composableBuilder( - column: $table.attachments, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get attachments => + $composableBuilder(column: $table.attachments, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get type => + $composableBuilder(column: $table.type, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get mentionedUsers => $composableBuilder( - column: $table.mentionedUsers, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get mentionedUsers => + $composableBuilder(column: $table.mentionedUsers, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get quotedMessageId => $composableBuilder( - column: $table.quotedMessageId, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get quotedMessageId => + $composableBuilder(column: $table.quotedMessageId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pollId => $composableBuilder( - column: $table.pollId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pollId => + $composableBuilder(column: $table.pollId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get showInChannel => $composableBuilder( - column: $table.showInChannel, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get showInChannel => + $composableBuilder(column: $table.showInChannel, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get command => $composableBuilder( - column: $table.command, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get command => + $composableBuilder(column: $table.command, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get silent => $composableBuilder( - column: $table.silent, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get silent => + $composableBuilder(column: $table.silent, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnOrderings(column)); $$MessagesTableOrderingComposer get parentId { final $$MessagesTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.parentId, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableOrderingComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.parentId, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableOrderingComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } $$ChannelsTableOrderingComposer get channelCid { final $$ChannelsTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableOrderingComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableOrderingComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$DraftMessagesTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $DraftMessagesTable> { +class $$DraftMessagesTableAnnotationComposer extends Composer<_$DriftChatDatabase, $DraftMessagesTable> { $$DraftMessagesTableAnnotationComposer({ required super.$db, required super.$table, @@ -11422,189 +11099,173 @@ class $$DraftMessagesTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get messageText => $composableBuilder( - column: $table.messageText, builder: (column) => column); + GeneratedColumn get messageText => + $composableBuilder(column: $table.messageText, builder: (column) => column); GeneratedColumnWithTypeConverter, String> get attachments => - $composableBuilder( - column: $table.attachments, builder: (column) => column); + $composableBuilder(column: $table.attachments, builder: (column) => column); - GeneratedColumn get type => - $composableBuilder(column: $table.type, builder: (column) => column); + GeneratedColumn get type => $composableBuilder(column: $table.type, builder: (column) => column); GeneratedColumnWithTypeConverter, String> get mentionedUsers => - $composableBuilder( - column: $table.mentionedUsers, builder: (column) => column); + $composableBuilder(column: $table.mentionedUsers, builder: (column) => column); - GeneratedColumn get quotedMessageId => $composableBuilder( - column: $table.quotedMessageId, builder: (column) => column); + GeneratedColumn get quotedMessageId => + $composableBuilder(column: $table.quotedMessageId, builder: (column) => column); - GeneratedColumn get pollId => - $composableBuilder(column: $table.pollId, builder: (column) => column); + GeneratedColumn get pollId => $composableBuilder(column: $table.pollId, builder: (column) => column); - GeneratedColumn get showInChannel => $composableBuilder( - column: $table.showInChannel, builder: (column) => column); + GeneratedColumn get showInChannel => + $composableBuilder(column: $table.showInChannel, builder: (column) => column); - GeneratedColumn get command => - $composableBuilder(column: $table.command, builder: (column) => column); + GeneratedColumn get command => $composableBuilder(column: $table.command, builder: (column) => column); - GeneratedColumn get silent => - $composableBuilder(column: $table.silent, builder: (column) => column); + GeneratedColumn get silent => $composableBuilder(column: $table.silent, builder: (column) => column); - GeneratedColumn get createdAt => - $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => column); $$MessagesTableAnnotationComposer get parentId { final $$MessagesTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.parentId, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableAnnotationComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.parentId, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableAnnotationComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } $$ChannelsTableAnnotationComposer get channelCid { final $$ChannelsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableAnnotationComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableAnnotationComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$DraftMessagesTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $DraftMessagesTable, - DraftMessageEntity, - $$DraftMessagesTableFilterComposer, - $$DraftMessagesTableOrderingComposer, - $$DraftMessagesTableAnnotationComposer, - $$DraftMessagesTableCreateCompanionBuilder, - $$DraftMessagesTableUpdateCompanionBuilder, - (DraftMessageEntity, $$DraftMessagesTableReferences), - DraftMessageEntity, - PrefetchHooks Function({bool parentId, bool channelCid})> { - $$DraftMessagesTableTableManager( - _$DriftChatDatabase db, $DraftMessagesTable table) - : super(TableManagerState( +class $$DraftMessagesTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $DraftMessagesTable, + DraftMessageEntity, + $$DraftMessagesTableFilterComposer, + $$DraftMessagesTableOrderingComposer, + $$DraftMessagesTableAnnotationComposer, + $$DraftMessagesTableCreateCompanionBuilder, + $$DraftMessagesTableUpdateCompanionBuilder, + (DraftMessageEntity, $$DraftMessagesTableReferences), + DraftMessageEntity, + PrefetchHooks Function({bool parentId, bool channelCid}) + > { + $$DraftMessagesTableTableManager(_$DriftChatDatabase db, $DraftMessagesTable table) + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$DraftMessagesTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$DraftMessagesTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$DraftMessagesTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value id = const Value.absent(), - Value messageText = const Value.absent(), - Value> attachments = const Value.absent(), - Value type = const Value.absent(), - Value> mentionedUsers = const Value.absent(), - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value pollId = const Value.absent(), - Value showInChannel = const Value.absent(), - Value command = const Value.absent(), - Value silent = const Value.absent(), - Value createdAt = const Value.absent(), - Value channelCid = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - DraftMessagesCompanion( - id: id, - messageText: messageText, - attachments: attachments, - type: type, - mentionedUsers: mentionedUsers, - parentId: parentId, - quotedMessageId: quotedMessageId, - pollId: pollId, - showInChannel: showInChannel, - command: command, - silent: silent, - createdAt: createdAt, - channelCid: channelCid, - extraData: extraData, - rowid: rowid, - ), - createCompanionCallback: ({ - required String id, - Value messageText = const Value.absent(), - required List attachments, - Value type = const Value.absent(), - required List mentionedUsers, - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value pollId = const Value.absent(), - Value showInChannel = const Value.absent(), - Value command = const Value.absent(), - Value silent = const Value.absent(), - Value createdAt = const Value.absent(), - required String channelCid, - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - DraftMessagesCompanion.insert( - id: id, - messageText: messageText, - attachments: attachments, - type: type, - mentionedUsers: mentionedUsers, - parentId: parentId, - quotedMessageId: quotedMessageId, - pollId: pollId, - showInChannel: showInChannel, - command: command, - silent: silent, - createdAt: createdAt, - channelCid: channelCid, - extraData: extraData, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => ( - e.readTable(table), - $$DraftMessagesTableReferences(db, table, e) - )) - .toList(), + createFilteringComposer: () => $$DraftMessagesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$DraftMessagesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$DraftMessagesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value messageText = const Value.absent(), + Value> attachments = const Value.absent(), + Value type = const Value.absent(), + Value> mentionedUsers = const Value.absent(), + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value pollId = const Value.absent(), + Value showInChannel = const Value.absent(), + Value command = const Value.absent(), + Value silent = const Value.absent(), + Value createdAt = const Value.absent(), + Value channelCid = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => DraftMessagesCompanion( + id: id, + messageText: messageText, + attachments: attachments, + type: type, + mentionedUsers: mentionedUsers, + parentId: parentId, + quotedMessageId: quotedMessageId, + pollId: pollId, + showInChannel: showInChannel, + command: command, + silent: silent, + createdAt: createdAt, + channelCid: channelCid, + extraData: extraData, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + Value messageText = const Value.absent(), + required List attachments, + Value type = const Value.absent(), + required List mentionedUsers, + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value pollId = const Value.absent(), + Value showInChannel = const Value.absent(), + Value command = const Value.absent(), + Value silent = const Value.absent(), + Value createdAt = const Value.absent(), + required String channelCid, + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => DraftMessagesCompanion.insert( + id: id, + messageText: messageText, + attachments: attachments, + type: type, + mentionedUsers: mentionedUsers, + parentId: parentId, + quotedMessageId: quotedMessageId, + pollId: pollId, + showInChannel: showInChannel, + command: command, + silent: silent, + createdAt: createdAt, + channelCid: channelCid, + extraData: extraData, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$DraftMessagesTableReferences(db, table, e))).toList(), prefetchHooksCallback: ({parentId = false, channelCid = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], - addJoins: < - T extends TableManagerState< + addJoins: + < + T extends TableManagerState< dynamic, dynamic, dynamic, @@ -11615,112 +11276,111 @@ class $$DraftMessagesTableTableManager extends RootTableManager< dynamic, dynamic, dynamic, - dynamic>>(state) { - if (parentId) { - state = state.withJoin( - currentTable: table, - currentColumn: table.parentId, - referencedTable: - $$DraftMessagesTableReferences._parentIdTable(db), - referencedColumn: - $$DraftMessagesTableReferences._parentIdTable(db).id, - ) as T; - } - if (channelCid) { - state = state.withJoin( - currentTable: table, - currentColumn: table.channelCid, - referencedTable: - $$DraftMessagesTableReferences._channelCidTable(db), - referencedColumn: - $$DraftMessagesTableReferences._channelCidTable(db).cid, - ) as T; - } - - return state; - }, + dynamic + > + >(state) { + if (parentId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.parentId, + referencedTable: $$DraftMessagesTableReferences._parentIdTable(db), + referencedColumn: $$DraftMessagesTableReferences._parentIdTable(db).id, + ) + as T; + } + if (channelCid) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.channelCid, + referencedTable: $$DraftMessagesTableReferences._channelCidTable(db), + referencedColumn: $$DraftMessagesTableReferences._channelCidTable(db).cid, + ) + as T; + } + + return state; + }, getPrefetchedDataCallback: (items) async { return []; }, ); }, - )); + ), + ); } -typedef $$DraftMessagesTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $DraftMessagesTable, - DraftMessageEntity, - $$DraftMessagesTableFilterComposer, - $$DraftMessagesTableOrderingComposer, - $$DraftMessagesTableAnnotationComposer, - $$DraftMessagesTableCreateCompanionBuilder, - $$DraftMessagesTableUpdateCompanionBuilder, - (DraftMessageEntity, $$DraftMessagesTableReferences), - DraftMessageEntity, - PrefetchHooks Function({bool parentId, bool channelCid})>; -typedef $$LocationsTableCreateCompanionBuilder = LocationsCompanion Function({ - Value channelCid, - Value messageId, - Value userId, - required double latitude, - required double longitude, - Value createdByDeviceId, - Value endAt, - Value createdAt, - Value updatedAt, - Value rowid, -}); -typedef $$LocationsTableUpdateCompanionBuilder = LocationsCompanion Function({ - Value channelCid, - Value messageId, - Value userId, - Value latitude, - Value longitude, - Value createdByDeviceId, - Value endAt, - Value createdAt, - Value updatedAt, - Value rowid, -}); - -final class $$LocationsTableReferences extends BaseReferences< - _$DriftChatDatabase, $LocationsTable, LocationEntity> { +typedef $$DraftMessagesTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $DraftMessagesTable, + DraftMessageEntity, + $$DraftMessagesTableFilterComposer, + $$DraftMessagesTableOrderingComposer, + $$DraftMessagesTableAnnotationComposer, + $$DraftMessagesTableCreateCompanionBuilder, + $$DraftMessagesTableUpdateCompanionBuilder, + (DraftMessageEntity, $$DraftMessagesTableReferences), + DraftMessageEntity, + PrefetchHooks Function({bool parentId, bool channelCid}) + >; +typedef $$LocationsTableCreateCompanionBuilder = + LocationsCompanion Function({ + Value channelCid, + Value messageId, + Value userId, + required double latitude, + required double longitude, + Value createdByDeviceId, + Value endAt, + Value createdAt, + Value updatedAt, + Value rowid, + }); +typedef $$LocationsTableUpdateCompanionBuilder = + LocationsCompanion Function({ + Value channelCid, + Value messageId, + Value userId, + Value latitude, + Value longitude, + Value createdByDeviceId, + Value endAt, + Value createdAt, + Value updatedAt, + Value rowid, + }); + +final class $$LocationsTableReferences extends BaseReferences<_$DriftChatDatabase, $LocationsTable, LocationEntity> { $$LocationsTableReferences(super.$_db, super.$_table, super.$_typedResult); static $ChannelsTable _channelCidTable(_$DriftChatDatabase db) => - db.channels.createAlias( - $_aliasNameGenerator(db.locations.channelCid, db.channels.cid)); + db.channels.createAlias($_aliasNameGenerator(db.locations.channelCid, db.channels.cid)); $$ChannelsTableProcessedTableManager? get channelCid { final $_column = $_itemColumn('channel_cid'); if ($_column == null) return null; - final manager = $$ChannelsTableTableManager($_db, $_db.channels) - .filter((f) => f.cid.sqlEquals($_column)); + final manager = $$ChannelsTableTableManager($_db, $_db.channels).filter((f) => f.cid.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_channelCidTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } static $MessagesTable _messageIdTable(_$DriftChatDatabase db) => - db.messages.createAlias( - $_aliasNameGenerator(db.locations.messageId, db.messages.id)); + db.messages.createAlias($_aliasNameGenerator(db.locations.messageId, db.messages.id)); $$MessagesTableProcessedTableManager? get messageId { final $_column = $_itemColumn('message_id'); if ($_column == null) return null; - final manager = $$MessagesTableTableManager($_db, $_db.messages) - .filter((f) => f.id.sqlEquals($_column)); + final manager = $$MessagesTableTableManager($_db, $_db.messages).filter((f) => f.id.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_messageIdTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } } -class $$LocationsTableFilterComposer - extends Composer<_$DriftChatDatabase, $LocationsTable> { +class $$LocationsTableFilterComposer extends Composer<_$DriftChatDatabase, $LocationsTable> { $$LocationsTableFilterComposer({ required super.$db, required super.$table, @@ -11728,71 +11388,65 @@ class $$LocationsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnFilters(column)); + ColumnFilters get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnFilters(column)); - ColumnFilters get latitude => $composableBuilder( - column: $table.latitude, builder: (column) => ColumnFilters(column)); + ColumnFilters get latitude => + $composableBuilder(column: $table.latitude, builder: (column) => ColumnFilters(column)); - ColumnFilters get longitude => $composableBuilder( - column: $table.longitude, builder: (column) => ColumnFilters(column)); + ColumnFilters get longitude => + $composableBuilder(column: $table.longitude, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdByDeviceId => $composableBuilder( - column: $table.createdByDeviceId, - builder: (column) => ColumnFilters(column)); + ColumnFilters get createdByDeviceId => + $composableBuilder(column: $table.createdByDeviceId, builder: (column) => ColumnFilters(column)); - ColumnFilters get endAt => $composableBuilder( - column: $table.endAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get endAt => + $composableBuilder(column: $table.endAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); $$ChannelsTableFilterComposer get channelCid { final $$ChannelsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableFilterComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableFilterComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } $$MessagesTableFilterComposer get messageId { final $$MessagesTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.messageId, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableFilterComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.messageId, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableFilterComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$LocationsTableOrderingComposer - extends Composer<_$DriftChatDatabase, $LocationsTable> { +class $$LocationsTableOrderingComposer extends Composer<_$DriftChatDatabase, $LocationsTable> { $$LocationsTableOrderingComposer({ required super.$db, required super.$table, @@ -11800,71 +11454,65 @@ class $$LocationsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get latitude => $composableBuilder( - column: $table.latitude, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get latitude => + $composableBuilder(column: $table.latitude, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get longitude => $composableBuilder( - column: $table.longitude, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get longitude => + $composableBuilder(column: $table.longitude, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdByDeviceId => $composableBuilder( - column: $table.createdByDeviceId, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdByDeviceId => + $composableBuilder(column: $table.createdByDeviceId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get endAt => $composableBuilder( - column: $table.endAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get endAt => + $composableBuilder(column: $table.endAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); $$ChannelsTableOrderingComposer get channelCid { final $$ChannelsTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableOrderingComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableOrderingComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } $$MessagesTableOrderingComposer get messageId { final $$MessagesTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.messageId, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableOrderingComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.messageId, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableOrderingComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$LocationsTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $LocationsTable> { +class $$LocationsTableAnnotationComposer extends Composer<_$DriftChatDatabase, $LocationsTable> { $$LocationsTableAnnotationComposer({ required super.$db, required super.$table, @@ -11872,150 +11520,138 @@ class $$LocationsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get userId => - $composableBuilder(column: $table.userId, builder: (column) => column); + GeneratedColumn get userId => $composableBuilder(column: $table.userId, builder: (column) => column); - GeneratedColumn get latitude => - $composableBuilder(column: $table.latitude, builder: (column) => column); + GeneratedColumn get latitude => $composableBuilder(column: $table.latitude, builder: (column) => column); - GeneratedColumn get longitude => - $composableBuilder(column: $table.longitude, builder: (column) => column); + GeneratedColumn get longitude => $composableBuilder(column: $table.longitude, builder: (column) => column); - GeneratedColumn get createdByDeviceId => $composableBuilder( - column: $table.createdByDeviceId, builder: (column) => column); + GeneratedColumn get createdByDeviceId => + $composableBuilder(column: $table.createdByDeviceId, builder: (column) => column); - GeneratedColumn get endAt => - $composableBuilder(column: $table.endAt, builder: (column) => column); + GeneratedColumn get endAt => $composableBuilder(column: $table.endAt, builder: (column) => column); - GeneratedColumn get createdAt => - $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - GeneratedColumn get updatedAt => - $composableBuilder(column: $table.updatedAt, builder: (column) => column); + GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); $$ChannelsTableAnnotationComposer get channelCid { final $$ChannelsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableAnnotationComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableAnnotationComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } $$MessagesTableAnnotationComposer get messageId { final $$MessagesTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.messageId, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableAnnotationComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.messageId, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableAnnotationComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$LocationsTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $LocationsTable, - LocationEntity, - $$LocationsTableFilterComposer, - $$LocationsTableOrderingComposer, - $$LocationsTableAnnotationComposer, - $$LocationsTableCreateCompanionBuilder, - $$LocationsTableUpdateCompanionBuilder, - (LocationEntity, $$LocationsTableReferences), - LocationEntity, - PrefetchHooks Function({bool channelCid, bool messageId})> { +class $$LocationsTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $LocationsTable, + LocationEntity, + $$LocationsTableFilterComposer, + $$LocationsTableOrderingComposer, + $$LocationsTableAnnotationComposer, + $$LocationsTableCreateCompanionBuilder, + $$LocationsTableUpdateCompanionBuilder, + (LocationEntity, $$LocationsTableReferences), + LocationEntity, + PrefetchHooks Function({bool channelCid, bool messageId}) + > { $$LocationsTableTableManager(_$DriftChatDatabase db, $LocationsTable table) - : super(TableManagerState( + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$LocationsTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$LocationsTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$LocationsTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value channelCid = const Value.absent(), - Value messageId = const Value.absent(), - Value userId = const Value.absent(), - Value latitude = const Value.absent(), - Value longitude = const Value.absent(), - Value createdByDeviceId = const Value.absent(), - Value endAt = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value rowid = const Value.absent(), - }) => - LocationsCompanion( - channelCid: channelCid, - messageId: messageId, - userId: userId, - latitude: latitude, - longitude: longitude, - createdByDeviceId: createdByDeviceId, - endAt: endAt, - createdAt: createdAt, - updatedAt: updatedAt, - rowid: rowid, - ), - createCompanionCallback: ({ - Value channelCid = const Value.absent(), - Value messageId = const Value.absent(), - Value userId = const Value.absent(), - required double latitude, - required double longitude, - Value createdByDeviceId = const Value.absent(), - Value endAt = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value rowid = const Value.absent(), - }) => - LocationsCompanion.insert( - channelCid: channelCid, - messageId: messageId, - userId: userId, - latitude: latitude, - longitude: longitude, - createdByDeviceId: createdByDeviceId, - endAt: endAt, - createdAt: createdAt, - updatedAt: updatedAt, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => ( - e.readTable(table), - $$LocationsTableReferences(db, table, e) - )) - .toList(), + createFilteringComposer: () => $$LocationsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$LocationsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$LocationsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value channelCid = const Value.absent(), + Value messageId = const Value.absent(), + Value userId = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value createdByDeviceId = const Value.absent(), + Value endAt = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value rowid = const Value.absent(), + }) => LocationsCompanion( + channelCid: channelCid, + messageId: messageId, + userId: userId, + latitude: latitude, + longitude: longitude, + createdByDeviceId: createdByDeviceId, + endAt: endAt, + createdAt: createdAt, + updatedAt: updatedAt, + rowid: rowid, + ), + createCompanionCallback: + ({ + Value channelCid = const Value.absent(), + Value messageId = const Value.absent(), + Value userId = const Value.absent(), + required double latitude, + required double longitude, + Value createdByDeviceId = const Value.absent(), + Value endAt = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value rowid = const Value.absent(), + }) => LocationsCompanion.insert( + channelCid: channelCid, + messageId: messageId, + userId: userId, + latitude: latitude, + longitude: longitude, + createdByDeviceId: createdByDeviceId, + endAt: endAt, + createdAt: createdAt, + updatedAt: updatedAt, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$LocationsTableReferences(db, table, e))).toList(), prefetchHooksCallback: ({channelCid = false, messageId = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], - addJoins: < - T extends TableManagerState< + addJoins: + < + T extends TableManagerState< dynamic, dynamic, dynamic, @@ -12026,150 +11662,150 @@ class $$LocationsTableTableManager extends RootTableManager< dynamic, dynamic, dynamic, - dynamic>>(state) { - if (channelCid) { - state = state.withJoin( - currentTable: table, - currentColumn: table.channelCid, - referencedTable: - $$LocationsTableReferences._channelCidTable(db), - referencedColumn: - $$LocationsTableReferences._channelCidTable(db).cid, - ) as T; - } - if (messageId) { - state = state.withJoin( - currentTable: table, - currentColumn: table.messageId, - referencedTable: - $$LocationsTableReferences._messageIdTable(db), - referencedColumn: - $$LocationsTableReferences._messageIdTable(db).id, - ) as T; - } - - return state; - }, + dynamic + > + >(state) { + if (channelCid) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.channelCid, + referencedTable: $$LocationsTableReferences._channelCidTable(db), + referencedColumn: $$LocationsTableReferences._channelCidTable(db).cid, + ) + as T; + } + if (messageId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.messageId, + referencedTable: $$LocationsTableReferences._messageIdTable(db), + referencedColumn: $$LocationsTableReferences._messageIdTable(db).id, + ) + as T; + } + + return state; + }, getPrefetchedDataCallback: (items) async { return []; }, ); }, - )); + ), + ); } -typedef $$LocationsTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $LocationsTable, - LocationEntity, - $$LocationsTableFilterComposer, - $$LocationsTableOrderingComposer, - $$LocationsTableAnnotationComposer, - $$LocationsTableCreateCompanionBuilder, - $$LocationsTableUpdateCompanionBuilder, - (LocationEntity, $$LocationsTableReferences), - LocationEntity, - PrefetchHooks Function({bool channelCid, bool messageId})>; -typedef $$PinnedMessagesTableCreateCompanionBuilder = PinnedMessagesCompanion - Function({ - required String id, - Value messageText, - required List attachments, - required String state, - Value type, - required List mentionedUsers, - Value?> reactionGroups, - Value parentId, - Value quotedMessageId, - Value pollId, - Value replyCount, - Value showInChannel, - Value shadowed, - Value command, - Value localCreatedAt, - Value remoteCreatedAt, - Value localUpdatedAt, - Value remoteUpdatedAt, - Value localDeletedAt, - Value remoteDeletedAt, - Value deletedForMe, - Value messageTextUpdatedAt, - Value userId, - Value channelRole, - Value pinned, - Value pinnedAt, - Value pinExpires, - Value pinnedByUserId, - required String channelCid, - Value?> i18n, - Value?> restrictedVisibility, - Value?> extraData, - Value rowid, -}); -typedef $$PinnedMessagesTableUpdateCompanionBuilder = PinnedMessagesCompanion - Function({ - Value id, - Value messageText, - Value> attachments, - Value state, - Value type, - Value> mentionedUsers, - Value?> reactionGroups, - Value parentId, - Value quotedMessageId, - Value pollId, - Value replyCount, - Value showInChannel, - Value shadowed, - Value command, - Value localCreatedAt, - Value remoteCreatedAt, - Value localUpdatedAt, - Value remoteUpdatedAt, - Value localDeletedAt, - Value remoteDeletedAt, - Value deletedForMe, - Value messageTextUpdatedAt, - Value userId, - Value channelRole, - Value pinned, - Value pinnedAt, - Value pinExpires, - Value pinnedByUserId, - Value channelCid, - Value?> i18n, - Value?> restrictedVisibility, - Value?> extraData, - Value rowid, -}); - -final class $$PinnedMessagesTableReferences extends BaseReferences< - _$DriftChatDatabase, $PinnedMessagesTable, PinnedMessageEntity> { - $$PinnedMessagesTableReferences( - super.$_db, super.$_table, super.$_typedResult); - - static MultiTypedResultKey<$PinnedMessageReactionsTable, - List> _pinnedMessageReactionsRefsTable( - _$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.pinnedMessageReactions, - aliasName: $_aliasNameGenerator( - db.pinnedMessages.id, db.pinnedMessageReactions.messageId)); - - $$PinnedMessageReactionsTableProcessedTableManager - get pinnedMessageReactionsRefs { +typedef $$LocationsTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $LocationsTable, + LocationEntity, + $$LocationsTableFilterComposer, + $$LocationsTableOrderingComposer, + $$LocationsTableAnnotationComposer, + $$LocationsTableCreateCompanionBuilder, + $$LocationsTableUpdateCompanionBuilder, + (LocationEntity, $$LocationsTableReferences), + LocationEntity, + PrefetchHooks Function({bool channelCid, bool messageId}) + >; +typedef $$PinnedMessagesTableCreateCompanionBuilder = + PinnedMessagesCompanion Function({ + required String id, + Value messageText, + required List attachments, + required String state, + Value type, + required List mentionedUsers, + Value?> reactionGroups, + Value parentId, + Value quotedMessageId, + Value pollId, + Value replyCount, + Value showInChannel, + Value shadowed, + Value command, + Value localCreatedAt, + Value remoteCreatedAt, + Value localUpdatedAt, + Value remoteUpdatedAt, + Value localDeletedAt, + Value remoteDeletedAt, + Value deletedForMe, + Value messageTextUpdatedAt, + Value userId, + Value channelRole, + Value pinned, + Value pinnedAt, + Value pinExpires, + Value pinnedByUserId, + required String channelCid, + Value?> i18n, + Value?> restrictedVisibility, + Value?> extraData, + Value rowid, + }); +typedef $$PinnedMessagesTableUpdateCompanionBuilder = + PinnedMessagesCompanion Function({ + Value id, + Value messageText, + Value> attachments, + Value state, + Value type, + Value> mentionedUsers, + Value?> reactionGroups, + Value parentId, + Value quotedMessageId, + Value pollId, + Value replyCount, + Value showInChannel, + Value shadowed, + Value command, + Value localCreatedAt, + Value remoteCreatedAt, + Value localUpdatedAt, + Value remoteUpdatedAt, + Value localDeletedAt, + Value remoteDeletedAt, + Value deletedForMe, + Value messageTextUpdatedAt, + Value userId, + Value channelRole, + Value pinned, + Value pinnedAt, + Value pinExpires, + Value pinnedByUserId, + Value channelCid, + Value?> i18n, + Value?> restrictedVisibility, + Value?> extraData, + Value rowid, + }); + +final class $$PinnedMessagesTableReferences + extends BaseReferences<_$DriftChatDatabase, $PinnedMessagesTable, PinnedMessageEntity> { + $$PinnedMessagesTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$PinnedMessageReactionsTable, List> + _pinnedMessageReactionsRefsTable(_$DriftChatDatabase db) => MultiTypedResultKey.fromTable( + db.pinnedMessageReactions, + aliasName: $_aliasNameGenerator(db.pinnedMessages.id, db.pinnedMessageReactions.messageId), + ); + + $$PinnedMessageReactionsTableProcessedTableManager get pinnedMessageReactionsRefs { final manager = $$PinnedMessageReactionsTableTableManager( - $_db, $_db.pinnedMessageReactions) - .filter((f) => f.messageId.id.sqlEquals($_itemColumn('id')!)); + $_db, + $_db.pinnedMessageReactions, + ).filter((f) => f.messageId.id.sqlEquals($_itemColumn('id')!)); - final cache = - $_typedResult.readTableOrNull(_pinnedMessageReactionsRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + final cache = $_typedResult.readTableOrNull(_pinnedMessageReactionsRefsTable($_db)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } } -class $$PinnedMessagesTableFilterComposer - extends Composer<_$DriftChatDatabase, $PinnedMessagesTable> { +class $$PinnedMessagesTableFilterComposer extends Composer<_$DriftChatDatabase, $PinnedMessagesTable> { $$PinnedMessagesTableFilterComposer({ required super.$db, required super.$table, @@ -12177,152 +11813,124 @@ class $$PinnedMessagesTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get messageText => $composableBuilder( - column: $table.messageText, builder: (column) => ColumnFilters(column)); + ColumnFilters get messageText => + $composableBuilder(column: $table.messageText, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, List, String> - get attachments => $composableBuilder( - column: $table.attachments, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, List, String> get attachments => + $composableBuilder(column: $table.attachments, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get state => $composableBuilder( - column: $table.state, builder: (column) => ColumnFilters(column)); + ColumnFilters get state => + $composableBuilder(column: $table.state, builder: (column) => ColumnFilters(column)); - ColumnFilters get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnFilters(column)); + ColumnFilters get type => $composableBuilder(column: $table.type, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, List, String> - get mentionedUsers => $composableBuilder( - column: $table.mentionedUsers, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, List, String> get mentionedUsers => + $composableBuilder(column: $table.mentionedUsers, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnWithTypeConverterFilters?, - Map, String> - get reactionGroups => $composableBuilder( - column: $table.reactionGroups, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get reactionGroups => + $composableBuilder(column: $table.reactionGroups, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get parentId => $composableBuilder( - column: $table.parentId, builder: (column) => ColumnFilters(column)); + ColumnFilters get parentId => + $composableBuilder(column: $table.parentId, builder: (column) => ColumnFilters(column)); - ColumnFilters get quotedMessageId => $composableBuilder( - column: $table.quotedMessageId, - builder: (column) => ColumnFilters(column)); + ColumnFilters get quotedMessageId => + $composableBuilder(column: $table.quotedMessageId, builder: (column) => ColumnFilters(column)); - ColumnFilters get pollId => $composableBuilder( - column: $table.pollId, builder: (column) => ColumnFilters(column)); + ColumnFilters get pollId => + $composableBuilder(column: $table.pollId, builder: (column) => ColumnFilters(column)); - ColumnFilters get replyCount => $composableBuilder( - column: $table.replyCount, builder: (column) => ColumnFilters(column)); + ColumnFilters get replyCount => + $composableBuilder(column: $table.replyCount, builder: (column) => ColumnFilters(column)); - ColumnFilters get showInChannel => $composableBuilder( - column: $table.showInChannel, builder: (column) => ColumnFilters(column)); + ColumnFilters get showInChannel => + $composableBuilder(column: $table.showInChannel, builder: (column) => ColumnFilters(column)); - ColumnFilters get shadowed => $composableBuilder( - column: $table.shadowed, builder: (column) => ColumnFilters(column)); + ColumnFilters get shadowed => + $composableBuilder(column: $table.shadowed, builder: (column) => ColumnFilters(column)); - ColumnFilters get command => $composableBuilder( - column: $table.command, builder: (column) => ColumnFilters(column)); + ColumnFilters get command => + $composableBuilder(column: $table.command, builder: (column) => ColumnFilters(column)); - ColumnFilters get localCreatedAt => $composableBuilder( - column: $table.localCreatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get localCreatedAt => + $composableBuilder(column: $table.localCreatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get remoteCreatedAt => $composableBuilder( - column: $table.remoteCreatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get remoteCreatedAt => + $composableBuilder(column: $table.remoteCreatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get localUpdatedAt => $composableBuilder( - column: $table.localUpdatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get localUpdatedAt => + $composableBuilder(column: $table.localUpdatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get remoteUpdatedAt => $composableBuilder( - column: $table.remoteUpdatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get remoteUpdatedAt => + $composableBuilder(column: $table.remoteUpdatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get localDeletedAt => $composableBuilder( - column: $table.localDeletedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get localDeletedAt => + $composableBuilder(column: $table.localDeletedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get remoteDeletedAt => $composableBuilder( - column: $table.remoteDeletedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get remoteDeletedAt => + $composableBuilder(column: $table.remoteDeletedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get deletedForMe => $composableBuilder( - column: $table.deletedForMe, builder: (column) => ColumnFilters(column)); + ColumnFilters get deletedForMe => + $composableBuilder(column: $table.deletedForMe, builder: (column) => ColumnFilters(column)); - ColumnFilters get messageTextUpdatedAt => $composableBuilder( - column: $table.messageTextUpdatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get messageTextUpdatedAt => + $composableBuilder(column: $table.messageTextUpdatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnFilters(column)); + ColumnFilters get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnFilters(column)); - ColumnFilters get channelRole => $composableBuilder( - column: $table.channelRole, builder: (column) => ColumnFilters(column)); + ColumnFilters get channelRole => + $composableBuilder(column: $table.channelRole, builder: (column) => ColumnFilters(column)); - ColumnFilters get pinned => $composableBuilder( - column: $table.pinned, builder: (column) => ColumnFilters(column)); + ColumnFilters get pinned => + $composableBuilder(column: $table.pinned, builder: (column) => ColumnFilters(column)); - ColumnFilters get pinnedAt => $composableBuilder( - column: $table.pinnedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get pinnedAt => + $composableBuilder(column: $table.pinnedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get pinExpires => $composableBuilder( - column: $table.pinExpires, builder: (column) => ColumnFilters(column)); + ColumnFilters get pinExpires => + $composableBuilder(column: $table.pinExpires, builder: (column) => ColumnFilters(column)); - ColumnFilters get pinnedByUserId => $composableBuilder( - column: $table.pinnedByUserId, - builder: (column) => ColumnFilters(column)); + ColumnFilters get pinnedByUserId => + $composableBuilder(column: $table.pinnedByUserId, builder: (column) => ColumnFilters(column)); - ColumnFilters get channelCid => $composableBuilder( - column: $table.channelCid, builder: (column) => ColumnFilters(column)); + ColumnFilters get channelCid => + $composableBuilder(column: $table.channelCid, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get i18n => $composableBuilder( - column: $table.i18n, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get i18n => + $composableBuilder(column: $table.i18n, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnWithTypeConverterFilters?, List, String> - get restrictedVisibility => $composableBuilder( - column: $table.restrictedVisibility, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, List, String> get restrictedVisibility => $composableBuilder( + column: $table.restrictedVisibility, + builder: (column) => ColumnWithTypeConverterFilters(column), + ); - ColumnWithTypeConverterFilters?, Map, - String> - get extraData => $composableBuilder( - column: $table.extraData, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnWithTypeConverterFilters(column)); Expression pinnedMessageReactionsRefs( - Expression Function($$PinnedMessageReactionsTableFilterComposer f) - f) { - final $$PinnedMessageReactionsTableFilterComposer composer = - $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.pinnedMessageReactions, - getReferencedColumn: (t) => t.messageId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PinnedMessageReactionsTableFilterComposer( - $db: $db, - $table: $db.pinnedMessageReactions, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + Expression Function($$PinnedMessageReactionsTableFilterComposer f) f, + ) { + final $$PinnedMessageReactionsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.pinnedMessageReactions, + getReferencedColumn: (t) => t.messageId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PinnedMessageReactionsTableFilterComposer( + $db: $db, + $table: $db.pinnedMessageReactions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } } -class $$PinnedMessagesTableOrderingComposer - extends Composer<_$DriftChatDatabase, $PinnedMessagesTable> { +class $$PinnedMessagesTableOrderingComposer extends Composer<_$DriftChatDatabase, $PinnedMessagesTable> { $$PinnedMessagesTableOrderingComposer({ required super.$db, required super.$table, @@ -12330,119 +11938,103 @@ class $$PinnedMessagesTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get messageText => $composableBuilder( - column: $table.messageText, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get messageText => + $composableBuilder(column: $table.messageText, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get attachments => $composableBuilder( - column: $table.attachments, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get attachments => + $composableBuilder(column: $table.attachments, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get state => $composableBuilder( - column: $table.state, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get state => + $composableBuilder(column: $table.state, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get type => + $composableBuilder(column: $table.type, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get mentionedUsers => $composableBuilder( - column: $table.mentionedUsers, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get mentionedUsers => + $composableBuilder(column: $table.mentionedUsers, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get reactionGroups => $composableBuilder( - column: $table.reactionGroups, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get reactionGroups => + $composableBuilder(column: $table.reactionGroups, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get parentId => $composableBuilder( - column: $table.parentId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get parentId => + $composableBuilder(column: $table.parentId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get quotedMessageId => $composableBuilder( - column: $table.quotedMessageId, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get quotedMessageId => + $composableBuilder(column: $table.quotedMessageId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pollId => $composableBuilder( - column: $table.pollId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pollId => + $composableBuilder(column: $table.pollId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get replyCount => $composableBuilder( - column: $table.replyCount, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get replyCount => + $composableBuilder(column: $table.replyCount, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get showInChannel => $composableBuilder( - column: $table.showInChannel, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get showInChannel => + $composableBuilder(column: $table.showInChannel, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get shadowed => $composableBuilder( - column: $table.shadowed, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get shadowed => + $composableBuilder(column: $table.shadowed, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get command => $composableBuilder( - column: $table.command, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get command => + $composableBuilder(column: $table.command, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get localCreatedAt => $composableBuilder( - column: $table.localCreatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get localCreatedAt => + $composableBuilder(column: $table.localCreatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get remoteCreatedAt => $composableBuilder( - column: $table.remoteCreatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get remoteCreatedAt => + $composableBuilder(column: $table.remoteCreatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get localUpdatedAt => $composableBuilder( - column: $table.localUpdatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get localUpdatedAt => + $composableBuilder(column: $table.localUpdatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get remoteUpdatedAt => $composableBuilder( - column: $table.remoteUpdatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get remoteUpdatedAt => + $composableBuilder(column: $table.remoteUpdatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get localDeletedAt => $composableBuilder( - column: $table.localDeletedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get localDeletedAt => + $composableBuilder(column: $table.localDeletedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get remoteDeletedAt => $composableBuilder( - column: $table.remoteDeletedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get remoteDeletedAt => + $composableBuilder(column: $table.remoteDeletedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get deletedForMe => $composableBuilder( - column: $table.deletedForMe, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get deletedForMe => + $composableBuilder(column: $table.deletedForMe, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get messageTextUpdatedAt => $composableBuilder( - column: $table.messageTextUpdatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get messageTextUpdatedAt => + $composableBuilder(column: $table.messageTextUpdatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get channelRole => $composableBuilder( - column: $table.channelRole, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get channelRole => + $composableBuilder(column: $table.channelRole, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pinned => $composableBuilder( - column: $table.pinned, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pinned => + $composableBuilder(column: $table.pinned, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pinnedAt => $composableBuilder( - column: $table.pinnedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pinnedAt => + $composableBuilder(column: $table.pinnedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pinExpires => $composableBuilder( - column: $table.pinExpires, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pinExpires => + $composableBuilder(column: $table.pinExpires, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pinnedByUserId => $composableBuilder( - column: $table.pinnedByUserId, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pinnedByUserId => + $composableBuilder(column: $table.pinnedByUserId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get channelCid => $composableBuilder( - column: $table.channelCid, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get channelCid => + $composableBuilder(column: $table.channelCid, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get i18n => $composableBuilder( - column: $table.i18n, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get i18n => + $composableBuilder(column: $table.i18n, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get restrictedVisibility => $composableBuilder( - column: $table.restrictedVisibility, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get restrictedVisibility => + $composableBuilder(column: $table.restrictedVisibility, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnOrderings(column)); } -class $$PinnedMessagesTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $PinnedMessagesTable> { +class $$PinnedMessagesTableAnnotationComposer extends Composer<_$DriftChatDatabase, $PinnedMessagesTable> { $$PinnedMessagesTableAnnotationComposer({ required super.$db, required super.$table, @@ -12450,406 +12042,376 @@ class $$PinnedMessagesTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get messageText => $composableBuilder( - column: $table.messageText, builder: (column) => column); + GeneratedColumn get messageText => + $composableBuilder(column: $table.messageText, builder: (column) => column); GeneratedColumnWithTypeConverter, String> get attachments => - $composableBuilder( - column: $table.attachments, builder: (column) => column); + $composableBuilder(column: $table.attachments, builder: (column) => column); - GeneratedColumn get state => - $composableBuilder(column: $table.state, builder: (column) => column); + GeneratedColumn get state => $composableBuilder(column: $table.state, builder: (column) => column); - GeneratedColumn get type => - $composableBuilder(column: $table.type, builder: (column) => column); + GeneratedColumn get type => $composableBuilder(column: $table.type, builder: (column) => column); GeneratedColumnWithTypeConverter, String> get mentionedUsers => - $composableBuilder( - column: $table.mentionedUsers, builder: (column) => column); + $composableBuilder(column: $table.mentionedUsers, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get reactionGroups => $composableBuilder( - column: $table.reactionGroups, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get reactionGroups => + $composableBuilder(column: $table.reactionGroups, builder: (column) => column); - GeneratedColumn get parentId => - $composableBuilder(column: $table.parentId, builder: (column) => column); + GeneratedColumn get parentId => $composableBuilder(column: $table.parentId, builder: (column) => column); - GeneratedColumn get quotedMessageId => $composableBuilder( - column: $table.quotedMessageId, builder: (column) => column); + GeneratedColumn get quotedMessageId => + $composableBuilder(column: $table.quotedMessageId, builder: (column) => column); - GeneratedColumn get pollId => - $composableBuilder(column: $table.pollId, builder: (column) => column); + GeneratedColumn get pollId => $composableBuilder(column: $table.pollId, builder: (column) => column); - GeneratedColumn get replyCount => $composableBuilder( - column: $table.replyCount, builder: (column) => column); + GeneratedColumn get replyCount => $composableBuilder(column: $table.replyCount, builder: (column) => column); - GeneratedColumn get showInChannel => $composableBuilder( - column: $table.showInChannel, builder: (column) => column); + GeneratedColumn get showInChannel => + $composableBuilder(column: $table.showInChannel, builder: (column) => column); - GeneratedColumn get shadowed => - $composableBuilder(column: $table.shadowed, builder: (column) => column); + GeneratedColumn get shadowed => $composableBuilder(column: $table.shadowed, builder: (column) => column); - GeneratedColumn get command => - $composableBuilder(column: $table.command, builder: (column) => column); + GeneratedColumn get command => $composableBuilder(column: $table.command, builder: (column) => column); - GeneratedColumn get localCreatedAt => $composableBuilder( - column: $table.localCreatedAt, builder: (column) => column); + GeneratedColumn get localCreatedAt => + $composableBuilder(column: $table.localCreatedAt, builder: (column) => column); - GeneratedColumn get remoteCreatedAt => $composableBuilder( - column: $table.remoteCreatedAt, builder: (column) => column); + GeneratedColumn get remoteCreatedAt => + $composableBuilder(column: $table.remoteCreatedAt, builder: (column) => column); - GeneratedColumn get localUpdatedAt => $composableBuilder( - column: $table.localUpdatedAt, builder: (column) => column); + GeneratedColumn get localUpdatedAt => + $composableBuilder(column: $table.localUpdatedAt, builder: (column) => column); - GeneratedColumn get remoteUpdatedAt => $composableBuilder( - column: $table.remoteUpdatedAt, builder: (column) => column); + GeneratedColumn get remoteUpdatedAt => + $composableBuilder(column: $table.remoteUpdatedAt, builder: (column) => column); - GeneratedColumn get localDeletedAt => $composableBuilder( - column: $table.localDeletedAt, builder: (column) => column); + GeneratedColumn get localDeletedAt => + $composableBuilder(column: $table.localDeletedAt, builder: (column) => column); - GeneratedColumn get remoteDeletedAt => $composableBuilder( - column: $table.remoteDeletedAt, builder: (column) => column); + GeneratedColumn get remoteDeletedAt => + $composableBuilder(column: $table.remoteDeletedAt, builder: (column) => column); - GeneratedColumn get deletedForMe => $composableBuilder( - column: $table.deletedForMe, builder: (column) => column); + GeneratedColumn get deletedForMe => + $composableBuilder(column: $table.deletedForMe, builder: (column) => column); - GeneratedColumn get messageTextUpdatedAt => $composableBuilder( - column: $table.messageTextUpdatedAt, builder: (column) => column); + GeneratedColumn get messageTextUpdatedAt => + $composableBuilder(column: $table.messageTextUpdatedAt, builder: (column) => column); - GeneratedColumn get userId => - $composableBuilder(column: $table.userId, builder: (column) => column); + GeneratedColumn get userId => $composableBuilder(column: $table.userId, builder: (column) => column); - GeneratedColumn get channelRole => $composableBuilder( - column: $table.channelRole, builder: (column) => column); + GeneratedColumn get channelRole => + $composableBuilder(column: $table.channelRole, builder: (column) => column); - GeneratedColumn get pinned => - $composableBuilder(column: $table.pinned, builder: (column) => column); + GeneratedColumn get pinned => $composableBuilder(column: $table.pinned, builder: (column) => column); - GeneratedColumn get pinnedAt => - $composableBuilder(column: $table.pinnedAt, builder: (column) => column); + GeneratedColumn get pinnedAt => $composableBuilder(column: $table.pinnedAt, builder: (column) => column); - GeneratedColumn get pinExpires => $composableBuilder( - column: $table.pinExpires, builder: (column) => column); + GeneratedColumn get pinExpires => + $composableBuilder(column: $table.pinExpires, builder: (column) => column); - GeneratedColumn get pinnedByUserId => $composableBuilder( - column: $table.pinnedByUserId, builder: (column) => column); + GeneratedColumn get pinnedByUserId => + $composableBuilder(column: $table.pinnedByUserId, builder: (column) => column); - GeneratedColumn get channelCid => $composableBuilder( - column: $table.channelCid, builder: (column) => column); + GeneratedColumn get channelCid => $composableBuilder(column: $table.channelCid, builder: (column) => column); GeneratedColumnWithTypeConverter?, String> get i18n => $composableBuilder(column: $table.i18n, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get restrictedVisibility => $composableBuilder( - column: $table.restrictedVisibility, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get restrictedVisibility => + $composableBuilder(column: $table.restrictedVisibility, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => column); Expression pinnedMessageReactionsRefs( - Expression Function($$PinnedMessageReactionsTableAnnotationComposer a) - f) { - final $$PinnedMessageReactionsTableAnnotationComposer composer = - $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.pinnedMessageReactions, - getReferencedColumn: (t) => t.messageId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PinnedMessageReactionsTableAnnotationComposer( - $db: $db, - $table: $db.pinnedMessageReactions, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + Expression Function($$PinnedMessageReactionsTableAnnotationComposer a) f, + ) { + final $$PinnedMessageReactionsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.pinnedMessageReactions, + getReferencedColumn: (t) => t.messageId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PinnedMessageReactionsTableAnnotationComposer( + $db: $db, + $table: $db.pinnedMessageReactions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } } -class $$PinnedMessagesTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $PinnedMessagesTable, - PinnedMessageEntity, - $$PinnedMessagesTableFilterComposer, - $$PinnedMessagesTableOrderingComposer, - $$PinnedMessagesTableAnnotationComposer, - $$PinnedMessagesTableCreateCompanionBuilder, - $$PinnedMessagesTableUpdateCompanionBuilder, - (PinnedMessageEntity, $$PinnedMessagesTableReferences), - PinnedMessageEntity, - PrefetchHooks Function({bool pinnedMessageReactionsRefs})> { - $$PinnedMessagesTableTableManager( - _$DriftChatDatabase db, $PinnedMessagesTable table) - : super(TableManagerState( +class $$PinnedMessagesTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $PinnedMessagesTable, + PinnedMessageEntity, + $$PinnedMessagesTableFilterComposer, + $$PinnedMessagesTableOrderingComposer, + $$PinnedMessagesTableAnnotationComposer, + $$PinnedMessagesTableCreateCompanionBuilder, + $$PinnedMessagesTableUpdateCompanionBuilder, + (PinnedMessageEntity, $$PinnedMessagesTableReferences), + PinnedMessageEntity, + PrefetchHooks Function({bool pinnedMessageReactionsRefs}) + > { + $$PinnedMessagesTableTableManager(_$DriftChatDatabase db, $PinnedMessagesTable table) + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$PinnedMessagesTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$PinnedMessagesTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$PinnedMessagesTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value id = const Value.absent(), - Value messageText = const Value.absent(), - Value> attachments = const Value.absent(), - Value state = const Value.absent(), - Value type = const Value.absent(), - Value> mentionedUsers = const Value.absent(), - Value?> reactionGroups = - const Value.absent(), - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value pollId = const Value.absent(), - Value replyCount = const Value.absent(), - Value showInChannel = const Value.absent(), - Value shadowed = const Value.absent(), - Value command = const Value.absent(), - Value localCreatedAt = const Value.absent(), - Value remoteCreatedAt = const Value.absent(), - Value localUpdatedAt = const Value.absent(), - Value remoteUpdatedAt = const Value.absent(), - Value localDeletedAt = const Value.absent(), - Value remoteDeletedAt = const Value.absent(), - Value deletedForMe = const Value.absent(), - Value messageTextUpdatedAt = const Value.absent(), - Value userId = const Value.absent(), - Value channelRole = const Value.absent(), - Value pinned = const Value.absent(), - Value pinnedAt = const Value.absent(), - Value pinExpires = const Value.absent(), - Value pinnedByUserId = const Value.absent(), - Value channelCid = const Value.absent(), - Value?> i18n = const Value.absent(), - Value?> restrictedVisibility = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - PinnedMessagesCompanion( - id: id, - messageText: messageText, - attachments: attachments, - state: state, - type: type, - mentionedUsers: mentionedUsers, - reactionGroups: reactionGroups, - parentId: parentId, - quotedMessageId: quotedMessageId, - pollId: pollId, - replyCount: replyCount, - showInChannel: showInChannel, - shadowed: shadowed, - command: command, - localCreatedAt: localCreatedAt, - remoteCreatedAt: remoteCreatedAt, - localUpdatedAt: localUpdatedAt, - remoteUpdatedAt: remoteUpdatedAt, - localDeletedAt: localDeletedAt, - remoteDeletedAt: remoteDeletedAt, - deletedForMe: deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt, - userId: userId, - channelRole: channelRole, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedByUserId: pinnedByUserId, - channelCid: channelCid, - i18n: i18n, - restrictedVisibility: restrictedVisibility, - extraData: extraData, - rowid: rowid, - ), - createCompanionCallback: ({ - required String id, - Value messageText = const Value.absent(), - required List attachments, - required String state, - Value type = const Value.absent(), - required List mentionedUsers, - Value?> reactionGroups = - const Value.absent(), - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value pollId = const Value.absent(), - Value replyCount = const Value.absent(), - Value showInChannel = const Value.absent(), - Value shadowed = const Value.absent(), - Value command = const Value.absent(), - Value localCreatedAt = const Value.absent(), - Value remoteCreatedAt = const Value.absent(), - Value localUpdatedAt = const Value.absent(), - Value remoteUpdatedAt = const Value.absent(), - Value localDeletedAt = const Value.absent(), - Value remoteDeletedAt = const Value.absent(), - Value deletedForMe = const Value.absent(), - Value messageTextUpdatedAt = const Value.absent(), - Value userId = const Value.absent(), - Value channelRole = const Value.absent(), - Value pinned = const Value.absent(), - Value pinnedAt = const Value.absent(), - Value pinExpires = const Value.absent(), - Value pinnedByUserId = const Value.absent(), - required String channelCid, - Value?> i18n = const Value.absent(), - Value?> restrictedVisibility = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - PinnedMessagesCompanion.insert( - id: id, - messageText: messageText, - attachments: attachments, - state: state, - type: type, - mentionedUsers: mentionedUsers, - reactionGroups: reactionGroups, - parentId: parentId, - quotedMessageId: quotedMessageId, - pollId: pollId, - replyCount: replyCount, - showInChannel: showInChannel, - shadowed: shadowed, - command: command, - localCreatedAt: localCreatedAt, - remoteCreatedAt: remoteCreatedAt, - localUpdatedAt: localUpdatedAt, - remoteUpdatedAt: remoteUpdatedAt, - localDeletedAt: localDeletedAt, - remoteDeletedAt: remoteDeletedAt, - deletedForMe: deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt, - userId: userId, - channelRole: channelRole, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedByUserId: pinnedByUserId, - channelCid: channelCid, - i18n: i18n, - restrictedVisibility: restrictedVisibility, - extraData: extraData, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => ( - e.readTable(table), - $$PinnedMessagesTableReferences(db, table, e) - )) - .toList(), + createFilteringComposer: () => $$PinnedMessagesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$PinnedMessagesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$PinnedMessagesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value messageText = const Value.absent(), + Value> attachments = const Value.absent(), + Value state = const Value.absent(), + Value type = const Value.absent(), + Value> mentionedUsers = const Value.absent(), + Value?> reactionGroups = const Value.absent(), + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value pollId = const Value.absent(), + Value replyCount = const Value.absent(), + Value showInChannel = const Value.absent(), + Value shadowed = const Value.absent(), + Value command = const Value.absent(), + Value localCreatedAt = const Value.absent(), + Value remoteCreatedAt = const Value.absent(), + Value localUpdatedAt = const Value.absent(), + Value remoteUpdatedAt = const Value.absent(), + Value localDeletedAt = const Value.absent(), + Value remoteDeletedAt = const Value.absent(), + Value deletedForMe = const Value.absent(), + Value messageTextUpdatedAt = const Value.absent(), + Value userId = const Value.absent(), + Value channelRole = const Value.absent(), + Value pinned = const Value.absent(), + Value pinnedAt = const Value.absent(), + Value pinExpires = const Value.absent(), + Value pinnedByUserId = const Value.absent(), + Value channelCid = const Value.absent(), + Value?> i18n = const Value.absent(), + Value?> restrictedVisibility = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => PinnedMessagesCompanion( + id: id, + messageText: messageText, + attachments: attachments, + state: state, + type: type, + mentionedUsers: mentionedUsers, + reactionGroups: reactionGroups, + parentId: parentId, + quotedMessageId: quotedMessageId, + pollId: pollId, + replyCount: replyCount, + showInChannel: showInChannel, + shadowed: shadowed, + command: command, + localCreatedAt: localCreatedAt, + remoteCreatedAt: remoteCreatedAt, + localUpdatedAt: localUpdatedAt, + remoteUpdatedAt: remoteUpdatedAt, + localDeletedAt: localDeletedAt, + remoteDeletedAt: remoteDeletedAt, + deletedForMe: deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt, + userId: userId, + channelRole: channelRole, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedByUserId: pinnedByUserId, + channelCid: channelCid, + i18n: i18n, + restrictedVisibility: restrictedVisibility, + extraData: extraData, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + Value messageText = const Value.absent(), + required List attachments, + required String state, + Value type = const Value.absent(), + required List mentionedUsers, + Value?> reactionGroups = const Value.absent(), + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value pollId = const Value.absent(), + Value replyCount = const Value.absent(), + Value showInChannel = const Value.absent(), + Value shadowed = const Value.absent(), + Value command = const Value.absent(), + Value localCreatedAt = const Value.absent(), + Value remoteCreatedAt = const Value.absent(), + Value localUpdatedAt = const Value.absent(), + Value remoteUpdatedAt = const Value.absent(), + Value localDeletedAt = const Value.absent(), + Value remoteDeletedAt = const Value.absent(), + Value deletedForMe = const Value.absent(), + Value messageTextUpdatedAt = const Value.absent(), + Value userId = const Value.absent(), + Value channelRole = const Value.absent(), + Value pinned = const Value.absent(), + Value pinnedAt = const Value.absent(), + Value pinExpires = const Value.absent(), + Value pinnedByUserId = const Value.absent(), + required String channelCid, + Value?> i18n = const Value.absent(), + Value?> restrictedVisibility = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => PinnedMessagesCompanion.insert( + id: id, + messageText: messageText, + attachments: attachments, + state: state, + type: type, + mentionedUsers: mentionedUsers, + reactionGroups: reactionGroups, + parentId: parentId, + quotedMessageId: quotedMessageId, + pollId: pollId, + replyCount: replyCount, + showInChannel: showInChannel, + shadowed: shadowed, + command: command, + localCreatedAt: localCreatedAt, + remoteCreatedAt: remoteCreatedAt, + localUpdatedAt: localUpdatedAt, + remoteUpdatedAt: remoteUpdatedAt, + localDeletedAt: localDeletedAt, + remoteDeletedAt: remoteDeletedAt, + deletedForMe: deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt, + userId: userId, + channelRole: channelRole, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedByUserId: pinnedByUserId, + channelCid: channelCid, + i18n: i18n, + restrictedVisibility: restrictedVisibility, + extraData: extraData, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$PinnedMessagesTableReferences(db, table, e))).toList(), prefetchHooksCallback: ({pinnedMessageReactionsRefs = false}) { return PrefetchHooks( db: db, - explicitlyWatchedTables: [ - if (pinnedMessageReactionsRefs) db.pinnedMessageReactions - ], + explicitlyWatchedTables: [if (pinnedMessageReactionsRefs) db.pinnedMessageReactions], addJoins: null, getPrefetchedDataCallback: (items) async { return [ if (pinnedMessageReactionsRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: $$PinnedMessagesTableReferences - ._pinnedMessageReactionsRefsTable(db), - managerFromTypedResult: (p0) => - $$PinnedMessagesTableReferences(db, table, p0) - .pinnedMessageReactionsRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems - .where((e) => e.messageId == item.id), - typedResults: items) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$PinnedMessagesTableReferences._pinnedMessageReactionsRefsTable(db), + managerFromTypedResult: (p0) => + $$PinnedMessagesTableReferences(db, table, p0).pinnedMessageReactionsRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.messageId == item.id), + typedResults: items, + ), ]; }, ); }, - )); + ), + ); } -typedef $$PinnedMessagesTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $PinnedMessagesTable, - PinnedMessageEntity, - $$PinnedMessagesTableFilterComposer, - $$PinnedMessagesTableOrderingComposer, - $$PinnedMessagesTableAnnotationComposer, - $$PinnedMessagesTableCreateCompanionBuilder, - $$PinnedMessagesTableUpdateCompanionBuilder, - (PinnedMessageEntity, $$PinnedMessagesTableReferences), - PinnedMessageEntity, - PrefetchHooks Function({bool pinnedMessageReactionsRefs})>; -typedef $$PollsTableCreateCompanionBuilder = PollsCompanion Function({ - required String id, - required String name, - Value description, - required List options, - Value votingVisibility, - Value enforceUniqueVote, - Value maxVotesAllowed, - Value allowUserSuggestedOptions, - Value allowAnswers, - Value isClosed, - Value answersCount, - required Map voteCountsByOption, - Value voteCount, - Value createdById, - Value createdAt, - Value updatedAt, - Value?> extraData, - Value rowid, -}); -typedef $$PollsTableUpdateCompanionBuilder = PollsCompanion Function({ - Value id, - Value name, - Value description, - Value> options, - Value votingVisibility, - Value enforceUniqueVote, - Value maxVotesAllowed, - Value allowUserSuggestedOptions, - Value allowAnswers, - Value isClosed, - Value answersCount, - Value> voteCountsByOption, - Value voteCount, - Value createdById, - Value createdAt, - Value updatedAt, - Value?> extraData, - Value rowid, -}); - -final class $$PollsTableReferences - extends BaseReferences<_$DriftChatDatabase, $PollsTable, PollEntity> { +typedef $$PinnedMessagesTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $PinnedMessagesTable, + PinnedMessageEntity, + $$PinnedMessagesTableFilterComposer, + $$PinnedMessagesTableOrderingComposer, + $$PinnedMessagesTableAnnotationComposer, + $$PinnedMessagesTableCreateCompanionBuilder, + $$PinnedMessagesTableUpdateCompanionBuilder, + (PinnedMessageEntity, $$PinnedMessagesTableReferences), + PinnedMessageEntity, + PrefetchHooks Function({bool pinnedMessageReactionsRefs}) + >; +typedef $$PollsTableCreateCompanionBuilder = + PollsCompanion Function({ + required String id, + required String name, + Value description, + required List options, + Value votingVisibility, + Value enforceUniqueVote, + Value maxVotesAllowed, + Value allowUserSuggestedOptions, + Value allowAnswers, + Value isClosed, + Value answersCount, + required Map voteCountsByOption, + Value voteCount, + Value createdById, + Value createdAt, + Value updatedAt, + Value?> extraData, + Value rowid, + }); +typedef $$PollsTableUpdateCompanionBuilder = + PollsCompanion Function({ + Value id, + Value name, + Value description, + Value> options, + Value votingVisibility, + Value enforceUniqueVote, + Value maxVotesAllowed, + Value allowUserSuggestedOptions, + Value allowAnswers, + Value isClosed, + Value answersCount, + Value> voteCountsByOption, + Value voteCount, + Value createdById, + Value createdAt, + Value updatedAt, + Value?> extraData, + Value rowid, + }); + +final class $$PollsTableReferences extends BaseReferences<_$DriftChatDatabase, $PollsTable, PollEntity> { $$PollsTableReferences(super.$_db, super.$_table, super.$_typedResult); - static MultiTypedResultKey<$PollVotesTable, List> - _pollVotesRefsTable(_$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.pollVotes, - aliasName: - $_aliasNameGenerator(db.polls.id, db.pollVotes.pollId)); + static MultiTypedResultKey<$PollVotesTable, List> _pollVotesRefsTable(_$DriftChatDatabase db) => + MultiTypedResultKey.fromTable(db.pollVotes, aliasName: $_aliasNameGenerator(db.polls.id, db.pollVotes.pollId)); $$PollVotesTableProcessedTableManager get pollVotesRefs { - final manager = $$PollVotesTableTableManager($_db, $_db.pollVotes) - .filter((f) => f.pollId.id.sqlEquals($_itemColumn('id')!)); + final manager = $$PollVotesTableTableManager( + $_db, + $_db.pollVotes, + ).filter((f) => f.pollId.id.sqlEquals($_itemColumn('id')!)); final cache = $_typedResult.readTableOrNull(_pollVotesRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } } -class $$PollsTableFilterComposer - extends Composer<_$DriftChatDatabase, $PollsTable> { +class $$PollsTableFilterComposer extends Composer<_$DriftChatDatabase, $PollsTable> { $$PollsTableFilterComposer({ required super.$db, required super.$table, @@ -12857,93 +12419,78 @@ class $$PollsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get name => $composableBuilder( - column: $table.name, builder: (column) => ColumnFilters(column)); + ColumnFilters get name => $composableBuilder(column: $table.name, builder: (column) => ColumnFilters(column)); - ColumnFilters get description => $composableBuilder( - column: $table.description, builder: (column) => ColumnFilters(column)); + ColumnFilters get description => + $composableBuilder(column: $table.description, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, List, String> - get options => $composableBuilder( - column: $table.options, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, List, String> get options => + $composableBuilder(column: $table.options, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnWithTypeConverterFilters - get votingVisibility => $composableBuilder( - column: $table.votingVisibility, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters get votingVisibility => + $composableBuilder(column: $table.votingVisibility, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get enforceUniqueVote => $composableBuilder( - column: $table.enforceUniqueVote, - builder: (column) => ColumnFilters(column)); + ColumnFilters get enforceUniqueVote => + $composableBuilder(column: $table.enforceUniqueVote, builder: (column) => ColumnFilters(column)); - ColumnFilters get maxVotesAllowed => $composableBuilder( - column: $table.maxVotesAllowed, - builder: (column) => ColumnFilters(column)); + ColumnFilters get maxVotesAllowed => + $composableBuilder(column: $table.maxVotesAllowed, builder: (column) => ColumnFilters(column)); - ColumnFilters get allowUserSuggestedOptions => $composableBuilder( - column: $table.allowUserSuggestedOptions, - builder: (column) => ColumnFilters(column)); + ColumnFilters get allowUserSuggestedOptions => + $composableBuilder(column: $table.allowUserSuggestedOptions, builder: (column) => ColumnFilters(column)); - ColumnFilters get allowAnswers => $composableBuilder( - column: $table.allowAnswers, builder: (column) => ColumnFilters(column)); + ColumnFilters get allowAnswers => + $composableBuilder(column: $table.allowAnswers, builder: (column) => ColumnFilters(column)); - ColumnFilters get isClosed => $composableBuilder( - column: $table.isClosed, builder: (column) => ColumnFilters(column)); + ColumnFilters get isClosed => + $composableBuilder(column: $table.isClosed, builder: (column) => ColumnFilters(column)); - ColumnFilters get answersCount => $composableBuilder( - column: $table.answersCount, builder: (column) => ColumnFilters(column)); + ColumnFilters get answersCount => + $composableBuilder(column: $table.answersCount, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, Map, String> - get voteCountsByOption => $composableBuilder( - column: $table.voteCountsByOption, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, Map, String> get voteCountsByOption => + $composableBuilder( + column: $table.voteCountsByOption, + builder: (column) => ColumnWithTypeConverterFilters(column), + ); - ColumnFilters get voteCount => $composableBuilder( - column: $table.voteCount, builder: (column) => ColumnFilters(column)); + ColumnFilters get voteCount => + $composableBuilder(column: $table.voteCount, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdById => $composableBuilder( - column: $table.createdById, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdById => + $composableBuilder(column: $table.createdById, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get extraData => $composableBuilder( - column: $table.extraData, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnWithTypeConverterFilters(column)); - Expression pollVotesRefs( - Expression Function($$PollVotesTableFilterComposer f) f) { + Expression pollVotesRefs(Expression Function($$PollVotesTableFilterComposer f) f) { final $$PollVotesTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.pollVotes, - getReferencedColumn: (t) => t.pollId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PollVotesTableFilterComposer( - $db: $db, - $table: $db.pollVotes, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.pollVotes, + getReferencedColumn: (t) => t.pollId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PollVotesTableFilterComposer( + $db: $db, + $table: $db.pollVotes, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } } -class $$PollsTableOrderingComposer - extends Composer<_$DriftChatDatabase, $PollsTable> { +class $$PollsTableOrderingComposer extends Composer<_$DriftChatDatabase, $PollsTable> { $$PollsTableOrderingComposer({ required super.$db, required super.$table, @@ -12951,67 +12498,58 @@ class $$PollsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get name => $composableBuilder( - column: $table.name, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get name => + $composableBuilder(column: $table.name, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get description => $composableBuilder( - column: $table.description, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get description => + $composableBuilder(column: $table.description, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get options => $composableBuilder( - column: $table.options, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get options => + $composableBuilder(column: $table.options, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get votingVisibility => $composableBuilder( - column: $table.votingVisibility, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get votingVisibility => + $composableBuilder(column: $table.votingVisibility, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get enforceUniqueVote => $composableBuilder( - column: $table.enforceUniqueVote, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get enforceUniqueVote => + $composableBuilder(column: $table.enforceUniqueVote, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get maxVotesAllowed => $composableBuilder( - column: $table.maxVotesAllowed, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get maxVotesAllowed => + $composableBuilder(column: $table.maxVotesAllowed, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get allowUserSuggestedOptions => $composableBuilder( - column: $table.allowUserSuggestedOptions, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get allowUserSuggestedOptions => + $composableBuilder(column: $table.allowUserSuggestedOptions, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get allowAnswers => $composableBuilder( - column: $table.allowAnswers, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get allowAnswers => + $composableBuilder(column: $table.allowAnswers, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get isClosed => $composableBuilder( - column: $table.isClosed, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get isClosed => + $composableBuilder(column: $table.isClosed, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get answersCount => $composableBuilder( - column: $table.answersCount, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get answersCount => + $composableBuilder(column: $table.answersCount, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get voteCountsByOption => $composableBuilder( - column: $table.voteCountsByOption, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get voteCountsByOption => + $composableBuilder(column: $table.voteCountsByOption, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get voteCount => $composableBuilder( - column: $table.voteCount, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get voteCount => + $composableBuilder(column: $table.voteCount, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdById => $composableBuilder( - column: $table.createdById, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdById => + $composableBuilder(column: $table.createdById, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnOrderings(column)); } -class $$PollsTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $PollsTable> { +class $$PollsTableAnnotationComposer extends Composer<_$DriftChatDatabase, $PollsTable> { $$PollsTableAnnotationComposer({ required super.$db, required super.$table, @@ -13019,188 +12557,174 @@ class $$PollsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get name => - $composableBuilder(column: $table.name, builder: (column) => column); + GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); - GeneratedColumn get description => $composableBuilder( - column: $table.description, builder: (column) => column); + GeneratedColumn get description => + $composableBuilder(column: $table.description, builder: (column) => column); GeneratedColumnWithTypeConverter, String> get options => $composableBuilder(column: $table.options, builder: (column) => column); - GeneratedColumnWithTypeConverter - get votingVisibility => $composableBuilder( - column: $table.votingVisibility, builder: (column) => column); + GeneratedColumnWithTypeConverter get votingVisibility => + $composableBuilder(column: $table.votingVisibility, builder: (column) => column); - GeneratedColumn get enforceUniqueVote => $composableBuilder( - column: $table.enforceUniqueVote, builder: (column) => column); + GeneratedColumn get enforceUniqueVote => + $composableBuilder(column: $table.enforceUniqueVote, builder: (column) => column); - GeneratedColumn get maxVotesAllowed => $composableBuilder( - column: $table.maxVotesAllowed, builder: (column) => column); + GeneratedColumn get maxVotesAllowed => + $composableBuilder(column: $table.maxVotesAllowed, builder: (column) => column); - GeneratedColumn get allowUserSuggestedOptions => $composableBuilder( - column: $table.allowUserSuggestedOptions, builder: (column) => column); + GeneratedColumn get allowUserSuggestedOptions => + $composableBuilder(column: $table.allowUserSuggestedOptions, builder: (column) => column); - GeneratedColumn get allowAnswers => $composableBuilder( - column: $table.allowAnswers, builder: (column) => column); + GeneratedColumn get allowAnswers => + $composableBuilder(column: $table.allowAnswers, builder: (column) => column); - GeneratedColumn get isClosed => - $composableBuilder(column: $table.isClosed, builder: (column) => column); + GeneratedColumn get isClosed => $composableBuilder(column: $table.isClosed, builder: (column) => column); - GeneratedColumn get answersCount => $composableBuilder( - column: $table.answersCount, builder: (column) => column); + GeneratedColumn get answersCount => $composableBuilder(column: $table.answersCount, builder: (column) => column); - GeneratedColumnWithTypeConverter, String> - get voteCountsByOption => $composableBuilder( - column: $table.voteCountsByOption, builder: (column) => column); + GeneratedColumnWithTypeConverter, String> get voteCountsByOption => + $composableBuilder(column: $table.voteCountsByOption, builder: (column) => column); - GeneratedColumn get voteCount => - $composableBuilder(column: $table.voteCount, builder: (column) => column); + GeneratedColumn get voteCount => $composableBuilder(column: $table.voteCount, builder: (column) => column); - GeneratedColumn get createdById => $composableBuilder( - column: $table.createdById, builder: (column) => column); + GeneratedColumn get createdById => + $composableBuilder(column: $table.createdById, builder: (column) => column); - GeneratedColumn get createdAt => - $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - GeneratedColumn get updatedAt => - $composableBuilder(column: $table.updatedAt, builder: (column) => column); + GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => column); - Expression pollVotesRefs( - Expression Function($$PollVotesTableAnnotationComposer a) f) { + Expression pollVotesRefs(Expression Function($$PollVotesTableAnnotationComposer a) f) { final $$PollVotesTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.pollVotes, - getReferencedColumn: (t) => t.pollId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PollVotesTableAnnotationComposer( - $db: $db, - $table: $db.pollVotes, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.pollVotes, + getReferencedColumn: (t) => t.pollId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PollVotesTableAnnotationComposer( + $db: $db, + $table: $db.pollVotes, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } } -class $$PollsTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $PollsTable, - PollEntity, - $$PollsTableFilterComposer, - $$PollsTableOrderingComposer, - $$PollsTableAnnotationComposer, - $$PollsTableCreateCompanionBuilder, - $$PollsTableUpdateCompanionBuilder, - (PollEntity, $$PollsTableReferences), - PollEntity, - PrefetchHooks Function({bool pollVotesRefs})> { +class $$PollsTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $PollsTable, + PollEntity, + $$PollsTableFilterComposer, + $$PollsTableOrderingComposer, + $$PollsTableAnnotationComposer, + $$PollsTableCreateCompanionBuilder, + $$PollsTableUpdateCompanionBuilder, + (PollEntity, $$PollsTableReferences), + PollEntity, + PrefetchHooks Function({bool pollVotesRefs}) + > { $$PollsTableTableManager(_$DriftChatDatabase db, $PollsTable table) - : super(TableManagerState( + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$PollsTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$PollsTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$PollsTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value id = const Value.absent(), - Value name = const Value.absent(), - Value description = const Value.absent(), - Value> options = const Value.absent(), - Value votingVisibility = const Value.absent(), - Value enforceUniqueVote = const Value.absent(), - Value maxVotesAllowed = const Value.absent(), - Value allowUserSuggestedOptions = const Value.absent(), - Value allowAnswers = const Value.absent(), - Value isClosed = const Value.absent(), - Value answersCount = const Value.absent(), - Value> voteCountsByOption = const Value.absent(), - Value voteCount = const Value.absent(), - Value createdById = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - PollsCompanion( - id: id, - name: name, - description: description, - options: options, - votingVisibility: votingVisibility, - enforceUniqueVote: enforceUniqueVote, - maxVotesAllowed: maxVotesAllowed, - allowUserSuggestedOptions: allowUserSuggestedOptions, - allowAnswers: allowAnswers, - isClosed: isClosed, - answersCount: answersCount, - voteCountsByOption: voteCountsByOption, - voteCount: voteCount, - createdById: createdById, - createdAt: createdAt, - updatedAt: updatedAt, - extraData: extraData, - rowid: rowid, - ), - createCompanionCallback: ({ - required String id, - required String name, - Value description = const Value.absent(), - required List options, - Value votingVisibility = const Value.absent(), - Value enforceUniqueVote = const Value.absent(), - Value maxVotesAllowed = const Value.absent(), - Value allowUserSuggestedOptions = const Value.absent(), - Value allowAnswers = const Value.absent(), - Value isClosed = const Value.absent(), - Value answersCount = const Value.absent(), - required Map voteCountsByOption, - Value voteCount = const Value.absent(), - Value createdById = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - PollsCompanion.insert( - id: id, - name: name, - description: description, - options: options, - votingVisibility: votingVisibility, - enforceUniqueVote: enforceUniqueVote, - maxVotesAllowed: maxVotesAllowed, - allowUserSuggestedOptions: allowUserSuggestedOptions, - allowAnswers: allowAnswers, - isClosed: isClosed, - answersCount: answersCount, - voteCountsByOption: voteCountsByOption, - voteCount: voteCount, - createdById: createdById, - createdAt: createdAt, - updatedAt: updatedAt, - extraData: extraData, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => - (e.readTable(table), $$PollsTableReferences(db, table, e))) - .toList(), + createFilteringComposer: () => $$PollsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$PollsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$PollsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value description = const Value.absent(), + Value> options = const Value.absent(), + Value votingVisibility = const Value.absent(), + Value enforceUniqueVote = const Value.absent(), + Value maxVotesAllowed = const Value.absent(), + Value allowUserSuggestedOptions = const Value.absent(), + Value allowAnswers = const Value.absent(), + Value isClosed = const Value.absent(), + Value answersCount = const Value.absent(), + Value> voteCountsByOption = const Value.absent(), + Value voteCount = const Value.absent(), + Value createdById = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => PollsCompanion( + id: id, + name: name, + description: description, + options: options, + votingVisibility: votingVisibility, + enforceUniqueVote: enforceUniqueVote, + maxVotesAllowed: maxVotesAllowed, + allowUserSuggestedOptions: allowUserSuggestedOptions, + allowAnswers: allowAnswers, + isClosed: isClosed, + answersCount: answersCount, + voteCountsByOption: voteCountsByOption, + voteCount: voteCount, + createdById: createdById, + createdAt: createdAt, + updatedAt: updatedAt, + extraData: extraData, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String name, + Value description = const Value.absent(), + required List options, + Value votingVisibility = const Value.absent(), + Value enforceUniqueVote = const Value.absent(), + Value maxVotesAllowed = const Value.absent(), + Value allowUserSuggestedOptions = const Value.absent(), + Value allowAnswers = const Value.absent(), + Value isClosed = const Value.absent(), + Value answersCount = const Value.absent(), + required Map voteCountsByOption, + Value voteCount = const Value.absent(), + Value createdById = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => PollsCompanion.insert( + id: id, + name: name, + description: description, + options: options, + votingVisibility: votingVisibility, + enforceUniqueVote: enforceUniqueVote, + maxVotesAllowed: maxVotesAllowed, + allowUserSuggestedOptions: allowUserSuggestedOptions, + allowAnswers: allowAnswers, + isClosed: isClosed, + answersCount: answersCount, + voteCountsByOption: voteCountsByOption, + voteCount: voteCount, + createdById: createdById, + createdAt: createdAt, + updatedAt: updatedAt, + extraData: extraData, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$PollsTableReferences(db, table, e))).toList(), prefetchHooksCallback: ({pollVotesRefs = false}) { return PrefetchHooks( db: db, @@ -13209,78 +12733,76 @@ class $$PollsTableTableManager extends RootTableManager< getPrefetchedDataCallback: (items) async { return [ if (pollVotesRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: - $$PollsTableReferences._pollVotesRefsTable(db), - managerFromTypedResult: (p0) => - $$PollsTableReferences(db, table, p0).pollVotesRefs, - referencedItemsForCurrentItem: (item, - referencedItems) => - referencedItems.where((e) => e.pollId == item.id), - typedResults: items) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$PollsTableReferences._pollVotesRefsTable(db), + managerFromTypedResult: (p0) => $$PollsTableReferences(db, table, p0).pollVotesRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.pollId == item.id), + typedResults: items, + ), ]; }, ); }, - )); + ), + ); } -typedef $$PollsTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $PollsTable, - PollEntity, - $$PollsTableFilterComposer, - $$PollsTableOrderingComposer, - $$PollsTableAnnotationComposer, - $$PollsTableCreateCompanionBuilder, - $$PollsTableUpdateCompanionBuilder, - (PollEntity, $$PollsTableReferences), - PollEntity, - PrefetchHooks Function({bool pollVotesRefs})>; -typedef $$PollVotesTableCreateCompanionBuilder = PollVotesCompanion Function({ - Value id, - Value pollId, - Value optionId, - Value answerText, - Value createdAt, - Value updatedAt, - Value userId, - Value rowid, -}); -typedef $$PollVotesTableUpdateCompanionBuilder = PollVotesCompanion Function({ - Value id, - Value pollId, - Value optionId, - Value answerText, - Value createdAt, - Value updatedAt, - Value userId, - Value rowid, -}); - -final class $$PollVotesTableReferences extends BaseReferences< - _$DriftChatDatabase, $PollVotesTable, PollVoteEntity> { +typedef $$PollsTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $PollsTable, + PollEntity, + $$PollsTableFilterComposer, + $$PollsTableOrderingComposer, + $$PollsTableAnnotationComposer, + $$PollsTableCreateCompanionBuilder, + $$PollsTableUpdateCompanionBuilder, + (PollEntity, $$PollsTableReferences), + PollEntity, + PrefetchHooks Function({bool pollVotesRefs}) + >; +typedef $$PollVotesTableCreateCompanionBuilder = + PollVotesCompanion Function({ + Value id, + Value pollId, + Value optionId, + Value answerText, + Value createdAt, + Value updatedAt, + Value userId, + Value rowid, + }); +typedef $$PollVotesTableUpdateCompanionBuilder = + PollVotesCompanion Function({ + Value id, + Value pollId, + Value optionId, + Value answerText, + Value createdAt, + Value updatedAt, + Value userId, + Value rowid, + }); + +final class $$PollVotesTableReferences extends BaseReferences<_$DriftChatDatabase, $PollVotesTable, PollVoteEntity> { $$PollVotesTableReferences(super.$_db, super.$_table, super.$_typedResult); - static $PollsTable _pollIdTable(_$DriftChatDatabase db) => db.polls - .createAlias($_aliasNameGenerator(db.pollVotes.pollId, db.polls.id)); + static $PollsTable _pollIdTable(_$DriftChatDatabase db) => + db.polls.createAlias($_aliasNameGenerator(db.pollVotes.pollId, db.polls.id)); $$PollsTableProcessedTableManager? get pollId { final $_column = $_itemColumn('poll_id'); if ($_column == null) return null; - final manager = $$PollsTableTableManager($_db, $_db.polls) - .filter((f) => f.id.sqlEquals($_column)); + final manager = $$PollsTableTableManager($_db, $_db.polls).filter((f) => f.id.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_pollIdTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } } -class $$PollVotesTableFilterComposer - extends Composer<_$DriftChatDatabase, $PollVotesTable> { +class $$PollVotesTableFilterComposer extends Composer<_$DriftChatDatabase, $PollVotesTable> { $$PollVotesTableFilterComposer({ required super.$db, required super.$table, @@ -13288,47 +12810,43 @@ class $$PollVotesTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get optionId => $composableBuilder( - column: $table.optionId, builder: (column) => ColumnFilters(column)); + ColumnFilters get optionId => + $composableBuilder(column: $table.optionId, builder: (column) => ColumnFilters(column)); - ColumnFilters get answerText => $composableBuilder( - column: $table.answerText, builder: (column) => ColumnFilters(column)); + ColumnFilters get answerText => + $composableBuilder(column: $table.answerText, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnFilters(column)); + ColumnFilters get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnFilters(column)); $$PollsTableFilterComposer get pollId { final $$PollsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.pollId, - referencedTable: $db.polls, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PollsTableFilterComposer( - $db: $db, - $table: $db.polls, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.pollId, + referencedTable: $db.polls, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PollsTableFilterComposer( + $db: $db, + $table: $db.polls, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$PollVotesTableOrderingComposer - extends Composer<_$DriftChatDatabase, $PollVotesTable> { +class $$PollVotesTableOrderingComposer extends Composer<_$DriftChatDatabase, $PollVotesTable> { $$PollVotesTableOrderingComposer({ required super.$db, required super.$table, @@ -13336,47 +12854,43 @@ class $$PollVotesTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get optionId => $composableBuilder( - column: $table.optionId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get optionId => + $composableBuilder(column: $table.optionId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get answerText => $composableBuilder( - column: $table.answerText, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get answerText => + $composableBuilder(column: $table.answerText, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnOrderings(column)); $$PollsTableOrderingComposer get pollId { final $$PollsTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.pollId, - referencedTable: $db.polls, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PollsTableOrderingComposer( - $db: $db, - $table: $db.polls, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.pollId, + referencedTable: $db.polls, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PollsTableOrderingComposer( + $db: $db, + $table: $db.polls, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$PollVotesTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $PollVotesTable> { +class $$PollVotesTableAnnotationComposer extends Composer<_$DriftChatDatabase, $PollVotesTable> { $$PollVotesTableAnnotationComposer({ required super.$db, required super.$table, @@ -13384,119 +12898,109 @@ class $$PollVotesTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get optionId => - $composableBuilder(column: $table.optionId, builder: (column) => column); + GeneratedColumn get optionId => $composableBuilder(column: $table.optionId, builder: (column) => column); - GeneratedColumn get answerText => $composableBuilder( - column: $table.answerText, builder: (column) => column); + GeneratedColumn get answerText => $composableBuilder(column: $table.answerText, builder: (column) => column); - GeneratedColumn get createdAt => - $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - GeneratedColumn get updatedAt => - $composableBuilder(column: $table.updatedAt, builder: (column) => column); + GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); - GeneratedColumn get userId => - $composableBuilder(column: $table.userId, builder: (column) => column); + GeneratedColumn get userId => $composableBuilder(column: $table.userId, builder: (column) => column); $$PollsTableAnnotationComposer get pollId { final $$PollsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.pollId, - referencedTable: $db.polls, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PollsTableAnnotationComposer( - $db: $db, - $table: $db.polls, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.pollId, + referencedTable: $db.polls, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PollsTableAnnotationComposer( + $db: $db, + $table: $db.polls, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$PollVotesTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $PollVotesTable, - PollVoteEntity, - $$PollVotesTableFilterComposer, - $$PollVotesTableOrderingComposer, - $$PollVotesTableAnnotationComposer, - $$PollVotesTableCreateCompanionBuilder, - $$PollVotesTableUpdateCompanionBuilder, - (PollVoteEntity, $$PollVotesTableReferences), - PollVoteEntity, - PrefetchHooks Function({bool pollId})> { +class $$PollVotesTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $PollVotesTable, + PollVoteEntity, + $$PollVotesTableFilterComposer, + $$PollVotesTableOrderingComposer, + $$PollVotesTableAnnotationComposer, + $$PollVotesTableCreateCompanionBuilder, + $$PollVotesTableUpdateCompanionBuilder, + (PollVoteEntity, $$PollVotesTableReferences), + PollVoteEntity, + PrefetchHooks Function({bool pollId}) + > { $$PollVotesTableTableManager(_$DriftChatDatabase db, $PollVotesTable table) - : super(TableManagerState( + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$PollVotesTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$PollVotesTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$PollVotesTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value id = const Value.absent(), - Value pollId = const Value.absent(), - Value optionId = const Value.absent(), - Value answerText = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value userId = const Value.absent(), - Value rowid = const Value.absent(), - }) => - PollVotesCompanion( - id: id, - pollId: pollId, - optionId: optionId, - answerText: answerText, - createdAt: createdAt, - updatedAt: updatedAt, - userId: userId, - rowid: rowid, - ), - createCompanionCallback: ({ - Value id = const Value.absent(), - Value pollId = const Value.absent(), - Value optionId = const Value.absent(), - Value answerText = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value userId = const Value.absent(), - Value rowid = const Value.absent(), - }) => - PollVotesCompanion.insert( - id: id, - pollId: pollId, - optionId: optionId, - answerText: answerText, - createdAt: createdAt, - updatedAt: updatedAt, - userId: userId, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => ( - e.readTable(table), - $$PollVotesTableReferences(db, table, e) - )) - .toList(), + createFilteringComposer: () => $$PollVotesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$PollVotesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$PollVotesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value pollId = const Value.absent(), + Value optionId = const Value.absent(), + Value answerText = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value userId = const Value.absent(), + Value rowid = const Value.absent(), + }) => PollVotesCompanion( + id: id, + pollId: pollId, + optionId: optionId, + answerText: answerText, + createdAt: createdAt, + updatedAt: updatedAt, + userId: userId, + rowid: rowid, + ), + createCompanionCallback: + ({ + Value id = const Value.absent(), + Value pollId = const Value.absent(), + Value optionId = const Value.absent(), + Value answerText = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value userId = const Value.absent(), + Value rowid = const Value.absent(), + }) => PollVotesCompanion.insert( + id: id, + pollId: pollId, + optionId: optionId, + answerText: answerText, + createdAt: createdAt, + updatedAt: updatedAt, + userId: userId, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$PollVotesTableReferences(db, table, e))).toList(), prefetchHooksCallback: ({pollId = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], - addJoins: < - T extends TableManagerState< + addJoins: + < + T extends TableManagerState< dynamic, dynamic, dynamic, @@ -13507,90 +13011,91 @@ class $$PollVotesTableTableManager extends RootTableManager< dynamic, dynamic, dynamic, - dynamic>>(state) { - if (pollId) { - state = state.withJoin( - currentTable: table, - currentColumn: table.pollId, - referencedTable: - $$PollVotesTableReferences._pollIdTable(db), - referencedColumn: - $$PollVotesTableReferences._pollIdTable(db).id, - ) as T; - } - - return state; - }, + dynamic + > + >(state) { + if (pollId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.pollId, + referencedTable: $$PollVotesTableReferences._pollIdTable(db), + referencedColumn: $$PollVotesTableReferences._pollIdTable(db).id, + ) + as T; + } + + return state; + }, getPrefetchedDataCallback: (items) async { return []; }, ); }, - )); + ), + ); } -typedef $$PollVotesTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $PollVotesTable, - PollVoteEntity, - $$PollVotesTableFilterComposer, - $$PollVotesTableOrderingComposer, - $$PollVotesTableAnnotationComposer, - $$PollVotesTableCreateCompanionBuilder, - $$PollVotesTableUpdateCompanionBuilder, - (PollVoteEntity, $$PollVotesTableReferences), - PollVoteEntity, - PrefetchHooks Function({bool pollId})>; -typedef $$PinnedMessageReactionsTableCreateCompanionBuilder - = PinnedMessageReactionsCompanion Function({ - Value userId, - Value messageId, - required String type, - Value emojiCode, - Value createdAt, - Value updatedAt, - Value score, - Value?> extraData, - Value rowid, -}); -typedef $$PinnedMessageReactionsTableUpdateCompanionBuilder - = PinnedMessageReactionsCompanion Function({ - Value userId, - Value messageId, - Value type, - Value emojiCode, - Value createdAt, - Value updatedAt, - Value score, - Value?> extraData, - Value rowid, -}); - -final class $$PinnedMessageReactionsTableReferences extends BaseReferences< - _$DriftChatDatabase, - $PinnedMessageReactionsTable, - PinnedMessageReactionEntity> { - $$PinnedMessageReactionsTableReferences( - super.$_db, super.$_table, super.$_typedResult); +typedef $$PollVotesTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $PollVotesTable, + PollVoteEntity, + $$PollVotesTableFilterComposer, + $$PollVotesTableOrderingComposer, + $$PollVotesTableAnnotationComposer, + $$PollVotesTableCreateCompanionBuilder, + $$PollVotesTableUpdateCompanionBuilder, + (PollVoteEntity, $$PollVotesTableReferences), + PollVoteEntity, + PrefetchHooks Function({bool pollId}) + >; +typedef $$PinnedMessageReactionsTableCreateCompanionBuilder = + PinnedMessageReactionsCompanion Function({ + Value userId, + Value messageId, + required String type, + Value emojiCode, + Value createdAt, + Value updatedAt, + Value score, + Value?> extraData, + Value rowid, + }); +typedef $$PinnedMessageReactionsTableUpdateCompanionBuilder = + PinnedMessageReactionsCompanion Function({ + Value userId, + Value messageId, + Value type, + Value emojiCode, + Value createdAt, + Value updatedAt, + Value score, + Value?> extraData, + Value rowid, + }); + +final class $$PinnedMessageReactionsTableReferences + extends BaseReferences<_$DriftChatDatabase, $PinnedMessageReactionsTable, PinnedMessageReactionEntity> { + $$PinnedMessageReactionsTableReferences(super.$_db, super.$_table, super.$_typedResult); static $PinnedMessagesTable _messageIdTable(_$DriftChatDatabase db) => - db.pinnedMessages.createAlias($_aliasNameGenerator( - db.pinnedMessageReactions.messageId, db.pinnedMessages.id)); + db.pinnedMessages.createAlias($_aliasNameGenerator(db.pinnedMessageReactions.messageId, db.pinnedMessages.id)); $$PinnedMessagesTableProcessedTableManager? get messageId { final $_column = $_itemColumn('message_id'); if ($_column == null) return null; - final manager = $$PinnedMessagesTableTableManager($_db, $_db.pinnedMessages) - .filter((f) => f.id.sqlEquals($_column)); + final manager = $$PinnedMessagesTableTableManager( + $_db, + $_db.pinnedMessages, + ).filter((f) => f.id.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_messageIdTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } } -class $$PinnedMessageReactionsTableFilterComposer - extends Composer<_$DriftChatDatabase, $PinnedMessageReactionsTable> { +class $$PinnedMessageReactionsTableFilterComposer extends Composer<_$DriftChatDatabase, $PinnedMessageReactionsTable> { $$PinnedMessageReactionsTableFilterComposer({ required super.$db, required super.$table, @@ -13598,47 +13103,40 @@ class $$PinnedMessageReactionsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnFilters(column)); + ColumnFilters get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnFilters(column)); - ColumnFilters get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnFilters(column)); + ColumnFilters get type => $composableBuilder(column: $table.type, builder: (column) => ColumnFilters(column)); - ColumnFilters get emojiCode => $composableBuilder( - column: $table.emojiCode, builder: (column) => ColumnFilters(column)); + ColumnFilters get emojiCode => + $composableBuilder(column: $table.emojiCode, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get score => $composableBuilder( - column: $table.score, builder: (column) => ColumnFilters(column)); + ColumnFilters get score => $composableBuilder(column: $table.score, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get extraData => $composableBuilder( - column: $table.extraData, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnWithTypeConverterFilters(column)); $$PinnedMessagesTableFilterComposer get messageId { final $$PinnedMessagesTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.messageId, - referencedTable: $db.pinnedMessages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PinnedMessagesTableFilterComposer( - $db: $db, - $table: $db.pinnedMessages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.messageId, + referencedTable: $db.pinnedMessages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PinnedMessagesTableFilterComposer( + $db: $db, + $table: $db.pinnedMessages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } @@ -13652,44 +13150,42 @@ class $$PinnedMessageReactionsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get type => + $composableBuilder(column: $table.type, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get emojiCode => $composableBuilder( - column: $table.emojiCode, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get emojiCode => + $composableBuilder(column: $table.emojiCode, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get score => $composableBuilder( - column: $table.score, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get score => + $composableBuilder(column: $table.score, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnOrderings(column)); $$PinnedMessagesTableOrderingComposer get messageId { final $$PinnedMessagesTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.messageId, - referencedTable: $db.pinnedMessages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PinnedMessagesTableOrderingComposer( - $db: $db, - $table: $db.pinnedMessages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.messageId, + referencedTable: $db.pinnedMessages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PinnedMessagesTableOrderingComposer( + $db: $db, + $table: $db.pinnedMessages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } @@ -13703,131 +13199,116 @@ class $$PinnedMessageReactionsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get userId => - $composableBuilder(column: $table.userId, builder: (column) => column); + GeneratedColumn get userId => $composableBuilder(column: $table.userId, builder: (column) => column); - GeneratedColumn get type => - $composableBuilder(column: $table.type, builder: (column) => column); + GeneratedColumn get type => $composableBuilder(column: $table.type, builder: (column) => column); - GeneratedColumn get emojiCode => - $composableBuilder(column: $table.emojiCode, builder: (column) => column); + GeneratedColumn get emojiCode => $composableBuilder(column: $table.emojiCode, builder: (column) => column); - GeneratedColumn get createdAt => - $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - GeneratedColumn get updatedAt => - $composableBuilder(column: $table.updatedAt, builder: (column) => column); + GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); - GeneratedColumn get score => - $composableBuilder(column: $table.score, builder: (column) => column); + GeneratedColumn get score => $composableBuilder(column: $table.score, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => column); $$PinnedMessagesTableAnnotationComposer get messageId { final $$PinnedMessagesTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.messageId, - referencedTable: $db.pinnedMessages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PinnedMessagesTableAnnotationComposer( - $db: $db, - $table: $db.pinnedMessages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.messageId, + referencedTable: $db.pinnedMessages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PinnedMessagesTableAnnotationComposer( + $db: $db, + $table: $db.pinnedMessages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$PinnedMessageReactionsTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $PinnedMessageReactionsTable, - PinnedMessageReactionEntity, - $$PinnedMessageReactionsTableFilterComposer, - $$PinnedMessageReactionsTableOrderingComposer, - $$PinnedMessageReactionsTableAnnotationComposer, - $$PinnedMessageReactionsTableCreateCompanionBuilder, - $$PinnedMessageReactionsTableUpdateCompanionBuilder, - (PinnedMessageReactionEntity, $$PinnedMessageReactionsTableReferences), - PinnedMessageReactionEntity, - PrefetchHooks Function({bool messageId})> { - $$PinnedMessageReactionsTableTableManager( - _$DriftChatDatabase db, $PinnedMessageReactionsTable table) - : super(TableManagerState( +class $$PinnedMessageReactionsTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $PinnedMessageReactionsTable, + PinnedMessageReactionEntity, + $$PinnedMessageReactionsTableFilterComposer, + $$PinnedMessageReactionsTableOrderingComposer, + $$PinnedMessageReactionsTableAnnotationComposer, + $$PinnedMessageReactionsTableCreateCompanionBuilder, + $$PinnedMessageReactionsTableUpdateCompanionBuilder, + (PinnedMessageReactionEntity, $$PinnedMessageReactionsTableReferences), + PinnedMessageReactionEntity, + PrefetchHooks Function({bool messageId}) + > { + $$PinnedMessageReactionsTableTableManager(_$DriftChatDatabase db, $PinnedMessageReactionsTable table) + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$PinnedMessageReactionsTableFilterComposer( - $db: db, $table: table), - createOrderingComposer: () => - $$PinnedMessageReactionsTableOrderingComposer( - $db: db, $table: table), - createComputedFieldComposer: () => - $$PinnedMessageReactionsTableAnnotationComposer( - $db: db, $table: table), - updateCompanionCallback: ({ - Value userId = const Value.absent(), - Value messageId = const Value.absent(), - Value type = const Value.absent(), - Value emojiCode = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value score = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - PinnedMessageReactionsCompanion( - userId: userId, - messageId: messageId, - type: type, - emojiCode: emojiCode, - createdAt: createdAt, - updatedAt: updatedAt, - score: score, - extraData: extraData, - rowid: rowid, - ), - createCompanionCallback: ({ - Value userId = const Value.absent(), - Value messageId = const Value.absent(), - required String type, - Value emojiCode = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value score = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - PinnedMessageReactionsCompanion.insert( - userId: userId, - messageId: messageId, - type: type, - emojiCode: emojiCode, - createdAt: createdAt, - updatedAt: updatedAt, - score: score, - extraData: extraData, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => ( - e.readTable(table), - $$PinnedMessageReactionsTableReferences(db, table, e) - )) - .toList(), + createFilteringComposer: () => $$PinnedMessageReactionsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$PinnedMessageReactionsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$PinnedMessageReactionsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value userId = const Value.absent(), + Value messageId = const Value.absent(), + Value type = const Value.absent(), + Value emojiCode = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value score = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => PinnedMessageReactionsCompanion( + userId: userId, + messageId: messageId, + type: type, + emojiCode: emojiCode, + createdAt: createdAt, + updatedAt: updatedAt, + score: score, + extraData: extraData, + rowid: rowid, + ), + createCompanionCallback: + ({ + Value userId = const Value.absent(), + Value messageId = const Value.absent(), + required String type, + Value emojiCode = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value score = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => PinnedMessageReactionsCompanion.insert( + userId: userId, + messageId: messageId, + type: type, + emojiCode: emojiCode, + createdAt: createdAt, + updatedAt: updatedAt, + score: score, + extraData: extraData, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$PinnedMessageReactionsTableReferences(db, table, e))).toList(), prefetchHooksCallback: ({messageId = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], - addJoins: < - T extends TableManagerState< + addJoins: + < + T extends TableManagerState< dynamic, dynamic, dynamic, @@ -13838,87 +13319,87 @@ class $$PinnedMessageReactionsTableTableManager extends RootTableManager< dynamic, dynamic, dynamic, - dynamic>>(state) { - if (messageId) { - state = state.withJoin( - currentTable: table, - currentColumn: table.messageId, - referencedTable: $$PinnedMessageReactionsTableReferences - ._messageIdTable(db), - referencedColumn: $$PinnedMessageReactionsTableReferences - ._messageIdTable(db) - .id, - ) as T; - } - - return state; - }, + dynamic + > + >(state) { + if (messageId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.messageId, + referencedTable: $$PinnedMessageReactionsTableReferences._messageIdTable(db), + referencedColumn: $$PinnedMessageReactionsTableReferences._messageIdTable(db).id, + ) + as T; + } + + return state; + }, getPrefetchedDataCallback: (items) async { return []; }, ); }, - )); + ), + ); } -typedef $$PinnedMessageReactionsTableProcessedTableManager - = ProcessedTableManager< - _$DriftChatDatabase, - $PinnedMessageReactionsTable, - PinnedMessageReactionEntity, - $$PinnedMessageReactionsTableFilterComposer, - $$PinnedMessageReactionsTableOrderingComposer, - $$PinnedMessageReactionsTableAnnotationComposer, - $$PinnedMessageReactionsTableCreateCompanionBuilder, - $$PinnedMessageReactionsTableUpdateCompanionBuilder, - (PinnedMessageReactionEntity, $$PinnedMessageReactionsTableReferences), - PinnedMessageReactionEntity, - PrefetchHooks Function({bool messageId})>; -typedef $$ReactionsTableCreateCompanionBuilder = ReactionsCompanion Function({ - Value userId, - Value messageId, - required String type, - Value emojiCode, - Value createdAt, - Value updatedAt, - Value score, - Value?> extraData, - Value rowid, -}); -typedef $$ReactionsTableUpdateCompanionBuilder = ReactionsCompanion Function({ - Value userId, - Value messageId, - Value type, - Value emojiCode, - Value createdAt, - Value updatedAt, - Value score, - Value?> extraData, - Value rowid, -}); - -final class $$ReactionsTableReferences extends BaseReferences< - _$DriftChatDatabase, $ReactionsTable, ReactionEntity> { +typedef $$PinnedMessageReactionsTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $PinnedMessageReactionsTable, + PinnedMessageReactionEntity, + $$PinnedMessageReactionsTableFilterComposer, + $$PinnedMessageReactionsTableOrderingComposer, + $$PinnedMessageReactionsTableAnnotationComposer, + $$PinnedMessageReactionsTableCreateCompanionBuilder, + $$PinnedMessageReactionsTableUpdateCompanionBuilder, + (PinnedMessageReactionEntity, $$PinnedMessageReactionsTableReferences), + PinnedMessageReactionEntity, + PrefetchHooks Function({bool messageId}) + >; +typedef $$ReactionsTableCreateCompanionBuilder = + ReactionsCompanion Function({ + Value userId, + Value messageId, + required String type, + Value emojiCode, + Value createdAt, + Value updatedAt, + Value score, + Value?> extraData, + Value rowid, + }); +typedef $$ReactionsTableUpdateCompanionBuilder = + ReactionsCompanion Function({ + Value userId, + Value messageId, + Value type, + Value emojiCode, + Value createdAt, + Value updatedAt, + Value score, + Value?> extraData, + Value rowid, + }); + +final class $$ReactionsTableReferences extends BaseReferences<_$DriftChatDatabase, $ReactionsTable, ReactionEntity> { $$ReactionsTableReferences(super.$_db, super.$_table, super.$_typedResult); static $MessagesTable _messageIdTable(_$DriftChatDatabase db) => - db.messages.createAlias( - $_aliasNameGenerator(db.reactions.messageId, db.messages.id)); + db.messages.createAlias($_aliasNameGenerator(db.reactions.messageId, db.messages.id)); $$MessagesTableProcessedTableManager? get messageId { final $_column = $_itemColumn('message_id'); if ($_column == null) return null; - final manager = $$MessagesTableTableManager($_db, $_db.messages) - .filter((f) => f.id.sqlEquals($_column)); + final manager = $$MessagesTableTableManager($_db, $_db.messages).filter((f) => f.id.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_messageIdTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } } -class $$ReactionsTableFilterComposer - extends Composer<_$DriftChatDatabase, $ReactionsTable> { +class $$ReactionsTableFilterComposer extends Composer<_$DriftChatDatabase, $ReactionsTable> { $$ReactionsTableFilterComposer({ required super.$db, required super.$table, @@ -13926,53 +13407,45 @@ class $$ReactionsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnFilters(column)); + ColumnFilters get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnFilters(column)); - ColumnFilters get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnFilters(column)); + ColumnFilters get type => $composableBuilder(column: $table.type, builder: (column) => ColumnFilters(column)); - ColumnFilters get emojiCode => $composableBuilder( - column: $table.emojiCode, builder: (column) => ColumnFilters(column)); + ColumnFilters get emojiCode => + $composableBuilder(column: $table.emojiCode, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get score => $composableBuilder( - column: $table.score, builder: (column) => ColumnFilters(column)); + ColumnFilters get score => $composableBuilder(column: $table.score, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get extraData => $composableBuilder( - column: $table.extraData, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnWithTypeConverterFilters(column)); $$MessagesTableFilterComposer get messageId { final $$MessagesTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.messageId, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableFilterComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.messageId, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableFilterComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$ReactionsTableOrderingComposer - extends Composer<_$DriftChatDatabase, $ReactionsTable> { +class $$ReactionsTableOrderingComposer extends Composer<_$DriftChatDatabase, $ReactionsTable> { $$ReactionsTableOrderingComposer({ required super.$db, required super.$table, @@ -13980,50 +13453,47 @@ class $$ReactionsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get type => + $composableBuilder(column: $table.type, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get emojiCode => $composableBuilder( - column: $table.emojiCode, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get emojiCode => + $composableBuilder(column: $table.emojiCode, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get score => $composableBuilder( - column: $table.score, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get score => + $composableBuilder(column: $table.score, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnOrderings(column)); $$MessagesTableOrderingComposer get messageId { final $$MessagesTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.messageId, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableOrderingComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.messageId, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableOrderingComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$ReactionsTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $ReactionsTable> { +class $$ReactionsTableAnnotationComposer extends Composer<_$DriftChatDatabase, $ReactionsTable> { $$ReactionsTableAnnotationComposer({ required super.$db, required super.$table, @@ -14031,127 +13501,116 @@ class $$ReactionsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get userId => - $composableBuilder(column: $table.userId, builder: (column) => column); + GeneratedColumn get userId => $composableBuilder(column: $table.userId, builder: (column) => column); - GeneratedColumn get type => - $composableBuilder(column: $table.type, builder: (column) => column); + GeneratedColumn get type => $composableBuilder(column: $table.type, builder: (column) => column); - GeneratedColumn get emojiCode => - $composableBuilder(column: $table.emojiCode, builder: (column) => column); + GeneratedColumn get emojiCode => $composableBuilder(column: $table.emojiCode, builder: (column) => column); - GeneratedColumn get createdAt => - $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - GeneratedColumn get updatedAt => - $composableBuilder(column: $table.updatedAt, builder: (column) => column); + GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); - GeneratedColumn get score => - $composableBuilder(column: $table.score, builder: (column) => column); + GeneratedColumn get score => $composableBuilder(column: $table.score, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => column); $$MessagesTableAnnotationComposer get messageId { final $$MessagesTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.messageId, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableAnnotationComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.messageId, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableAnnotationComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$ReactionsTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $ReactionsTable, - ReactionEntity, - $$ReactionsTableFilterComposer, - $$ReactionsTableOrderingComposer, - $$ReactionsTableAnnotationComposer, - $$ReactionsTableCreateCompanionBuilder, - $$ReactionsTableUpdateCompanionBuilder, - (ReactionEntity, $$ReactionsTableReferences), - ReactionEntity, - PrefetchHooks Function({bool messageId})> { +class $$ReactionsTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $ReactionsTable, + ReactionEntity, + $$ReactionsTableFilterComposer, + $$ReactionsTableOrderingComposer, + $$ReactionsTableAnnotationComposer, + $$ReactionsTableCreateCompanionBuilder, + $$ReactionsTableUpdateCompanionBuilder, + (ReactionEntity, $$ReactionsTableReferences), + ReactionEntity, + PrefetchHooks Function({bool messageId}) + > { $$ReactionsTableTableManager(_$DriftChatDatabase db, $ReactionsTable table) - : super(TableManagerState( + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$ReactionsTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$ReactionsTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$ReactionsTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value userId = const Value.absent(), - Value messageId = const Value.absent(), - Value type = const Value.absent(), - Value emojiCode = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value score = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - ReactionsCompanion( - userId: userId, - messageId: messageId, - type: type, - emojiCode: emojiCode, - createdAt: createdAt, - updatedAt: updatedAt, - score: score, - extraData: extraData, - rowid: rowid, - ), - createCompanionCallback: ({ - Value userId = const Value.absent(), - Value messageId = const Value.absent(), - required String type, - Value emojiCode = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value score = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - ReactionsCompanion.insert( - userId: userId, - messageId: messageId, - type: type, - emojiCode: emojiCode, - createdAt: createdAt, - updatedAt: updatedAt, - score: score, - extraData: extraData, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => ( - e.readTable(table), - $$ReactionsTableReferences(db, table, e) - )) - .toList(), + createFilteringComposer: () => $$ReactionsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$ReactionsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$ReactionsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value userId = const Value.absent(), + Value messageId = const Value.absent(), + Value type = const Value.absent(), + Value emojiCode = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value score = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => ReactionsCompanion( + userId: userId, + messageId: messageId, + type: type, + emojiCode: emojiCode, + createdAt: createdAt, + updatedAt: updatedAt, + score: score, + extraData: extraData, + rowid: rowid, + ), + createCompanionCallback: + ({ + Value userId = const Value.absent(), + Value messageId = const Value.absent(), + required String type, + Value emojiCode = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value score = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => ReactionsCompanion.insert( + userId: userId, + messageId: messageId, + type: type, + emojiCode: emojiCode, + createdAt: createdAt, + updatedAt: updatedAt, + score: score, + extraData: extraData, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$ReactionsTableReferences(db, table, e))).toList(), prefetchHooksCallback: ({messageId = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], - addJoins: < - T extends TableManagerState< + addJoins: + < + T extends TableManagerState< dynamic, dynamic, dynamic, @@ -14162,71 +13621,77 @@ class $$ReactionsTableTableManager extends RootTableManager< dynamic, dynamic, dynamic, - dynamic>>(state) { - if (messageId) { - state = state.withJoin( - currentTable: table, - currentColumn: table.messageId, - referencedTable: - $$ReactionsTableReferences._messageIdTable(db), - referencedColumn: - $$ReactionsTableReferences._messageIdTable(db).id, - ) as T; - } - - return state; - }, + dynamic + > + >(state) { + if (messageId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.messageId, + referencedTable: $$ReactionsTableReferences._messageIdTable(db), + referencedColumn: $$ReactionsTableReferences._messageIdTable(db).id, + ) + as T; + } + + return state; + }, getPrefetchedDataCallback: (items) async { return []; }, ); }, - )); + ), + ); } -typedef $$ReactionsTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $ReactionsTable, - ReactionEntity, - $$ReactionsTableFilterComposer, - $$ReactionsTableOrderingComposer, - $$ReactionsTableAnnotationComposer, - $$ReactionsTableCreateCompanionBuilder, - $$ReactionsTableUpdateCompanionBuilder, - (ReactionEntity, $$ReactionsTableReferences), - ReactionEntity, - PrefetchHooks Function({bool messageId})>; -typedef $$UsersTableCreateCompanionBuilder = UsersCompanion Function({ - required String id, - Value role, - Value language, - Value createdAt, - Value updatedAt, - Value lastActive, - Value online, - Value banned, - Value?> teamsRole, - Value avgResponseTime, - required Map extraData, - Value rowid, -}); -typedef $$UsersTableUpdateCompanionBuilder = UsersCompanion Function({ - Value id, - Value role, - Value language, - Value createdAt, - Value updatedAt, - Value lastActive, - Value online, - Value banned, - Value?> teamsRole, - Value avgResponseTime, - Value> extraData, - Value rowid, -}); - -class $$UsersTableFilterComposer - extends Composer<_$DriftChatDatabase, $UsersTable> { +typedef $$ReactionsTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $ReactionsTable, + ReactionEntity, + $$ReactionsTableFilterComposer, + $$ReactionsTableOrderingComposer, + $$ReactionsTableAnnotationComposer, + $$ReactionsTableCreateCompanionBuilder, + $$ReactionsTableUpdateCompanionBuilder, + (ReactionEntity, $$ReactionsTableReferences), + ReactionEntity, + PrefetchHooks Function({bool messageId}) + >; +typedef $$UsersTableCreateCompanionBuilder = + UsersCompanion Function({ + required String id, + Value role, + Value language, + Value createdAt, + Value updatedAt, + Value lastActive, + Value online, + Value banned, + Value?> teamsRole, + Value avgResponseTime, + required Map extraData, + Value rowid, + }); +typedef $$UsersTableUpdateCompanionBuilder = + UsersCompanion Function({ + Value id, + Value role, + Value language, + Value createdAt, + Value updatedAt, + Value lastActive, + Value online, + Value banned, + Value?> teamsRole, + Value avgResponseTime, + Value> extraData, + Value rowid, + }); + +class $$UsersTableFilterComposer extends Composer<_$DriftChatDatabase, $UsersTable> { $$UsersTableFilterComposer({ required super.$db, required super.$table, @@ -14234,49 +13699,39 @@ class $$UsersTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get role => $composableBuilder( - column: $table.role, builder: (column) => ColumnFilters(column)); + ColumnFilters get role => $composableBuilder(column: $table.role, builder: (column) => ColumnFilters(column)); - ColumnFilters get language => $composableBuilder( - column: $table.language, builder: (column) => ColumnFilters(column)); + ColumnFilters get language => + $composableBuilder(column: $table.language, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get lastActive => $composableBuilder( - column: $table.lastActive, builder: (column) => ColumnFilters(column)); + ColumnFilters get lastActive => + $composableBuilder(column: $table.lastActive, builder: (column) => ColumnFilters(column)); - ColumnFilters get online => $composableBuilder( - column: $table.online, builder: (column) => ColumnFilters(column)); + ColumnFilters get online => + $composableBuilder(column: $table.online, builder: (column) => ColumnFilters(column)); - ColumnFilters get banned => $composableBuilder( - column: $table.banned, builder: (column) => ColumnFilters(column)); + ColumnFilters get banned => + $composableBuilder(column: $table.banned, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get teamsRole => $composableBuilder( - column: $table.teamsRole, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get teamsRole => + $composableBuilder(column: $table.teamsRole, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get avgResponseTime => $composableBuilder( - column: $table.avgResponseTime, - builder: (column) => ColumnFilters(column)); + ColumnFilters get avgResponseTime => + $composableBuilder(column: $table.avgResponseTime, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, Map, - String> - get extraData => $composableBuilder( - column: $table.extraData, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, Map, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnWithTypeConverterFilters(column)); } -class $$UsersTableOrderingComposer - extends Composer<_$DriftChatDatabase, $UsersTable> { +class $$UsersTableOrderingComposer extends Composer<_$DriftChatDatabase, $UsersTable> { $$UsersTableOrderingComposer({ required super.$db, required super.$table, @@ -14284,43 +13739,40 @@ class $$UsersTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get role => $composableBuilder( - column: $table.role, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get role => + $composableBuilder(column: $table.role, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get language => $composableBuilder( - column: $table.language, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get language => + $composableBuilder(column: $table.language, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get lastActive => $composableBuilder( - column: $table.lastActive, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get lastActive => + $composableBuilder(column: $table.lastActive, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get online => $composableBuilder( - column: $table.online, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get online => + $composableBuilder(column: $table.online, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get banned => $composableBuilder( - column: $table.banned, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get banned => + $composableBuilder(column: $table.banned, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get teamsRole => $composableBuilder( - column: $table.teamsRole, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get teamsRole => + $composableBuilder(column: $table.teamsRole, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get avgResponseTime => $composableBuilder( - column: $table.avgResponseTime, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get avgResponseTime => + $composableBuilder(column: $table.avgResponseTime, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnOrderings(column)); } -class $$UsersTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $UsersTable> { +class $$UsersTableAnnotationComposer extends Composer<_$DriftChatDatabase, $UsersTable> { $$UsersTableAnnotationComposer({ required super.$db, required super.$table, @@ -14328,198 +13780,188 @@ class $$UsersTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get role => - $composableBuilder(column: $table.role, builder: (column) => column); + GeneratedColumn get role => $composableBuilder(column: $table.role, builder: (column) => column); - GeneratedColumn get language => - $composableBuilder(column: $table.language, builder: (column) => column); + GeneratedColumn get language => $composableBuilder(column: $table.language, builder: (column) => column); - GeneratedColumn get createdAt => - $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - GeneratedColumn get updatedAt => - $composableBuilder(column: $table.updatedAt, builder: (column) => column); + GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); - GeneratedColumn get lastActive => $composableBuilder( - column: $table.lastActive, builder: (column) => column); + GeneratedColumn get lastActive => + $composableBuilder(column: $table.lastActive, builder: (column) => column); - GeneratedColumn get online => - $composableBuilder(column: $table.online, builder: (column) => column); + GeneratedColumn get online => $composableBuilder(column: $table.online, builder: (column) => column); - GeneratedColumn get banned => - $composableBuilder(column: $table.banned, builder: (column) => column); + GeneratedColumn get banned => $composableBuilder(column: $table.banned, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get teamsRole => $composableBuilder( - column: $table.teamsRole, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get teamsRole => + $composableBuilder(column: $table.teamsRole, builder: (column) => column); - GeneratedColumn get avgResponseTime => $composableBuilder( - column: $table.avgResponseTime, builder: (column) => column); + GeneratedColumn get avgResponseTime => + $composableBuilder(column: $table.avgResponseTime, builder: (column) => column); - GeneratedColumnWithTypeConverter, String> - get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => column); + GeneratedColumnWithTypeConverter, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => column); } -class $$UsersTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $UsersTable, - UserEntity, - $$UsersTableFilterComposer, - $$UsersTableOrderingComposer, - $$UsersTableAnnotationComposer, - $$UsersTableCreateCompanionBuilder, - $$UsersTableUpdateCompanionBuilder, - (UserEntity, BaseReferences<_$DriftChatDatabase, $UsersTable, UserEntity>), - UserEntity, - PrefetchHooks Function()> { +class $$UsersTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $UsersTable, + UserEntity, + $$UsersTableFilterComposer, + $$UsersTableOrderingComposer, + $$UsersTableAnnotationComposer, + $$UsersTableCreateCompanionBuilder, + $$UsersTableUpdateCompanionBuilder, + (UserEntity, BaseReferences<_$DriftChatDatabase, $UsersTable, UserEntity>), + UserEntity, + PrefetchHooks Function() + > { $$UsersTableTableManager(_$DriftChatDatabase db, $UsersTable table) - : super(TableManagerState( + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$UsersTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$UsersTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$UsersTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value id = const Value.absent(), - Value role = const Value.absent(), - Value language = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value lastActive = const Value.absent(), - Value online = const Value.absent(), - Value banned = const Value.absent(), - Value?> teamsRole = const Value.absent(), - Value avgResponseTime = const Value.absent(), - Value> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - UsersCompanion( - id: id, - role: role, - language: language, - createdAt: createdAt, - updatedAt: updatedAt, - lastActive: lastActive, - online: online, - banned: banned, - teamsRole: teamsRole, - avgResponseTime: avgResponseTime, - extraData: extraData, - rowid: rowid, - ), - createCompanionCallback: ({ - required String id, - Value role = const Value.absent(), - Value language = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value lastActive = const Value.absent(), - Value online = const Value.absent(), - Value banned = const Value.absent(), - Value?> teamsRole = const Value.absent(), - Value avgResponseTime = const Value.absent(), - required Map extraData, - Value rowid = const Value.absent(), - }) => - UsersCompanion.insert( - id: id, - role: role, - language: language, - createdAt: createdAt, - updatedAt: updatedAt, - lastActive: lastActive, - online: online, - banned: banned, - teamsRole: teamsRole, - avgResponseTime: avgResponseTime, - extraData: extraData, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) - .toList(), + createFilteringComposer: () => $$UsersTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$UsersTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$UsersTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value role = const Value.absent(), + Value language = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value lastActive = const Value.absent(), + Value online = const Value.absent(), + Value banned = const Value.absent(), + Value?> teamsRole = const Value.absent(), + Value avgResponseTime = const Value.absent(), + Value> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => UsersCompanion( + id: id, + role: role, + language: language, + createdAt: createdAt, + updatedAt: updatedAt, + lastActive: lastActive, + online: online, + banned: banned, + teamsRole: teamsRole, + avgResponseTime: avgResponseTime, + extraData: extraData, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + Value role = const Value.absent(), + Value language = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value lastActive = const Value.absent(), + Value online = const Value.absent(), + Value banned = const Value.absent(), + Value?> teamsRole = const Value.absent(), + Value avgResponseTime = const Value.absent(), + required Map extraData, + Value rowid = const Value.absent(), + }) => UsersCompanion.insert( + id: id, + role: role, + language: language, + createdAt: createdAt, + updatedAt: updatedAt, + lastActive: lastActive, + online: online, + banned: banned, + teamsRole: teamsRole, + avgResponseTime: avgResponseTime, + extraData: extraData, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), prefetchHooksCallback: null, - )); + ), + ); } -typedef $$UsersTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $UsersTable, - UserEntity, - $$UsersTableFilterComposer, - $$UsersTableOrderingComposer, - $$UsersTableAnnotationComposer, - $$UsersTableCreateCompanionBuilder, - $$UsersTableUpdateCompanionBuilder, - (UserEntity, BaseReferences<_$DriftChatDatabase, $UsersTable, UserEntity>), - UserEntity, - PrefetchHooks Function()>; -typedef $$MembersTableCreateCompanionBuilder = MembersCompanion Function({ - required String userId, - required String channelCid, - Value channelRole, - Value inviteAcceptedAt, - Value inviteRejectedAt, - Value invited, - Value banned, - Value shadowBanned, - Value pinnedAt, - Value archivedAt, - Value isModerator, - Value?> extraData, - Value createdAt, - Value updatedAt, - required List deletedMessages, - Value rowid, -}); -typedef $$MembersTableUpdateCompanionBuilder = MembersCompanion Function({ - Value userId, - Value channelCid, - Value channelRole, - Value inviteAcceptedAt, - Value inviteRejectedAt, - Value invited, - Value banned, - Value shadowBanned, - Value pinnedAt, - Value archivedAt, - Value isModerator, - Value?> extraData, - Value createdAt, - Value updatedAt, - Value> deletedMessages, - Value rowid, -}); - -final class $$MembersTableReferences - extends BaseReferences<_$DriftChatDatabase, $MembersTable, MemberEntity> { +typedef $$UsersTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $UsersTable, + UserEntity, + $$UsersTableFilterComposer, + $$UsersTableOrderingComposer, + $$UsersTableAnnotationComposer, + $$UsersTableCreateCompanionBuilder, + $$UsersTableUpdateCompanionBuilder, + (UserEntity, BaseReferences<_$DriftChatDatabase, $UsersTable, UserEntity>), + UserEntity, + PrefetchHooks Function() + >; +typedef $$MembersTableCreateCompanionBuilder = + MembersCompanion Function({ + required String userId, + required String channelCid, + Value channelRole, + Value inviteAcceptedAt, + Value inviteRejectedAt, + Value invited, + Value banned, + Value shadowBanned, + Value pinnedAt, + Value archivedAt, + Value isModerator, + Value?> extraData, + Value createdAt, + Value updatedAt, + required List deletedMessages, + Value rowid, + }); +typedef $$MembersTableUpdateCompanionBuilder = + MembersCompanion Function({ + Value userId, + Value channelCid, + Value channelRole, + Value inviteAcceptedAt, + Value inviteRejectedAt, + Value invited, + Value banned, + Value shadowBanned, + Value pinnedAt, + Value archivedAt, + Value isModerator, + Value?> extraData, + Value createdAt, + Value updatedAt, + Value> deletedMessages, + Value rowid, + }); + +final class $$MembersTableReferences extends BaseReferences<_$DriftChatDatabase, $MembersTable, MemberEntity> { $$MembersTableReferences(super.$_db, super.$_table, super.$_typedResult); static $ChannelsTable _channelCidTable(_$DriftChatDatabase db) => - db.channels.createAlias( - $_aliasNameGenerator(db.members.channelCid, db.channels.cid)); + db.channels.createAlias($_aliasNameGenerator(db.members.channelCid, db.channels.cid)); $$ChannelsTableProcessedTableManager get channelCid { final $_column = $_itemColumn('channel_cid')!; - final manager = $$ChannelsTableTableManager($_db, $_db.channels) - .filter((f) => f.cid.sqlEquals($_column)); + final manager = $$ChannelsTableTableManager($_db, $_db.channels).filter((f) => f.cid.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_channelCidTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } } -class $$MembersTableFilterComposer - extends Composer<_$DriftChatDatabase, $MembersTable> { +class $$MembersTableFilterComposer extends Composer<_$DriftChatDatabase, $MembersTable> { $$MembersTableFilterComposer({ required super.$db, required super.$table, @@ -14527,78 +13969,68 @@ class $$MembersTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnFilters(column)); + ColumnFilters get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnFilters(column)); - ColumnFilters get channelRole => $composableBuilder( - column: $table.channelRole, builder: (column) => ColumnFilters(column)); + ColumnFilters get channelRole => + $composableBuilder(column: $table.channelRole, builder: (column) => ColumnFilters(column)); - ColumnFilters get inviteAcceptedAt => $composableBuilder( - column: $table.inviteAcceptedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get inviteAcceptedAt => + $composableBuilder(column: $table.inviteAcceptedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get inviteRejectedAt => $composableBuilder( - column: $table.inviteRejectedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get inviteRejectedAt => + $composableBuilder(column: $table.inviteRejectedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get invited => $composableBuilder( - column: $table.invited, builder: (column) => ColumnFilters(column)); + ColumnFilters get invited => + $composableBuilder(column: $table.invited, builder: (column) => ColumnFilters(column)); - ColumnFilters get banned => $composableBuilder( - column: $table.banned, builder: (column) => ColumnFilters(column)); + ColumnFilters get banned => + $composableBuilder(column: $table.banned, builder: (column) => ColumnFilters(column)); - ColumnFilters get shadowBanned => $composableBuilder( - column: $table.shadowBanned, builder: (column) => ColumnFilters(column)); + ColumnFilters get shadowBanned => + $composableBuilder(column: $table.shadowBanned, builder: (column) => ColumnFilters(column)); - ColumnFilters get pinnedAt => $composableBuilder( - column: $table.pinnedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get pinnedAt => + $composableBuilder(column: $table.pinnedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get archivedAt => $composableBuilder( - column: $table.archivedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get archivedAt => + $composableBuilder(column: $table.archivedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get isModerator => $composableBuilder( - column: $table.isModerator, builder: (column) => ColumnFilters(column)); + ColumnFilters get isModerator => + $composableBuilder(column: $table.isModerator, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get extraData => $composableBuilder( - column: $table.extraData, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, List, String> - get deletedMessages => $composableBuilder( - column: $table.deletedMessages, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, List, String> get deletedMessages => + $composableBuilder(column: $table.deletedMessages, builder: (column) => ColumnWithTypeConverterFilters(column)); $$ChannelsTableFilterComposer get channelCid { final $$ChannelsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableFilterComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableFilterComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$MembersTableOrderingComposer - extends Composer<_$DriftChatDatabase, $MembersTable> { +class $$MembersTableOrderingComposer extends Composer<_$DriftChatDatabase, $MembersTable> { $$MembersTableOrderingComposer({ required super.$db, required super.$table, @@ -14606,75 +14038,68 @@ class $$MembersTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get channelRole => $composableBuilder( - column: $table.channelRole, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get channelRole => + $composableBuilder(column: $table.channelRole, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get inviteAcceptedAt => $composableBuilder( - column: $table.inviteAcceptedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get inviteAcceptedAt => + $composableBuilder(column: $table.inviteAcceptedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get inviteRejectedAt => $composableBuilder( - column: $table.inviteRejectedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get inviteRejectedAt => + $composableBuilder(column: $table.inviteRejectedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get invited => $composableBuilder( - column: $table.invited, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get invited => + $composableBuilder(column: $table.invited, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get banned => $composableBuilder( - column: $table.banned, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get banned => + $composableBuilder(column: $table.banned, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get shadowBanned => $composableBuilder( - column: $table.shadowBanned, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get shadowBanned => + $composableBuilder(column: $table.shadowBanned, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pinnedAt => $composableBuilder( - column: $table.pinnedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pinnedAt => + $composableBuilder(column: $table.pinnedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get archivedAt => $composableBuilder( - column: $table.archivedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get archivedAt => + $composableBuilder(column: $table.archivedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get isModerator => $composableBuilder( - column: $table.isModerator, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get isModerator => + $composableBuilder(column: $table.isModerator, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get deletedMessages => $composableBuilder( - column: $table.deletedMessages, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get deletedMessages => + $composableBuilder(column: $table.deletedMessages, builder: (column) => ColumnOrderings(column)); $$ChannelsTableOrderingComposer get channelCid { final $$ChannelsTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableOrderingComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableOrderingComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$MembersTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $MembersTable> { +class $$MembersTableAnnotationComposer extends Composer<_$DriftChatDatabase, $MembersTable> { $$MembersTableAnnotationComposer({ required super.$db, required super.$table, @@ -14682,175 +14107,164 @@ class $$MembersTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get userId => - $composableBuilder(column: $table.userId, builder: (column) => column); + GeneratedColumn get userId => $composableBuilder(column: $table.userId, builder: (column) => column); - GeneratedColumn get channelRole => $composableBuilder( - column: $table.channelRole, builder: (column) => column); + GeneratedColumn get channelRole => + $composableBuilder(column: $table.channelRole, builder: (column) => column); - GeneratedColumn get inviteAcceptedAt => $composableBuilder( - column: $table.inviteAcceptedAt, builder: (column) => column); + GeneratedColumn get inviteAcceptedAt => + $composableBuilder(column: $table.inviteAcceptedAt, builder: (column) => column); - GeneratedColumn get inviteRejectedAt => $composableBuilder( - column: $table.inviteRejectedAt, builder: (column) => column); + GeneratedColumn get inviteRejectedAt => + $composableBuilder(column: $table.inviteRejectedAt, builder: (column) => column); - GeneratedColumn get invited => - $composableBuilder(column: $table.invited, builder: (column) => column); + GeneratedColumn get invited => $composableBuilder(column: $table.invited, builder: (column) => column); - GeneratedColumn get banned => - $composableBuilder(column: $table.banned, builder: (column) => column); + GeneratedColumn get banned => $composableBuilder(column: $table.banned, builder: (column) => column); - GeneratedColumn get shadowBanned => $composableBuilder( - column: $table.shadowBanned, builder: (column) => column); + GeneratedColumn get shadowBanned => + $composableBuilder(column: $table.shadowBanned, builder: (column) => column); - GeneratedColumn get pinnedAt => - $composableBuilder(column: $table.pinnedAt, builder: (column) => column); + GeneratedColumn get pinnedAt => $composableBuilder(column: $table.pinnedAt, builder: (column) => column); - GeneratedColumn get archivedAt => $composableBuilder( - column: $table.archivedAt, builder: (column) => column); + GeneratedColumn get archivedAt => + $composableBuilder(column: $table.archivedAt, builder: (column) => column); - GeneratedColumn get isModerator => $composableBuilder( - column: $table.isModerator, builder: (column) => column); + GeneratedColumn get isModerator => $composableBuilder(column: $table.isModerator, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => column); - GeneratedColumn get createdAt => - $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - GeneratedColumn get updatedAt => - $composableBuilder(column: $table.updatedAt, builder: (column) => column); + GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); GeneratedColumnWithTypeConverter, String> get deletedMessages => - $composableBuilder( - column: $table.deletedMessages, builder: (column) => column); + $composableBuilder(column: $table.deletedMessages, builder: (column) => column); $$ChannelsTableAnnotationComposer get channelCid { final $$ChannelsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableAnnotationComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableAnnotationComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$MembersTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $MembersTable, - MemberEntity, - $$MembersTableFilterComposer, - $$MembersTableOrderingComposer, - $$MembersTableAnnotationComposer, - $$MembersTableCreateCompanionBuilder, - $$MembersTableUpdateCompanionBuilder, - (MemberEntity, $$MembersTableReferences), - MemberEntity, - PrefetchHooks Function({bool channelCid})> { +class $$MembersTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $MembersTable, + MemberEntity, + $$MembersTableFilterComposer, + $$MembersTableOrderingComposer, + $$MembersTableAnnotationComposer, + $$MembersTableCreateCompanionBuilder, + $$MembersTableUpdateCompanionBuilder, + (MemberEntity, $$MembersTableReferences), + MemberEntity, + PrefetchHooks Function({bool channelCid}) + > { $$MembersTableTableManager(_$DriftChatDatabase db, $MembersTable table) - : super(TableManagerState( + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$MembersTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$MembersTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$MembersTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value userId = const Value.absent(), - Value channelCid = const Value.absent(), - Value channelRole = const Value.absent(), - Value inviteAcceptedAt = const Value.absent(), - Value inviteRejectedAt = const Value.absent(), - Value invited = const Value.absent(), - Value banned = const Value.absent(), - Value shadowBanned = const Value.absent(), - Value pinnedAt = const Value.absent(), - Value archivedAt = const Value.absent(), - Value isModerator = const Value.absent(), - Value?> extraData = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value> deletedMessages = const Value.absent(), - Value rowid = const Value.absent(), - }) => - MembersCompanion( - userId: userId, - channelCid: channelCid, - channelRole: channelRole, - inviteAcceptedAt: inviteAcceptedAt, - inviteRejectedAt: inviteRejectedAt, - invited: invited, - banned: banned, - shadowBanned: shadowBanned, - pinnedAt: pinnedAt, - archivedAt: archivedAt, - isModerator: isModerator, - extraData: extraData, - createdAt: createdAt, - updatedAt: updatedAt, - deletedMessages: deletedMessages, - rowid: rowid, - ), - createCompanionCallback: ({ - required String userId, - required String channelCid, - Value channelRole = const Value.absent(), - Value inviteAcceptedAt = const Value.absent(), - Value inviteRejectedAt = const Value.absent(), - Value invited = const Value.absent(), - Value banned = const Value.absent(), - Value shadowBanned = const Value.absent(), - Value pinnedAt = const Value.absent(), - Value archivedAt = const Value.absent(), - Value isModerator = const Value.absent(), - Value?> extraData = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - required List deletedMessages, - Value rowid = const Value.absent(), - }) => - MembersCompanion.insert( - userId: userId, - channelCid: channelCid, - channelRole: channelRole, - inviteAcceptedAt: inviteAcceptedAt, - inviteRejectedAt: inviteRejectedAt, - invited: invited, - banned: banned, - shadowBanned: shadowBanned, - pinnedAt: pinnedAt, - archivedAt: archivedAt, - isModerator: isModerator, - extraData: extraData, - createdAt: createdAt, - updatedAt: updatedAt, - deletedMessages: deletedMessages, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => - (e.readTable(table), $$MembersTableReferences(db, table, e))) - .toList(), + createFilteringComposer: () => $$MembersTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$MembersTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$MembersTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value userId = const Value.absent(), + Value channelCid = const Value.absent(), + Value channelRole = const Value.absent(), + Value inviteAcceptedAt = const Value.absent(), + Value inviteRejectedAt = const Value.absent(), + Value invited = const Value.absent(), + Value banned = const Value.absent(), + Value shadowBanned = const Value.absent(), + Value pinnedAt = const Value.absent(), + Value archivedAt = const Value.absent(), + Value isModerator = const Value.absent(), + Value?> extraData = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value> deletedMessages = const Value.absent(), + Value rowid = const Value.absent(), + }) => MembersCompanion( + userId: userId, + channelCid: channelCid, + channelRole: channelRole, + inviteAcceptedAt: inviteAcceptedAt, + inviteRejectedAt: inviteRejectedAt, + invited: invited, + banned: banned, + shadowBanned: shadowBanned, + pinnedAt: pinnedAt, + archivedAt: archivedAt, + isModerator: isModerator, + extraData: extraData, + createdAt: createdAt, + updatedAt: updatedAt, + deletedMessages: deletedMessages, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String userId, + required String channelCid, + Value channelRole = const Value.absent(), + Value inviteAcceptedAt = const Value.absent(), + Value inviteRejectedAt = const Value.absent(), + Value invited = const Value.absent(), + Value banned = const Value.absent(), + Value shadowBanned = const Value.absent(), + Value pinnedAt = const Value.absent(), + Value archivedAt = const Value.absent(), + Value isModerator = const Value.absent(), + Value?> extraData = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + required List deletedMessages, + Value rowid = const Value.absent(), + }) => MembersCompanion.insert( + userId: userId, + channelCid: channelCid, + channelRole: channelRole, + inviteAcceptedAt: inviteAcceptedAt, + inviteRejectedAt: inviteRejectedAt, + invited: invited, + banned: banned, + shadowBanned: shadowBanned, + pinnedAt: pinnedAt, + archivedAt: archivedAt, + isModerator: isModerator, + extraData: extraData, + createdAt: createdAt, + updatedAt: updatedAt, + deletedMessages: deletedMessages, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$MembersTableReferences(db, table, e))).toList(), prefetchHooksCallback: ({channelCid = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], - addJoins: < - T extends TableManagerState< + addJoins: + < + T extends TableManagerState< dynamic, dynamic, dynamic, @@ -14861,82 +14275,85 @@ class $$MembersTableTableManager extends RootTableManager< dynamic, dynamic, dynamic, - dynamic>>(state) { - if (channelCid) { - state = state.withJoin( - currentTable: table, - currentColumn: table.channelCid, - referencedTable: - $$MembersTableReferences._channelCidTable(db), - referencedColumn: - $$MembersTableReferences._channelCidTable(db).cid, - ) as T; - } - - return state; - }, + dynamic + > + >(state) { + if (channelCid) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.channelCid, + referencedTable: $$MembersTableReferences._channelCidTable(db), + referencedColumn: $$MembersTableReferences._channelCidTable(db).cid, + ) + as T; + } + + return state; + }, getPrefetchedDataCallback: (items) async { return []; }, ); }, - )); + ), + ); } -typedef $$MembersTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $MembersTable, - MemberEntity, - $$MembersTableFilterComposer, - $$MembersTableOrderingComposer, - $$MembersTableAnnotationComposer, - $$MembersTableCreateCompanionBuilder, - $$MembersTableUpdateCompanionBuilder, - (MemberEntity, $$MembersTableReferences), - MemberEntity, - PrefetchHooks Function({bool channelCid})>; -typedef $$ReadsTableCreateCompanionBuilder = ReadsCompanion Function({ - required DateTime lastRead, - required String userId, - required String channelCid, - Value unreadMessages, - Value lastReadMessageId, - Value lastDeliveredAt, - Value lastDeliveredMessageId, - Value rowid, -}); -typedef $$ReadsTableUpdateCompanionBuilder = ReadsCompanion Function({ - Value lastRead, - Value userId, - Value channelCid, - Value unreadMessages, - Value lastReadMessageId, - Value lastDeliveredAt, - Value lastDeliveredMessageId, - Value rowid, -}); - -final class $$ReadsTableReferences - extends BaseReferences<_$DriftChatDatabase, $ReadsTable, ReadEntity> { +typedef $$MembersTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $MembersTable, + MemberEntity, + $$MembersTableFilterComposer, + $$MembersTableOrderingComposer, + $$MembersTableAnnotationComposer, + $$MembersTableCreateCompanionBuilder, + $$MembersTableUpdateCompanionBuilder, + (MemberEntity, $$MembersTableReferences), + MemberEntity, + PrefetchHooks Function({bool channelCid}) + >; +typedef $$ReadsTableCreateCompanionBuilder = + ReadsCompanion Function({ + required DateTime lastRead, + required String userId, + required String channelCid, + Value unreadMessages, + Value lastReadMessageId, + Value lastDeliveredAt, + Value lastDeliveredMessageId, + Value rowid, + }); +typedef $$ReadsTableUpdateCompanionBuilder = + ReadsCompanion Function({ + Value lastRead, + Value userId, + Value channelCid, + Value unreadMessages, + Value lastReadMessageId, + Value lastDeliveredAt, + Value lastDeliveredMessageId, + Value rowid, + }); + +final class $$ReadsTableReferences extends BaseReferences<_$DriftChatDatabase, $ReadsTable, ReadEntity> { $$ReadsTableReferences(super.$_db, super.$_table, super.$_typedResult); - static $ChannelsTable _channelCidTable(_$DriftChatDatabase db) => db.channels - .createAlias($_aliasNameGenerator(db.reads.channelCid, db.channels.cid)); + static $ChannelsTable _channelCidTable(_$DriftChatDatabase db) => + db.channels.createAlias($_aliasNameGenerator(db.reads.channelCid, db.channels.cid)); $$ChannelsTableProcessedTableManager get channelCid { final $_column = $_itemColumn('channel_cid')!; - final manager = $$ChannelsTableTableManager($_db, $_db.channels) - .filter((f) => f.cid.sqlEquals($_column)); + final manager = $$ChannelsTableTableManager($_db, $_db.channels).filter((f) => f.cid.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_channelCidTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } } -class $$ReadsTableFilterComposer - extends Composer<_$DriftChatDatabase, $ReadsTable> { +class $$ReadsTableFilterComposer extends Composer<_$DriftChatDatabase, $ReadsTable> { $$ReadsTableFilterComposer({ required super.$db, required super.$table, @@ -14944,51 +14361,44 @@ class $$ReadsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get lastRead => $composableBuilder( - column: $table.lastRead, builder: (column) => ColumnFilters(column)); + ColumnFilters get lastRead => + $composableBuilder(column: $table.lastRead, builder: (column) => ColumnFilters(column)); - ColumnFilters get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnFilters(column)); + ColumnFilters get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnFilters(column)); - ColumnFilters get unreadMessages => $composableBuilder( - column: $table.unreadMessages, - builder: (column) => ColumnFilters(column)); + ColumnFilters get unreadMessages => + $composableBuilder(column: $table.unreadMessages, builder: (column) => ColumnFilters(column)); - ColumnFilters get lastReadMessageId => $composableBuilder( - column: $table.lastReadMessageId, - builder: (column) => ColumnFilters(column)); + ColumnFilters get lastReadMessageId => + $composableBuilder(column: $table.lastReadMessageId, builder: (column) => ColumnFilters(column)); - ColumnFilters get lastDeliveredAt => $composableBuilder( - column: $table.lastDeliveredAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get lastDeliveredAt => + $composableBuilder(column: $table.lastDeliveredAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get lastDeliveredMessageId => $composableBuilder( - column: $table.lastDeliveredMessageId, - builder: (column) => ColumnFilters(column)); + ColumnFilters get lastDeliveredMessageId => + $composableBuilder(column: $table.lastDeliveredMessageId, builder: (column) => ColumnFilters(column)); $$ChannelsTableFilterComposer get channelCid { final $$ChannelsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableFilterComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableFilterComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$ReadsTableOrderingComposer - extends Composer<_$DriftChatDatabase, $ReadsTable> { +class $$ReadsTableOrderingComposer extends Composer<_$DriftChatDatabase, $ReadsTable> { $$ReadsTableOrderingComposer({ required super.$db, required super.$table, @@ -14996,51 +14406,44 @@ class $$ReadsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get lastRead => $composableBuilder( - column: $table.lastRead, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get lastRead => + $composableBuilder(column: $table.lastRead, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get unreadMessages => $composableBuilder( - column: $table.unreadMessages, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get unreadMessages => + $composableBuilder(column: $table.unreadMessages, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get lastReadMessageId => $composableBuilder( - column: $table.lastReadMessageId, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get lastReadMessageId => + $composableBuilder(column: $table.lastReadMessageId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get lastDeliveredAt => $composableBuilder( - column: $table.lastDeliveredAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get lastDeliveredAt => + $composableBuilder(column: $table.lastDeliveredAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get lastDeliveredMessageId => $composableBuilder( - column: $table.lastDeliveredMessageId, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get lastDeliveredMessageId => + $composableBuilder(column: $table.lastDeliveredMessageId, builder: (column) => ColumnOrderings(column)); $$ChannelsTableOrderingComposer get channelCid { final $$ChannelsTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableOrderingComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableOrderingComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$ReadsTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $ReadsTable> { +class $$ReadsTableAnnotationComposer extends Composer<_$DriftChatDatabase, $ReadsTable> { $$ReadsTableAnnotationComposer({ required super.$db, required super.$table, @@ -15048,117 +14451,113 @@ class $$ReadsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get lastRead => - $composableBuilder(column: $table.lastRead, builder: (column) => column); + GeneratedColumn get lastRead => $composableBuilder(column: $table.lastRead, builder: (column) => column); - GeneratedColumn get userId => - $composableBuilder(column: $table.userId, builder: (column) => column); + GeneratedColumn get userId => $composableBuilder(column: $table.userId, builder: (column) => column); - GeneratedColumn get unreadMessages => $composableBuilder( - column: $table.unreadMessages, builder: (column) => column); + GeneratedColumn get unreadMessages => + $composableBuilder(column: $table.unreadMessages, builder: (column) => column); - GeneratedColumn get lastReadMessageId => $composableBuilder( - column: $table.lastReadMessageId, builder: (column) => column); + GeneratedColumn get lastReadMessageId => + $composableBuilder(column: $table.lastReadMessageId, builder: (column) => column); - GeneratedColumn get lastDeliveredAt => $composableBuilder( - column: $table.lastDeliveredAt, builder: (column) => column); + GeneratedColumn get lastDeliveredAt => + $composableBuilder(column: $table.lastDeliveredAt, builder: (column) => column); - GeneratedColumn get lastDeliveredMessageId => $composableBuilder( - column: $table.lastDeliveredMessageId, builder: (column) => column); + GeneratedColumn get lastDeliveredMessageId => + $composableBuilder(column: $table.lastDeliveredMessageId, builder: (column) => column); $$ChannelsTableAnnotationComposer get channelCid { final $$ChannelsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableAnnotationComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableAnnotationComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$ReadsTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $ReadsTable, - ReadEntity, - $$ReadsTableFilterComposer, - $$ReadsTableOrderingComposer, - $$ReadsTableAnnotationComposer, - $$ReadsTableCreateCompanionBuilder, - $$ReadsTableUpdateCompanionBuilder, - (ReadEntity, $$ReadsTableReferences), - ReadEntity, - PrefetchHooks Function({bool channelCid})> { +class $$ReadsTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $ReadsTable, + ReadEntity, + $$ReadsTableFilterComposer, + $$ReadsTableOrderingComposer, + $$ReadsTableAnnotationComposer, + $$ReadsTableCreateCompanionBuilder, + $$ReadsTableUpdateCompanionBuilder, + (ReadEntity, $$ReadsTableReferences), + ReadEntity, + PrefetchHooks Function({bool channelCid}) + > { $$ReadsTableTableManager(_$DriftChatDatabase db, $ReadsTable table) - : super(TableManagerState( + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$ReadsTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$ReadsTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$ReadsTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value lastRead = const Value.absent(), - Value userId = const Value.absent(), - Value channelCid = const Value.absent(), - Value unreadMessages = const Value.absent(), - Value lastReadMessageId = const Value.absent(), - Value lastDeliveredAt = const Value.absent(), - Value lastDeliveredMessageId = const Value.absent(), - Value rowid = const Value.absent(), - }) => - ReadsCompanion( - lastRead: lastRead, - userId: userId, - channelCid: channelCid, - unreadMessages: unreadMessages, - lastReadMessageId: lastReadMessageId, - lastDeliveredAt: lastDeliveredAt, - lastDeliveredMessageId: lastDeliveredMessageId, - rowid: rowid, - ), - createCompanionCallback: ({ - required DateTime lastRead, - required String userId, - required String channelCid, - Value unreadMessages = const Value.absent(), - Value lastReadMessageId = const Value.absent(), - Value lastDeliveredAt = const Value.absent(), - Value lastDeliveredMessageId = const Value.absent(), - Value rowid = const Value.absent(), - }) => - ReadsCompanion.insert( - lastRead: lastRead, - userId: userId, - channelCid: channelCid, - unreadMessages: unreadMessages, - lastReadMessageId: lastReadMessageId, - lastDeliveredAt: lastDeliveredAt, - lastDeliveredMessageId: lastDeliveredMessageId, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => - (e.readTable(table), $$ReadsTableReferences(db, table, e))) - .toList(), + createFilteringComposer: () => $$ReadsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$ReadsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$ReadsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value lastRead = const Value.absent(), + Value userId = const Value.absent(), + Value channelCid = const Value.absent(), + Value unreadMessages = const Value.absent(), + Value lastReadMessageId = const Value.absent(), + Value lastDeliveredAt = const Value.absent(), + Value lastDeliveredMessageId = const Value.absent(), + Value rowid = const Value.absent(), + }) => ReadsCompanion( + lastRead: lastRead, + userId: userId, + channelCid: channelCid, + unreadMessages: unreadMessages, + lastReadMessageId: lastReadMessageId, + lastDeliveredAt: lastDeliveredAt, + lastDeliveredMessageId: lastDeliveredMessageId, + rowid: rowid, + ), + createCompanionCallback: + ({ + required DateTime lastRead, + required String userId, + required String channelCid, + Value unreadMessages = const Value.absent(), + Value lastReadMessageId = const Value.absent(), + Value lastDeliveredAt = const Value.absent(), + Value lastDeliveredMessageId = const Value.absent(), + Value rowid = const Value.absent(), + }) => ReadsCompanion.insert( + lastRead: lastRead, + userId: userId, + channelCid: channelCid, + unreadMessages: unreadMessages, + lastReadMessageId: lastReadMessageId, + lastDeliveredAt: lastDeliveredAt, + lastDeliveredMessageId: lastDeliveredMessageId, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$ReadsTableReferences(db, table, e))).toList(), prefetchHooksCallback: ({channelCid = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], - addJoins: < - T extends TableManagerState< + addJoins: + < + T extends TableManagerState< dynamic, dynamic, dynamic, @@ -15169,55 +14568,59 @@ class $$ReadsTableTableManager extends RootTableManager< dynamic, dynamic, dynamic, - dynamic>>(state) { - if (channelCid) { - state = state.withJoin( - currentTable: table, - currentColumn: table.channelCid, - referencedTable: - $$ReadsTableReferences._channelCidTable(db), - referencedColumn: - $$ReadsTableReferences._channelCidTable(db).cid, - ) as T; - } - - return state; - }, + dynamic + > + >(state) { + if (channelCid) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.channelCid, + referencedTable: $$ReadsTableReferences._channelCidTable(db), + referencedColumn: $$ReadsTableReferences._channelCidTable(db).cid, + ) + as T; + } + + return state; + }, getPrefetchedDataCallback: (items) async { return []; }, ); }, - )); + ), + ); } -typedef $$ReadsTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $ReadsTable, - ReadEntity, - $$ReadsTableFilterComposer, - $$ReadsTableOrderingComposer, - $$ReadsTableAnnotationComposer, - $$ReadsTableCreateCompanionBuilder, - $$ReadsTableUpdateCompanionBuilder, - (ReadEntity, $$ReadsTableReferences), - ReadEntity, - PrefetchHooks Function({bool channelCid})>; -typedef $$ChannelQueriesTableCreateCompanionBuilder = ChannelQueriesCompanion - Function({ - required String queryHash, - required String channelCid, - Value rowid, -}); -typedef $$ChannelQueriesTableUpdateCompanionBuilder = ChannelQueriesCompanion - Function({ - Value queryHash, - Value channelCid, - Value rowid, -}); - -class $$ChannelQueriesTableFilterComposer - extends Composer<_$DriftChatDatabase, $ChannelQueriesTable> { +typedef $$ReadsTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $ReadsTable, + ReadEntity, + $$ReadsTableFilterComposer, + $$ReadsTableOrderingComposer, + $$ReadsTableAnnotationComposer, + $$ReadsTableCreateCompanionBuilder, + $$ReadsTableUpdateCompanionBuilder, + (ReadEntity, $$ReadsTableReferences), + ReadEntity, + PrefetchHooks Function({bool channelCid}) + >; +typedef $$ChannelQueriesTableCreateCompanionBuilder = + ChannelQueriesCompanion Function({ + required String queryHash, + required String channelCid, + Value rowid, + }); +typedef $$ChannelQueriesTableUpdateCompanionBuilder = + ChannelQueriesCompanion Function({ + Value queryHash, + Value channelCid, + Value rowid, + }); + +class $$ChannelQueriesTableFilterComposer extends Composer<_$DriftChatDatabase, $ChannelQueriesTable> { $$ChannelQueriesTableFilterComposer({ required super.$db, required super.$table, @@ -15225,15 +14628,14 @@ class $$ChannelQueriesTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get queryHash => $composableBuilder( - column: $table.queryHash, builder: (column) => ColumnFilters(column)); + ColumnFilters get queryHash => + $composableBuilder(column: $table.queryHash, builder: (column) => ColumnFilters(column)); - ColumnFilters get channelCid => $composableBuilder( - column: $table.channelCid, builder: (column) => ColumnFilters(column)); + ColumnFilters get channelCid => + $composableBuilder(column: $table.channelCid, builder: (column) => ColumnFilters(column)); } -class $$ChannelQueriesTableOrderingComposer - extends Composer<_$DriftChatDatabase, $ChannelQueriesTable> { +class $$ChannelQueriesTableOrderingComposer extends Composer<_$DriftChatDatabase, $ChannelQueriesTable> { $$ChannelQueriesTableOrderingComposer({ required super.$db, required super.$table, @@ -15241,15 +14643,14 @@ class $$ChannelQueriesTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get queryHash => $composableBuilder( - column: $table.queryHash, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get queryHash => + $composableBuilder(column: $table.queryHash, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get channelCid => $composableBuilder( - column: $table.channelCid, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get channelCid => + $composableBuilder(column: $table.channelCid, builder: (column) => ColumnOrderings(column)); } -class $$ChannelQueriesTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $ChannelQueriesTable> { +class $$ChannelQueriesTableAnnotationComposer extends Composer<_$DriftChatDatabase, $ChannelQueriesTable> { $$ChannelQueriesTableAnnotationComposer({ required super.$db, required super.$table, @@ -15257,106 +14658,96 @@ class $$ChannelQueriesTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get queryHash => - $composableBuilder(column: $table.queryHash, builder: (column) => column); + GeneratedColumn get queryHash => $composableBuilder(column: $table.queryHash, builder: (column) => column); - GeneratedColumn get channelCid => $composableBuilder( - column: $table.channelCid, builder: (column) => column); + GeneratedColumn get channelCid => $composableBuilder(column: $table.channelCid, builder: (column) => column); } -class $$ChannelQueriesTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $ChannelQueriesTable, - ChannelQueryEntity, - $$ChannelQueriesTableFilterComposer, - $$ChannelQueriesTableOrderingComposer, - $$ChannelQueriesTableAnnotationComposer, - $$ChannelQueriesTableCreateCompanionBuilder, - $$ChannelQueriesTableUpdateCompanionBuilder, - ( - ChannelQueryEntity, - BaseReferences<_$DriftChatDatabase, $ChannelQueriesTable, - ChannelQueryEntity> - ), - ChannelQueryEntity, - PrefetchHooks Function()> { - $$ChannelQueriesTableTableManager( - _$DriftChatDatabase db, $ChannelQueriesTable table) - : super(TableManagerState( +class $$ChannelQueriesTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $ChannelQueriesTable, + ChannelQueryEntity, + $$ChannelQueriesTableFilterComposer, + $$ChannelQueriesTableOrderingComposer, + $$ChannelQueriesTableAnnotationComposer, + $$ChannelQueriesTableCreateCompanionBuilder, + $$ChannelQueriesTableUpdateCompanionBuilder, + (ChannelQueryEntity, BaseReferences<_$DriftChatDatabase, $ChannelQueriesTable, ChannelQueryEntity>), + ChannelQueryEntity, + PrefetchHooks Function() + > { + $$ChannelQueriesTableTableManager(_$DriftChatDatabase db, $ChannelQueriesTable table) + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$ChannelQueriesTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$ChannelQueriesTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$ChannelQueriesTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value queryHash = const Value.absent(), - Value channelCid = const Value.absent(), - Value rowid = const Value.absent(), - }) => - ChannelQueriesCompanion( - queryHash: queryHash, - channelCid: channelCid, - rowid: rowid, - ), - createCompanionCallback: ({ - required String queryHash, - required String channelCid, - Value rowid = const Value.absent(), - }) => - ChannelQueriesCompanion.insert( - queryHash: queryHash, - channelCid: channelCid, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) - .toList(), + createFilteringComposer: () => $$ChannelQueriesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$ChannelQueriesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$ChannelQueriesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value queryHash = const Value.absent(), + Value channelCid = const Value.absent(), + Value rowid = const Value.absent(), + }) => ChannelQueriesCompanion( + queryHash: queryHash, + channelCid: channelCid, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String queryHash, + required String channelCid, + Value rowid = const Value.absent(), + }) => ChannelQueriesCompanion.insert( + queryHash: queryHash, + channelCid: channelCid, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), prefetchHooksCallback: null, - )); + ), + ); } -typedef $$ChannelQueriesTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $ChannelQueriesTable, - ChannelQueryEntity, - $$ChannelQueriesTableFilterComposer, - $$ChannelQueriesTableOrderingComposer, - $$ChannelQueriesTableAnnotationComposer, - $$ChannelQueriesTableCreateCompanionBuilder, - $$ChannelQueriesTableUpdateCompanionBuilder, - ( +typedef $$ChannelQueriesTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $ChannelQueriesTable, ChannelQueryEntity, - BaseReferences<_$DriftChatDatabase, $ChannelQueriesTable, - ChannelQueryEntity> - ), - ChannelQueryEntity, - PrefetchHooks Function()>; -typedef $$ConnectionEventsTableCreateCompanionBuilder - = ConnectionEventsCompanion Function({ - Value id, - required String type, - Value?> ownUser, - Value totalUnreadCount, - Value unreadChannels, - Value lastEventAt, - Value lastSyncAt, -}); -typedef $$ConnectionEventsTableUpdateCompanionBuilder - = ConnectionEventsCompanion Function({ - Value id, - Value type, - Value?> ownUser, - Value totalUnreadCount, - Value unreadChannels, - Value lastEventAt, - Value lastSyncAt, -}); - -class $$ConnectionEventsTableFilterComposer - extends Composer<_$DriftChatDatabase, $ConnectionEventsTable> { + $$ChannelQueriesTableFilterComposer, + $$ChannelQueriesTableOrderingComposer, + $$ChannelQueriesTableAnnotationComposer, + $$ChannelQueriesTableCreateCompanionBuilder, + $$ChannelQueriesTableUpdateCompanionBuilder, + (ChannelQueryEntity, BaseReferences<_$DriftChatDatabase, $ChannelQueriesTable, ChannelQueryEntity>), + ChannelQueryEntity, + PrefetchHooks Function() + >; +typedef $$ConnectionEventsTableCreateCompanionBuilder = + ConnectionEventsCompanion Function({ + Value id, + required String type, + Value?> ownUser, + Value totalUnreadCount, + Value unreadChannels, + Value lastEventAt, + Value lastSyncAt, + }); +typedef $$ConnectionEventsTableUpdateCompanionBuilder = + ConnectionEventsCompanion Function({ + Value id, + Value type, + Value?> ownUser, + Value totalUnreadCount, + Value unreadChannels, + Value lastEventAt, + Value lastSyncAt, + }); + +class $$ConnectionEventsTableFilterComposer extends Composer<_$DriftChatDatabase, $ConnectionEventsTable> { $$ConnectionEventsTableFilterComposer({ required super.$db, required super.$table, @@ -15364,35 +14755,27 @@ class $$ConnectionEventsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnFilters(column)); + ColumnFilters get type => $composableBuilder(column: $table.type, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get ownUser => $composableBuilder( - column: $table.ownUser, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get ownUser => + $composableBuilder(column: $table.ownUser, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get totalUnreadCount => $composableBuilder( - column: $table.totalUnreadCount, - builder: (column) => ColumnFilters(column)); + ColumnFilters get totalUnreadCount => + $composableBuilder(column: $table.totalUnreadCount, builder: (column) => ColumnFilters(column)); - ColumnFilters get unreadChannels => $composableBuilder( - column: $table.unreadChannels, - builder: (column) => ColumnFilters(column)); + ColumnFilters get unreadChannels => + $composableBuilder(column: $table.unreadChannels, builder: (column) => ColumnFilters(column)); - ColumnFilters get lastEventAt => $composableBuilder( - column: $table.lastEventAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get lastEventAt => + $composableBuilder(column: $table.lastEventAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get lastSyncAt => $composableBuilder( - column: $table.lastSyncAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get lastSyncAt => + $composableBuilder(column: $table.lastSyncAt, builder: (column) => ColumnFilters(column)); } -class $$ConnectionEventsTableOrderingComposer - extends Composer<_$DriftChatDatabase, $ConnectionEventsTable> { +class $$ConnectionEventsTableOrderingComposer extends Composer<_$DriftChatDatabase, $ConnectionEventsTable> { $$ConnectionEventsTableOrderingComposer({ required super.$db, required super.$table, @@ -15400,32 +14783,28 @@ class $$ConnectionEventsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get type => + $composableBuilder(column: $table.type, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get ownUser => $composableBuilder( - column: $table.ownUser, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get ownUser => + $composableBuilder(column: $table.ownUser, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get totalUnreadCount => $composableBuilder( - column: $table.totalUnreadCount, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get totalUnreadCount => + $composableBuilder(column: $table.totalUnreadCount, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get unreadChannels => $composableBuilder( - column: $table.unreadChannels, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get unreadChannels => + $composableBuilder(column: $table.unreadChannels, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get lastEventAt => $composableBuilder( - column: $table.lastEventAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get lastEventAt => + $composableBuilder(column: $table.lastEventAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get lastSyncAt => $composableBuilder( - column: $table.lastSyncAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get lastSyncAt => + $composableBuilder(column: $table.lastSyncAt, builder: (column) => ColumnOrderings(column)); } -class $$ConnectionEventsTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $ConnectionEventsTable> { +class $$ConnectionEventsTableAnnotationComposer extends Composer<_$DriftChatDatabase, $ConnectionEventsTable> { $$ConnectionEventsTableAnnotationComposer({ required super.$db, required super.$table, @@ -15433,145 +14812,123 @@ class $$ConnectionEventsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get type => - $composableBuilder(column: $table.type, builder: (column) => column); + GeneratedColumn get type => $composableBuilder(column: $table.type, builder: (column) => column); GeneratedColumnWithTypeConverter?, String> get ownUser => $composableBuilder(column: $table.ownUser, builder: (column) => column); - GeneratedColumn get totalUnreadCount => $composableBuilder( - column: $table.totalUnreadCount, builder: (column) => column); + GeneratedColumn get totalUnreadCount => + $composableBuilder(column: $table.totalUnreadCount, builder: (column) => column); - GeneratedColumn get unreadChannels => $composableBuilder( - column: $table.unreadChannels, builder: (column) => column); + GeneratedColumn get unreadChannels => + $composableBuilder(column: $table.unreadChannels, builder: (column) => column); - GeneratedColumn get lastEventAt => $composableBuilder( - column: $table.lastEventAt, builder: (column) => column); + GeneratedColumn get lastEventAt => + $composableBuilder(column: $table.lastEventAt, builder: (column) => column); - GeneratedColumn get lastSyncAt => $composableBuilder( - column: $table.lastSyncAt, builder: (column) => column); + GeneratedColumn get lastSyncAt => + $composableBuilder(column: $table.lastSyncAt, builder: (column) => column); } -class $$ConnectionEventsTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $ConnectionEventsTable, - ConnectionEventEntity, - $$ConnectionEventsTableFilterComposer, - $$ConnectionEventsTableOrderingComposer, - $$ConnectionEventsTableAnnotationComposer, - $$ConnectionEventsTableCreateCompanionBuilder, - $$ConnectionEventsTableUpdateCompanionBuilder, - ( - ConnectionEventEntity, - BaseReferences<_$DriftChatDatabase, $ConnectionEventsTable, - ConnectionEventEntity> - ), - ConnectionEventEntity, - PrefetchHooks Function()> { - $$ConnectionEventsTableTableManager( - _$DriftChatDatabase db, $ConnectionEventsTable table) - : super(TableManagerState( +class $$ConnectionEventsTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $ConnectionEventsTable, + ConnectionEventEntity, + $$ConnectionEventsTableFilterComposer, + $$ConnectionEventsTableOrderingComposer, + $$ConnectionEventsTableAnnotationComposer, + $$ConnectionEventsTableCreateCompanionBuilder, + $$ConnectionEventsTableUpdateCompanionBuilder, + (ConnectionEventEntity, BaseReferences<_$DriftChatDatabase, $ConnectionEventsTable, ConnectionEventEntity>), + ConnectionEventEntity, + PrefetchHooks Function() + > { + $$ConnectionEventsTableTableManager(_$DriftChatDatabase db, $ConnectionEventsTable table) + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$ConnectionEventsTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$ConnectionEventsTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$ConnectionEventsTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value id = const Value.absent(), - Value type = const Value.absent(), - Value?> ownUser = const Value.absent(), - Value totalUnreadCount = const Value.absent(), - Value unreadChannels = const Value.absent(), - Value lastEventAt = const Value.absent(), - Value lastSyncAt = const Value.absent(), - }) => - ConnectionEventsCompanion( - id: id, - type: type, - ownUser: ownUser, - totalUnreadCount: totalUnreadCount, - unreadChannels: unreadChannels, - lastEventAt: lastEventAt, - lastSyncAt: lastSyncAt, - ), - createCompanionCallback: ({ - Value id = const Value.absent(), - required String type, - Value?> ownUser = const Value.absent(), - Value totalUnreadCount = const Value.absent(), - Value unreadChannels = const Value.absent(), - Value lastEventAt = const Value.absent(), - Value lastSyncAt = const Value.absent(), - }) => - ConnectionEventsCompanion.insert( - id: id, - type: type, - ownUser: ownUser, - totalUnreadCount: totalUnreadCount, - unreadChannels: unreadChannels, - lastEventAt: lastEventAt, - lastSyncAt: lastSyncAt, - ), - withReferenceMapper: (p0) => p0 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) - .toList(), + createFilteringComposer: () => $$ConnectionEventsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$ConnectionEventsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$ConnectionEventsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value type = const Value.absent(), + Value?> ownUser = const Value.absent(), + Value totalUnreadCount = const Value.absent(), + Value unreadChannels = const Value.absent(), + Value lastEventAt = const Value.absent(), + Value lastSyncAt = const Value.absent(), + }) => ConnectionEventsCompanion( + id: id, + type: type, + ownUser: ownUser, + totalUnreadCount: totalUnreadCount, + unreadChannels: unreadChannels, + lastEventAt: lastEventAt, + lastSyncAt: lastSyncAt, + ), + createCompanionCallback: + ({ + Value id = const Value.absent(), + required String type, + Value?> ownUser = const Value.absent(), + Value totalUnreadCount = const Value.absent(), + Value unreadChannels = const Value.absent(), + Value lastEventAt = const Value.absent(), + Value lastSyncAt = const Value.absent(), + }) => ConnectionEventsCompanion.insert( + id: id, + type: type, + ownUser: ownUser, + totalUnreadCount: totalUnreadCount, + unreadChannels: unreadChannels, + lastEventAt: lastEventAt, + lastSyncAt: lastSyncAt, + ), + withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), prefetchHooksCallback: null, - )); + ), + ); } -typedef $$ConnectionEventsTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $ConnectionEventsTable, - ConnectionEventEntity, - $$ConnectionEventsTableFilterComposer, - $$ConnectionEventsTableOrderingComposer, - $$ConnectionEventsTableAnnotationComposer, - $$ConnectionEventsTableCreateCompanionBuilder, - $$ConnectionEventsTableUpdateCompanionBuilder, - ( +typedef $$ConnectionEventsTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $ConnectionEventsTable, + ConnectionEventEntity, + $$ConnectionEventsTableFilterComposer, + $$ConnectionEventsTableOrderingComposer, + $$ConnectionEventsTableAnnotationComposer, + $$ConnectionEventsTableCreateCompanionBuilder, + $$ConnectionEventsTableUpdateCompanionBuilder, + (ConnectionEventEntity, BaseReferences<_$DriftChatDatabase, $ConnectionEventsTable, ConnectionEventEntity>), ConnectionEventEntity, - BaseReferences<_$DriftChatDatabase, $ConnectionEventsTable, - ConnectionEventEntity> - ), - ConnectionEventEntity, - PrefetchHooks Function()>; + PrefetchHooks Function() + >; class $DriftChatDatabaseManager { final _$DriftChatDatabase _db; $DriftChatDatabaseManager(this._db); - $$ChannelsTableTableManager get channels => - $$ChannelsTableTableManager(_db, _db.channels); - $$MessagesTableTableManager get messages => - $$MessagesTableTableManager(_db, _db.messages); - $$DraftMessagesTableTableManager get draftMessages => - $$DraftMessagesTableTableManager(_db, _db.draftMessages); - $$LocationsTableTableManager get locations => - $$LocationsTableTableManager(_db, _db.locations); - $$PinnedMessagesTableTableManager get pinnedMessages => - $$PinnedMessagesTableTableManager(_db, _db.pinnedMessages); - $$PollsTableTableManager get polls => - $$PollsTableTableManager(_db, _db.polls); - $$PollVotesTableTableManager get pollVotes => - $$PollVotesTableTableManager(_db, _db.pollVotes); + $$ChannelsTableTableManager get channels => $$ChannelsTableTableManager(_db, _db.channels); + $$MessagesTableTableManager get messages => $$MessagesTableTableManager(_db, _db.messages); + $$DraftMessagesTableTableManager get draftMessages => $$DraftMessagesTableTableManager(_db, _db.draftMessages); + $$LocationsTableTableManager get locations => $$LocationsTableTableManager(_db, _db.locations); + $$PinnedMessagesTableTableManager get pinnedMessages => $$PinnedMessagesTableTableManager(_db, _db.pinnedMessages); + $$PollsTableTableManager get polls => $$PollsTableTableManager(_db, _db.polls); + $$PollVotesTableTableManager get pollVotes => $$PollVotesTableTableManager(_db, _db.pollVotes); $$PinnedMessageReactionsTableTableManager get pinnedMessageReactions => - $$PinnedMessageReactionsTableTableManager( - _db, _db.pinnedMessageReactions); - $$ReactionsTableTableManager get reactions => - $$ReactionsTableTableManager(_db, _db.reactions); - $$UsersTableTableManager get users => - $$UsersTableTableManager(_db, _db.users); - $$MembersTableTableManager get members => - $$MembersTableTableManager(_db, _db.members); - $$ReadsTableTableManager get reads => - $$ReadsTableTableManager(_db, _db.reads); - $$ChannelQueriesTableTableManager get channelQueries => - $$ChannelQueriesTableTableManager(_db, _db.channelQueries); + $$PinnedMessageReactionsTableTableManager(_db, _db.pinnedMessageReactions); + $$ReactionsTableTableManager get reactions => $$ReactionsTableTableManager(_db, _db.reactions); + $$UsersTableTableManager get users => $$UsersTableTableManager(_db, _db.users); + $$MembersTableTableManager get members => $$MembersTableTableManager(_db, _db.members); + $$ReadsTableTableManager get reads => $$ReadsTableTableManager(_db, _db.reads); + $$ChannelQueriesTableTableManager get channelQueries => $$ChannelQueriesTableTableManager(_db, _db.channelQueries); $$ConnectionEventsTableTableManager get connectionEvents => $$ConnectionEventsTableTableManager(_db, _db.connectionEvents); } diff --git a/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart index ca31c3bb2d..0af7d3a543 100644 --- a/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart +++ b/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart @@ -25,13 +25,15 @@ class SharedDB { if (connectionMode == ConnectionMode.background) { return DriftChatDatabase( userId, - DatabaseConnection.delayed(Future(() async { - final isolate = await _createMoorIsolate( - dbName, - logStatements: logStatements, - ); - return isolate.connect(); - })), + DatabaseConnection.delayed( + Future(() async { + final isolate = await _createMoorIsolate( + dbName, + logStatements: logStatements, + ); + return isolate.connect(); + }), + ), ); } @@ -64,10 +66,12 @@ class SharedDB { } static void _startBackground(_IsolateStartRequest request) { - final executor = LazyDatabase(() async => NativeDatabase( - File(request.targetPath), - logStatements: request.logStatements, - )); + final executor = LazyDatabase( + () async => NativeDatabase( + File(request.targetPath), + logStatements: request.logStatements, + ), + ); final moorIsolate = DriftIsolate.inCurrent( () => DatabaseConnection(executor), ); diff --git a/packages/stream_chat_persistence/lib/src/entity/channel_queries.dart b/packages/stream_chat_persistence/lib/src/entity/channel_queries.dart index a9d3dc6cc0..44829c0fa0 100644 --- a/packages/stream_chat_persistence/lib/src/entity/channel_queries.dart +++ b/packages/stream_chat_persistence/lib/src/entity/channel_queries.dart @@ -12,7 +12,7 @@ class ChannelQueries extends Table { @override Set get primaryKey => { - queryHash, - channelCid, - }; + queryHash, + channelCid, + }; } diff --git a/packages/stream_chat_persistence/lib/src/entity/channels.dart b/packages/stream_chat_persistence/lib/src/entity/channels.dart index d7e6cc4112..1e417e3ab0 100644 --- a/packages/stream_chat_persistence/lib/src/entity/channels.dart +++ b/packages/stream_chat_persistence/lib/src/entity/channels.dart @@ -15,8 +15,7 @@ class Channels extends Table { TextColumn get cid => text()(); /// List of user permissions on this channel - TextColumn get ownCapabilities => - text().nullable().map(ListConverter())(); + TextColumn get ownCapabilities => text().nullable().map(ListConverter())(); /// The channel configuration data TextColumn get config => text().map(MapConverter())(); diff --git a/packages/stream_chat_persistence/lib/src/entity/draft_messages.dart b/packages/stream_chat_persistence/lib/src/entity/draft_messages.dart index 0105aec3a1..6a9054eec1 100644 --- a/packages/stream_chat_persistence/lib/src/entity/draft_messages.dart +++ b/packages/stream_chat_persistence/lib/src/entity/draft_messages.dart @@ -24,9 +24,7 @@ class DraftMessages extends Table { TextColumn get mentionedUsers => text().map(ListConverter())(); /// The ID of the parent message, if the message is a thread reply. - TextColumn get parentId => text() - .nullable() - .references(Messages, #id, onDelete: KeyAction.cascade)(); + TextColumn get parentId => text().nullable().references(Messages, #id, onDelete: KeyAction.cascade)(); /// The ID of the quoted message, if the message is a quoted reply. TextColumn get quotedMessageId => text().nullable()(); @@ -47,8 +45,7 @@ class DraftMessages extends Table { DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); /// The channel cid of which this message is part of - TextColumn get channelCid => - text().references(Channels, #cid, onDelete: KeyAction.cascade)(); + TextColumn get channelCid => text().references(Channels, #cid, onDelete: KeyAction.cascade)(); /// Message custom extraData TextColumn get extraData => text().nullable().map(MapConverter())(); diff --git a/packages/stream_chat_persistence/lib/src/entity/locations.dart b/packages/stream_chat_persistence/lib/src/entity/locations.dart index b12f448b8b..93b0678ec9 100644 --- a/packages/stream_chat_persistence/lib/src/entity/locations.dart +++ b/packages/stream_chat_persistence/lib/src/entity/locations.dart @@ -7,14 +7,10 @@ import 'package:stream_chat_persistence/src/entity/messages.dart'; @DataClassName('LocationEntity') class Locations extends Table { /// The channel CID where the location is shared - TextColumn get channelCid => text() - .nullable() - .references(Channels, #cid, onDelete: KeyAction.cascade)(); + TextColumn get channelCid => text().nullable().references(Channels, #cid, onDelete: KeyAction.cascade)(); /// The ID of the message that contains this shared location - TextColumn get messageId => text() - .nullable() - .references(Messages, #id, onDelete: KeyAction.cascade)(); + TextColumn get messageId => text().nullable().references(Messages, #id, onDelete: KeyAction.cascade)(); /// The ID of the user who shared the location TextColumn get userId => text().nullable()(); diff --git a/packages/stream_chat_persistence/lib/src/entity/members.dart b/packages/stream_chat_persistence/lib/src/entity/members.dart index 9e3734b38c..801c44c594 100644 --- a/packages/stream_chat_persistence/lib/src/entity/members.dart +++ b/packages/stream_chat_persistence/lib/src/entity/members.dart @@ -10,8 +10,7 @@ class Members extends Table { TextColumn get userId => text()(); /// The channel cid of which this user is part of - TextColumn get channelCid => - text().references(Channels, #cid, onDelete: KeyAction.cascade)(); + TextColumn get channelCid => text().references(Channels, #cid, onDelete: KeyAction.cascade)(); /// The role of the user in the channel TextColumn get channelRole => text().nullable()(); @@ -32,12 +31,10 @@ class Members extends Table { BoolColumn get shadowBanned => boolean().withDefault(const Constant(false))(); /// The date at which the channel was pinned by the member - DateTimeColumn get pinnedAt => - dateTime().nullable().withDefault(const Constant(null))(); + DateTimeColumn get pinnedAt => dateTime().nullable().withDefault(const Constant(null))(); /// The date at which the channel was archived by the member - DateTimeColumn get archivedAt => - dateTime().nullable().withDefault(const Constant(null))(); + DateTimeColumn get archivedAt => dateTime().nullable().withDefault(const Constant(null))(); /// True if the user is a moderator of the channel BoolColumn get isModerator => boolean().withDefault(const Constant(false))(); diff --git a/packages/stream_chat_persistence/lib/src/entity/messages.dart b/packages/stream_chat_persistence/lib/src/entity/messages.dart index aea14217f8..6ede2f9f12 100644 --- a/packages/stream_chat_persistence/lib/src/entity/messages.dart +++ b/packages/stream_chat_persistence/lib/src/entity/messages.dart @@ -28,8 +28,7 @@ class Messages extends Table { TextColumn get mentionedUsers => text().map(ListConverter())(); /// A map describing the reaction group for every reaction - TextColumn get reactionGroups => - text().map(ReactionGroupsConverter()).nullable()(); + TextColumn get reactionGroups => text().map(ReactionGroupsConverter()).nullable()(); /// The ID of the parent message, if the message is a thread reply. TextColumn get parentId => text().nullable()(); @@ -124,16 +123,13 @@ class Messages extends Table { TextColumn get pinnedByUserId => text().nullable()(); /// The channel cid of which this message is part of - TextColumn get channelCid => - text().references(Channels, #cid, onDelete: KeyAction.cascade)(); + TextColumn get channelCid => text().references(Channels, #cid, onDelete: KeyAction.cascade)(); /// A Map of [messageText] translations. - TextColumn get i18n => - text().nullable().map(NullableMapConverter())(); + TextColumn get i18n => text().nullable().map(NullableMapConverter())(); /// The list of user ids that should be able to see the message. - TextColumn get restrictedVisibility => - text().nullable().map(ListConverter())(); + TextColumn get restrictedVisibility => text().nullable().map(ListConverter())(); /// Message custom extraData TextColumn get extraData => text().nullable().map(MapConverter())(); diff --git a/packages/stream_chat_persistence/lib/src/entity/pinned_message_reactions.dart b/packages/stream_chat_persistence/lib/src/entity/pinned_message_reactions.dart index 6e490cf8fc..97fa438f95 100644 --- a/packages/stream_chat_persistence/lib/src/entity/pinned_message_reactions.dart +++ b/packages/stream_chat_persistence/lib/src/entity/pinned_message_reactions.dart @@ -8,7 +8,5 @@ import 'package:stream_chat_persistence/src/entity/reactions.dart'; class PinnedMessageReactions extends Reactions { /// The messageId to which the reaction belongs @override - TextColumn get messageId => text() - .nullable() - .references(PinnedMessages, #id, onDelete: KeyAction.cascade)(); + TextColumn get messageId => text().nullable().references(PinnedMessages, #id, onDelete: KeyAction.cascade)(); } diff --git a/packages/stream_chat_persistence/lib/src/entity/poll_votes.dart b/packages/stream_chat_persistence/lib/src/entity/poll_votes.dart index 5087b5a0df..f6a069d8d0 100644 --- a/packages/stream_chat_persistence/lib/src/entity/poll_votes.dart +++ b/packages/stream_chat_persistence/lib/src/entity/poll_votes.dart @@ -9,8 +9,7 @@ class PollVotes extends Table { TextColumn get id => text().nullable()(); /// The unique identifier of the poll the vote belongs to. - TextColumn get pollId => - text().nullable().references(Polls, #id, onDelete: KeyAction.cascade)(); + TextColumn get pollId => text().nullable().references(Polls, #id, onDelete: KeyAction.cascade)(); /// The unique identifier of the option selected in the poll. /// diff --git a/packages/stream_chat_persistence/lib/src/entity/polls.dart b/packages/stream_chat_persistence/lib/src/entity/polls.dart index f377301dba..ee4d99e85c 100644 --- a/packages/stream_chat_persistence/lib/src/entity/polls.dart +++ b/packages/stream_chat_persistence/lib/src/entity/polls.dart @@ -22,15 +22,13 @@ class Polls extends Table { /// Represents the visibility of the voting process. /// /// Defaults to 'public'. - TextColumn get votingVisibility => text() - .map(const VotingVisibilityConverter()) - .withDefault(const Constant('public'))(); + TextColumn get votingVisibility => + text().map(const VotingVisibilityConverter()).withDefault(const Constant('public'))(); /// If true, only unique votes are allowed. /// /// Defaults to false. - BoolColumn get enforceUniqueVote => - boolean().withDefault(const Constant(false))(); + BoolColumn get enforceUniqueVote => boolean().withDefault(const Constant(false))(); /// The maximum number of votes allowed per user. IntColumn get maxVotesAllowed => integer().nullable()(); @@ -38,8 +36,7 @@ class Polls extends Table { /// If true, users can suggest their own options. /// /// Defaults to false. - BoolColumn get allowUserSuggestedOptions => - boolean().withDefault(const Constant(false))(); + BoolColumn get allowUserSuggestedOptions => boolean().withDefault(const Constant(false))(); /// If true, users can provide their own answers/comments. /// diff --git a/packages/stream_chat_persistence/lib/src/entity/reactions.dart b/packages/stream_chat_persistence/lib/src/entity/reactions.dart index 64d23bf979..b43044e772 100644 --- a/packages/stream_chat_persistence/lib/src/entity/reactions.dart +++ b/packages/stream_chat_persistence/lib/src/entity/reactions.dart @@ -10,9 +10,7 @@ class Reactions extends Table { TextColumn get userId => text().nullable()(); /// The messageId to which the reaction belongs - TextColumn get messageId => text() - .nullable() - .references(Messages, #id, onDelete: KeyAction.cascade)(); + TextColumn get messageId => text().nullable().references(Messages, #id, onDelete: KeyAction.cascade)(); /// The type of the reaction TextColumn get type => text()(); @@ -34,8 +32,8 @@ class Reactions extends Table { @override Set get primaryKey => { - messageId, - type, - userId, - }; + messageId, + type, + userId, + }; } diff --git a/packages/stream_chat_persistence/lib/src/entity/reads.dart b/packages/stream_chat_persistence/lib/src/entity/reads.dart index 2a842d3d69..aadac4c69c 100644 --- a/packages/stream_chat_persistence/lib/src/entity/reads.dart +++ b/packages/stream_chat_persistence/lib/src/entity/reads.dart @@ -12,8 +12,7 @@ class Reads extends Table { TextColumn get userId => text()(); /// The channel cid of which this read belongs - TextColumn get channelCid => - text().references(Channels, #cid, onDelete: KeyAction.cascade)(); + TextColumn get channelCid => text().references(Channels, #cid, onDelete: KeyAction.cascade)(); /// Number of unread messages IntColumn get unreadMessages => integer().withDefault(const Constant(0))(); @@ -29,7 +28,7 @@ class Reads extends Table { @override Set get primaryKey => { - userId, - channelCid, - }; + userId, + channelCid, + }; } diff --git a/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart index 60c0910fba..35b3bc1d84 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart @@ -32,34 +32,33 @@ extension ChannelEntityX on ChannelEntity { List reads = const [], List messages = const [], List pinnedMessages = const [], - }) => - ChannelState( - members: members, - read: reads, - messages: messages, - pinnedMessages: pinnedMessages, - channel: toChannelModel(createdBy: createdBy), - ); + }) => ChannelState( + members: members, + read: reads, + messages: messages, + pinnedMessages: pinnedMessages, + channel: toChannelModel(createdBy: createdBy), + ); } /// Useful mapping functions for [ChannelModel] extension ChannelModelX on ChannelModel { /// Maps a [ChannelModel] into [ChannelEntity] ChannelEntity toEntity() => ChannelEntity( - id: id, - type: type, - cid: cid, - ownCapabilities: ownCapabilities, - config: config.toJson(), - frozen: frozen, - lastMessageAt: lastMessageAt, - createdAt: createdAt, - updatedAt: updatedAt, - deletedAt: deletedAt, - memberCount: memberCount, - messageCount: messageCount, - createdById: createdBy?.id, - filterTags: filterTags, - extraData: extraData, - ); + id: id, + type: type, + cid: cid, + ownCapabilities: ownCapabilities, + config: config.toJson(), + frozen: frozen, + lastMessageAt: lastMessageAt, + createdAt: createdAt, + updatedAt: updatedAt, + deletedAt: deletedAt, + memberCount: memberCount, + messageCount: messageCount, + createdById: createdBy?.id, + filterTags: filterTags, + extraData: extraData, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/draft_message_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/draft_message_mapper.dart index f4ae5b31ce..9dd08a0d00 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/draft_message_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/draft_message_mapper.dart @@ -47,23 +47,23 @@ extension DraftMessageEntityX on DraftMessageEntity { extension DraftMessageX on Draft { /// Maps a [DraftMessage] into [DraftMessageEntity] DraftMessageEntity toEntity() => DraftMessageEntity( - id: message.id, - channelCid: channelCid, - messageText: message.text, - type: message.type, - createdAt: createdAt, - attachments: message.attachments.map((it) { - return jsonEncode(it.toData()); - }).toList(), - parentId: parentId, - showInChannel: message.showInChannel, - mentionedUsers: message.mentionedUsers.map((e) { - return jsonEncode(e.toJson()); - }).toList(), - quotedMessageId: message.quotedMessageId, - silent: message.silent, - command: message.command, - pollId: message.pollId, - extraData: message.extraData, - ); + id: message.id, + channelCid: channelCid, + messageText: message.text, + type: message.type, + createdAt: createdAt, + attachments: message.attachments.map((it) { + return jsonEncode(it.toData()); + }).toList(), + parentId: parentId, + showInChannel: message.showInChannel, + mentionedUsers: message.mentionedUsers.map((e) { + return jsonEncode(e.toJson()); + }).toList(), + quotedMessageId: message.quotedMessageId, + silent: message.silent, + command: message.command, + pollId: message.pollId, + extraData: message.extraData, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart index 0c3adb7b07..43a420b060 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart @@ -5,10 +5,10 @@ import 'package:stream_chat_persistence/src/db/drift_chat_database.dart'; extension ConnectionEventX on ConnectionEventEntity { /// Maps a [ConnectionEventEntity] into [Event] Event toEvent() => Event( - type: type, - createdAt: lastEventAt, - me: ownUser != null ? OwnUser.fromJson(ownUser!) : null, - totalUnreadCount: totalUnreadCount, - unreadChannels: unreadChannels, - ); + type: type, + createdAt: lastEventAt, + me: ownUser != null ? OwnUser.fromJson(ownUser!) : null, + totalUnreadCount: totalUnreadCount, + unreadChannels: unreadChannels, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/location_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/location_mapper.dart index bcbc18be1b..35985f9132 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/location_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/location_mapper.dart @@ -7,34 +7,33 @@ extension LocationEntityX on LocationEntity { Location toLocation({ ChannelModel? channel, Message? message, - }) => - Location( - channelCid: channelCid, - channel: channel, - messageId: messageId, - message: message, - userId: userId, - latitude: latitude, - longitude: longitude, - createdByDeviceId: createdByDeviceId, - endAt: endAt, - createdAt: createdAt, - updatedAt: updatedAt, - ); + }) => Location( + channelCid: channelCid, + channel: channel, + messageId: messageId, + message: message, + userId: userId, + latitude: latitude, + longitude: longitude, + createdByDeviceId: createdByDeviceId, + endAt: endAt, + createdAt: createdAt, + updatedAt: updatedAt, + ); } /// Useful mapping functions for [Location] extension LocationX on Location { /// Maps a [Location] into [LocationEntity] LocationEntity toEntity() => LocationEntity( - channelCid: channelCid, - messageId: messageId, - userId: userId, - latitude: latitude, - longitude: longitude, - createdByDeviceId: createdByDeviceId, - endAt: endAt, - createdAt: createdAt, - updatedAt: updatedAt, - ); + channelCid: channelCid, + messageId: messageId, + userId: userId, + latitude: latitude, + longitude: longitude, + createdByDeviceId: createdByDeviceId, + endAt: endAt, + createdAt: createdAt, + updatedAt: updatedAt, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart index f37aaf1bd4..26f7df3015 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart @@ -5,42 +5,42 @@ import 'package:stream_chat_persistence/src/db/drift_chat_database.dart'; extension MemberEntityX on MemberEntity { /// Maps a [MemberEntity] into [Member] Member toMember({User? user}) => Member( - user: user, - userId: userId, - banned: banned, - shadowBanned: shadowBanned, - updatedAt: updatedAt, - createdAt: createdAt, - channelRole: channelRole, - inviteAcceptedAt: inviteAcceptedAt, - invited: invited, - inviteRejectedAt: inviteRejectedAt, - pinnedAt: pinnedAt, - archivedAt: archivedAt, - isModerator: isModerator, - deletedMessages: deletedMessages, - extraData: extraData ?? {}, - ); + user: user, + userId: userId, + banned: banned, + shadowBanned: shadowBanned, + updatedAt: updatedAt, + createdAt: createdAt, + channelRole: channelRole, + inviteAcceptedAt: inviteAcceptedAt, + invited: invited, + inviteRejectedAt: inviteRejectedAt, + pinnedAt: pinnedAt, + archivedAt: archivedAt, + isModerator: isModerator, + deletedMessages: deletedMessages, + extraData: extraData ?? {}, + ); } /// Useful mapping functions for [Member] extension MemberX on Member { /// Maps a [Member] into [MemberEntity] MemberEntity toEntity({required String cid}) => MemberEntity( - userId: user!.id, - banned: banned, - shadowBanned: shadowBanned, - channelCid: cid, - createdAt: createdAt, - isModerator: isModerator, - inviteRejectedAt: inviteRejectedAt, - invited: invited, - inviteAcceptedAt: inviteAcceptedAt, - pinnedAt: pinnedAt, - archivedAt: archivedAt, - channelRole: channelRole, - updatedAt: updatedAt, - deletedMessages: deletedMessages, - extraData: extraData, - ); + userId: user!.id, + banned: banned, + shadowBanned: shadowBanned, + channelCid: cid, + createdAt: createdAt, + isModerator: isModerator, + inviteRejectedAt: inviteRejectedAt, + invited: invited, + inviteAcceptedAt: inviteAcceptedAt, + pinnedAt: pinnedAt, + archivedAt: archivedAt, + channelRole: channelRole, + updatedAt: updatedAt, + deletedMessages: deletedMessages, + extraData: extraData, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart index e6d3402278..584ac6f222 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart @@ -15,87 +15,85 @@ extension MessageEntityX on MessageEntity { Poll? poll, Draft? draft, Location? sharedLocation, - }) => - Message( - shadowed: shadowed, - latestReactions: latestReactions, - ownReactions: ownReactions, - attachments: attachments.map((it) { - final json = jsonDecode(it); - return Attachment.fromData(json); - }).toList(), - extraData: extraData ?? {}, - createdAt: remoteCreatedAt, - localCreatedAt: localCreatedAt, - updatedAt: remoteUpdatedAt, - localUpdatedAt: localUpdatedAt, - deletedAt: remoteDeletedAt, - localDeletedAt: localDeletedAt, - deletedForMe: deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt, - id: id, - type: type, - state: MessageState.fromJson(jsonDecode(state)), - command: command, - parentId: parentId, - quotedMessageId: quotedMessageId, - quotedMessage: quotedMessage, - pollId: pollId, - poll: poll, - reactionGroups: reactionGroups, - replyCount: replyCount, - showInChannel: showInChannel, - text: messageText, - user: user, - channelRole: channelRole, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedBy: pinnedBy, - mentionedUsers: - mentionedUsers.map((e) => User.fromJson(jsonDecode(e))).toList(), - i18n: i18n, - restrictedVisibility: restrictedVisibility, - draft: draft, - sharedLocation: sharedLocation, - ); + }) => Message( + shadowed: shadowed, + latestReactions: latestReactions, + ownReactions: ownReactions, + attachments: attachments.map((it) { + final json = jsonDecode(it); + return Attachment.fromData(json); + }).toList(), + extraData: extraData ?? {}, + createdAt: remoteCreatedAt, + localCreatedAt: localCreatedAt, + updatedAt: remoteUpdatedAt, + localUpdatedAt: localUpdatedAt, + deletedAt: remoteDeletedAt, + localDeletedAt: localDeletedAt, + deletedForMe: deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt, + id: id, + type: type, + state: MessageState.fromJson(jsonDecode(state)), + command: command, + parentId: parentId, + quotedMessageId: quotedMessageId, + quotedMessage: quotedMessage, + pollId: pollId, + poll: poll, + reactionGroups: reactionGroups, + replyCount: replyCount, + showInChannel: showInChannel, + text: messageText, + user: user, + channelRole: channelRole, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedBy: pinnedBy, + mentionedUsers: mentionedUsers.map((e) => User.fromJson(jsonDecode(e))).toList(), + i18n: i18n, + restrictedVisibility: restrictedVisibility, + draft: draft, + sharedLocation: sharedLocation, + ); } /// Useful mapping functions for [Message] extension MessageX on Message { /// Maps a [Message] into [MessageEntity] MessageEntity toEntity({required String cid}) => MessageEntity( - id: id, - attachments: attachments.map((it) => jsonEncode(it.toData())).toList(), - channelCid: cid, - type: type, - parentId: parentId, - quotedMessageId: quotedMessageId, - pollId: pollId, - command: command, - remoteCreatedAt: remoteCreatedAt, - localCreatedAt: localCreatedAt, - shadowed: shadowed, - showInChannel: showInChannel, - replyCount: replyCount, - reactionGroups: reactionGroups, - mentionedUsers: mentionedUsers.map(jsonEncode).toList(), - state: jsonEncode(state.toJson()), - remoteUpdatedAt: remoteUpdatedAt, - localUpdatedAt: localUpdatedAt, - extraData: extraData, - userId: user?.id, - channelRole: channelRole, - remoteDeletedAt: remoteDeletedAt, - localDeletedAt: localDeletedAt, - deletedForMe: deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt, - messageText: text, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedByUserId: pinnedBy?.id, - i18n: i18n, - restrictedVisibility: restrictedVisibility, - ); + id: id, + attachments: attachments.map((it) => jsonEncode(it.toData())).toList(), + channelCid: cid, + type: type, + parentId: parentId, + quotedMessageId: quotedMessageId, + pollId: pollId, + command: command, + remoteCreatedAt: remoteCreatedAt, + localCreatedAt: localCreatedAt, + shadowed: shadowed, + showInChannel: showInChannel, + replyCount: replyCount, + reactionGroups: reactionGroups, + mentionedUsers: mentionedUsers.map(jsonEncode).toList(), + state: jsonEncode(state.toJson()), + remoteUpdatedAt: remoteUpdatedAt, + localUpdatedAt: localUpdatedAt, + extraData: extraData, + userId: user?.id, + channelRole: channelRole, + remoteDeletedAt: remoteDeletedAt, + localDeletedAt: localDeletedAt, + deletedForMe: deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt, + messageText: text, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedByUserId: pinnedBy?.id, + i18n: i18n, + restrictedVisibility: restrictedVisibility, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart index 2cb11d3422..90a06b5f55 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart @@ -15,88 +15,85 @@ extension PinnedMessageEntityX on PinnedMessageEntity { Poll? poll, Draft? draft, Location? sharedLocation, - }) => - Message( - shadowed: shadowed, - latestReactions: latestReactions, - ownReactions: ownReactions, - attachments: attachments.map((it) { - final json = jsonDecode(it); - return Attachment.fromData(json); - }).toList(), - extraData: extraData ?? {}, - createdAt: remoteCreatedAt, - localCreatedAt: localCreatedAt, - updatedAt: remoteUpdatedAt, - localUpdatedAt: localUpdatedAt, - deletedAt: remoteDeletedAt, - localDeletedAt: localDeletedAt, - deletedForMe: deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt, - id: id, - type: type, - state: MessageState.fromJson(jsonDecode(state)), - command: command, - parentId: parentId, - quotedMessageId: quotedMessageId, - quotedMessage: quotedMessage, - pollId: pollId, - poll: poll, - reactionGroups: reactionGroups, - replyCount: replyCount, - showInChannel: showInChannel, - text: messageText, - user: user, - channelRole: channelRole, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedBy: pinnedBy, - mentionedUsers: - mentionedUsers.map((e) => User.fromJson(jsonDecode(e))).toList(), - i18n: i18n, - restrictedVisibility: restrictedVisibility, - draft: draft, - sharedLocation: sharedLocation, - ); + }) => Message( + shadowed: shadowed, + latestReactions: latestReactions, + ownReactions: ownReactions, + attachments: attachments.map((it) { + final json = jsonDecode(it); + return Attachment.fromData(json); + }).toList(), + extraData: extraData ?? {}, + createdAt: remoteCreatedAt, + localCreatedAt: localCreatedAt, + updatedAt: remoteUpdatedAt, + localUpdatedAt: localUpdatedAt, + deletedAt: remoteDeletedAt, + localDeletedAt: localDeletedAt, + deletedForMe: deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt, + id: id, + type: type, + state: MessageState.fromJson(jsonDecode(state)), + command: command, + parentId: parentId, + quotedMessageId: quotedMessageId, + quotedMessage: quotedMessage, + pollId: pollId, + poll: poll, + reactionGroups: reactionGroups, + replyCount: replyCount, + showInChannel: showInChannel, + text: messageText, + user: user, + channelRole: channelRole, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedBy: pinnedBy, + mentionedUsers: mentionedUsers.map((e) => User.fromJson(jsonDecode(e))).toList(), + i18n: i18n, + restrictedVisibility: restrictedVisibility, + draft: draft, + sharedLocation: sharedLocation, + ); } /// Useful mapping functions for [Message] extension PMessageX on Message { /// Maps a [Message] into [PinnedMessageEntity] - PinnedMessageEntity toPinnedEntity({required String cid}) => - PinnedMessageEntity( - id: id, - attachments: attachments.map((it) => jsonEncode(it.toData())).toList(), - channelCid: cid, - type: type, - parentId: parentId, - quotedMessageId: quotedMessageId, - pollId: pollId, - command: command, - remoteCreatedAt: remoteCreatedAt, - localCreatedAt: localCreatedAt, - shadowed: shadowed, - showInChannel: showInChannel, - replyCount: replyCount, - reactionGroups: reactionGroups, - mentionedUsers: mentionedUsers.map(jsonEncode).toList(), - state: jsonEncode(state.toJson()), - remoteUpdatedAt: remoteUpdatedAt, - localUpdatedAt: localUpdatedAt, - extraData: extraData, - userId: user?.id, - channelRole: channelRole, - remoteDeletedAt: remoteDeletedAt, - localDeletedAt: localDeletedAt, - deletedForMe: deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt, - messageText: text, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedByUserId: pinnedBy?.id, - i18n: i18n, - restrictedVisibility: restrictedVisibility, - ); + PinnedMessageEntity toPinnedEntity({required String cid}) => PinnedMessageEntity( + id: id, + attachments: attachments.map((it) => jsonEncode(it.toData())).toList(), + channelCid: cid, + type: type, + parentId: parentId, + quotedMessageId: quotedMessageId, + pollId: pollId, + command: command, + remoteCreatedAt: remoteCreatedAt, + localCreatedAt: localCreatedAt, + shadowed: shadowed, + showInChannel: showInChannel, + replyCount: replyCount, + reactionGroups: reactionGroups, + mentionedUsers: mentionedUsers.map(jsonEncode).toList(), + state: jsonEncode(state.toJson()), + remoteUpdatedAt: remoteUpdatedAt, + localUpdatedAt: localUpdatedAt, + extraData: extraData, + userId: user?.id, + channelRole: channelRole, + remoteDeletedAt: remoteDeletedAt, + localDeletedAt: localDeletedAt, + deletedForMe: deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt, + messageText: text, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedByUserId: pinnedBy?.id, + i18n: i18n, + restrictedVisibility: restrictedVisibility, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/pinned_message_reaction_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/pinned_message_reaction_mapper.dart index 00deda65d2..1802280fd5 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/pinned_message_reaction_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/pinned_message_reaction_mapper.dart @@ -5,29 +5,29 @@ import 'package:stream_chat_persistence/src/db/drift_chat_database.dart'; extension PinnedMessageReactionEntityX on PinnedMessageReactionEntity { /// Maps a [PinnedMessageReactionEntity] into [Reaction] Reaction toReaction({User? user}) => Reaction( - type: type, - userId: userId, - user: user, - messageId: messageId, - score: score, - emojiCode: emojiCode, - createdAt: createdAt, - updatedAt: updatedAt, - extraData: extraData ?? {}, - ); + type: type, + userId: userId, + user: user, + messageId: messageId, + score: score, + emojiCode: emojiCode, + createdAt: createdAt, + updatedAt: updatedAt, + extraData: extraData ?? {}, + ); } /// Useful mapping functions for [Reaction] extension PReactionX on Reaction { /// Maps a [Reaction] into [ReactionEntity] PinnedMessageReactionEntity toPinnedEntity() => PinnedMessageReactionEntity( - type: type, - userId: userId ?? user?.id, - messageId: messageId, - score: score, - emojiCode: emojiCode, - createdAt: createdAt, - updatedAt: updatedAt, - extraData: extraData, - ); + type: type, + userId: userId ?? user?.id, + messageId: messageId, + score: score, + emojiCode: emojiCode, + createdAt: createdAt, + updatedAt: updatedAt, + extraData: extraData, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/poll_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/poll_mapper.dart index 451b5e853c..2b9a388ce9 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/poll_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/poll_mapper.dart @@ -45,22 +45,22 @@ extension PollEntityX on PollEntity { extension PollX on Poll { /// Maps a [Poll] into [PollEntity] PollEntity toEntity() => PollEntity( - id: id, - name: name, - description: description, - options: options.map(jsonEncode).toList(), - votingVisibility: votingVisibility, - enforceUniqueVote: enforceUniqueVote, - maxVotesAllowed: maxVotesAllowed, - allowAnswers: allowAnswers, - answersCount: answersCount, - allowUserSuggestedOptions: allowUserSuggestedOptions, - isClosed: isClosed, - createdAt: createdAt, - updatedAt: updatedAt, - voteCountsByOption: voteCountsByOption, - voteCount: voteCount, - createdById: createdById, - extraData: extraData, - ); + id: id, + name: name, + description: description, + options: options.map(jsonEncode).toList(), + votingVisibility: votingVisibility, + enforceUniqueVote: enforceUniqueVote, + maxVotesAllowed: maxVotesAllowed, + allowAnswers: allowAnswers, + answersCount: answersCount, + allowUserSuggestedOptions: allowUserSuggestedOptions, + isClosed: isClosed, + createdAt: createdAt, + updatedAt: updatedAt, + voteCountsByOption: voteCountsByOption, + voteCount: voteCount, + createdById: createdById, + extraData: extraData, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/poll_vote_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/poll_vote_mapper.dart index 25fa23ab35..a9cac39709 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/poll_vote_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/poll_vote_mapper.dart @@ -6,29 +6,28 @@ extension PollVoteEntityX on PollVoteEntity { /// Maps a [PollVoteEntity] into [PollVote] PollVote toPollVote({ User? user, - }) => - PollVote( - id: id, - pollId: pollId, - optionId: optionId, - answerText: answerText, - createdAt: createdAt, - updatedAt: updatedAt, - userId: userId, - user: user, - ); + }) => PollVote( + id: id, + pollId: pollId, + optionId: optionId, + answerText: answerText, + createdAt: createdAt, + updatedAt: updatedAt, + userId: userId, + user: user, + ); } /// Useful mapping functions for [PollVote] extension PollVoteX on PollVote { /// Maps a [PollVote] into [PollVoteEntity] PollVoteEntity toEntity() => PollVoteEntity( - id: id, - pollId: pollId, - optionId: optionId, - answerText: answerText, - createdAt: createdAt, - updatedAt: updatedAt, - userId: userId, - ); + id: id, + pollId: pollId, + optionId: optionId, + answerText: answerText, + createdAt: createdAt, + updatedAt: updatedAt, + userId: userId, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart index 81b2bd4419..cc62e59db7 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart @@ -5,29 +5,29 @@ import 'package:stream_chat_persistence/src/db/drift_chat_database.dart'; extension ReactionEntityX on ReactionEntity { /// Maps a [ReactionEntity] into [Reaction] Reaction toReaction({User? user}) => Reaction( - type: type, - userId: userId, - user: user, - messageId: messageId, - score: score, - emojiCode: emojiCode, - createdAt: createdAt, - updatedAt: updatedAt, - extraData: extraData ?? {}, - ); + type: type, + userId: userId, + user: user, + messageId: messageId, + score: score, + emojiCode: emojiCode, + createdAt: createdAt, + updatedAt: updatedAt, + extraData: extraData ?? {}, + ); } /// Useful mapping functions for [Reaction] extension ReactionX on Reaction { /// Maps a [Reaction] into [ReactionEntity] ReactionEntity toEntity() => ReactionEntity( - type: type, - userId: userId ?? user?.id, - messageId: messageId, - score: score, - emojiCode: emojiCode, - createdAt: createdAt, - updatedAt: updatedAt, - extraData: extraData, - ); + type: type, + userId: userId ?? user?.id, + messageId: messageId, + score: score, + emojiCode: emojiCode, + createdAt: createdAt, + updatedAt: updatedAt, + extraData: extraData, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart index bdcaefc70e..d26e8bf80a 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart @@ -5,25 +5,25 @@ import 'package:stream_chat_persistence/src/db/drift_chat_database.dart'; extension ReadEntityX on ReadEntity { /// Maps a [ReadEntity] into [Read] Read toRead({required User user}) => Read( - user: user, - lastRead: lastRead, - unreadMessages: unreadMessages, - lastReadMessageId: lastReadMessageId, - lastDeliveredAt: lastDeliveredAt, - lastDeliveredMessageId: lastDeliveredMessageId, - ); + user: user, + lastRead: lastRead, + unreadMessages: unreadMessages, + lastReadMessageId: lastReadMessageId, + lastDeliveredAt: lastDeliveredAt, + lastDeliveredMessageId: lastDeliveredMessageId, + ); } /// Useful mapping functions for [Read] extension ReadX on Read { /// Maps a [Read] into [ReadEntity] ReadEntity toEntity({required String cid}) => ReadEntity( - lastRead: lastRead, - userId: user.id, - channelCid: cid, - unreadMessages: unreadMessages, - lastReadMessageId: lastReadMessageId, - lastDeliveredAt: lastDeliveredAt, - lastDeliveredMessageId: lastDeliveredMessageId, - ); + lastRead: lastRead, + userId: user.id, + channelCid: cid, + unreadMessages: unreadMessages, + lastReadMessageId: lastReadMessageId, + lastDeliveredAt: lastDeliveredAt, + lastDeliveredMessageId: lastDeliveredMessageId, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/user_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/user_mapper.dart index 4e894e81c6..678f4cc3a9 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/user_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/user_mapper.dart @@ -5,34 +5,34 @@ import 'package:stream_chat_persistence/src/db/drift_chat_database.dart'; extension UserEntityX on UserEntity { /// Maps a [UserEntity] into [User] User toUser() => User( - id: id, - updatedAt: updatedAt, - language: language, - role: role, - online: online, - lastActive: lastActive, - extraData: extraData, - banned: banned, - createdAt: createdAt, - teamsRole: teamsRole, - avgResponseTime: avgResponseTime, - ); + id: id, + updatedAt: updatedAt, + language: language, + role: role, + online: online, + lastActive: lastActive, + extraData: extraData, + banned: banned, + createdAt: createdAt, + teamsRole: teamsRole, + avgResponseTime: avgResponseTime, + ); } /// Useful mapping functions for [User] extension UserX on User { /// Maps a [User] into [UserEntity] UserEntity toEntity() => UserEntity( - id: id, - role: role, - language: language, - createdAt: createdAt, - updatedAt: updatedAt, - lastActive: lastActive, - online: online, - banned: banned, - teamsRole: teamsRole, - avgResponseTime: avgResponseTime, - extraData: extraData, - ); + id: id, + role: role, + language: language, + createdAt: createdAt, + updatedAt: updatedAt, + lastActive: lastActive, + online: online, + banned: banned, + teamsRole: teamsRole, + avgResponseTime: avgResponseTime, + extraData: extraData, + ); } diff --git a/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart b/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart index c0a15cdb95..3db937a51a 100644 --- a/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart +++ b/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart @@ -33,9 +33,9 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { /// Otherwise, falls back to the local storage based implementation. bool webUseExperimentalIndexedDb = false, LogHandlerFunction? logHandlerFunction, - }) : _connectionMode = connectionMode, - _webUseIndexedDbIfSupported = webUseExperimentalIndexedDb, - _logger = Logger.detached('💽')..level = logLevel { + }) : _connectionMode = connectionMode, + _webUseIndexedDbIfSupported = webUseExperimentalIndexedDb, + _logger = Logger.detached('💽')..level = logLevel { _logger.onRecord.listen(logHandlerFunction ?? _defaultLogHandler); } @@ -72,12 +72,11 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { Future _defaultDatabaseProvider( String userId, ConnectionMode mode, - ) => - SharedDB.constructDatabase( - userId, - connectionMode: mode, - webUseIndexedDbIfSupported: _webUseIndexedDbIfSupported, - ); + ) => SharedDB.constructDatabase( + userId, + connectionMode: mode, + webUseIndexedDbIfSupported: _webUseIndexedDbIfSupported, + ); @override bool get isConnected => db != null; @@ -97,8 +96,7 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { ); } _logger.info('connect'); - db = databaseProvider?.call(userId, _connectionMode) ?? - await _defaultDatabaseProvider(userId, _connectionMode); + db = databaseProvider?.call(userId, _connectionMode) ?? await _defaultDatabaseProvider(userId, _connectionMode); } @override @@ -222,17 +220,19 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { _logger.info('deleteMessagesFromUser'); // Delete from both messages and pinned_messages tables - await Future.wait([ - db!.messageDao.deleteMessagesByUser, - db!.pinnedMessageDao.deleteMessagesByUser, - ].map( - (f) => f.call( - cid: cid, - userId: userId, - hardDelete: hardDelete, - deletedAt: deletedAt, + await Future.wait( + [ + db!.messageDao.deleteMessagesByUser, + db!.pinnedMessageDao.deleteMessagesByUser, + ].map( + (f) => f.call( + cid: cid, + userId: userId, + hardDelete: hardDelete, + deletedAt: deletedAt, + ), ), - )); + ); } @override diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index b14d4d3dbb..0c789f426f 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -18,8 +18,8 @@ issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: drift: ^2.28.0 diff --git a/packages/stream_chat_persistence/test/mock_chat_database.dart b/packages/stream_chat_persistence/test/mock_chat_database.dart index cb0f06a541..fe4facfd75 100644 --- a/packages/stream_chat_persistence/test/mock_chat_database.dart +++ b/packages/stream_chat_persistence/test/mock_chat_database.dart @@ -19,8 +19,7 @@ class MockChatDatabase extends Mock implements DriftChatDatabase { MessageDao? _messageDao; @override - PinnedMessageDao get pinnedMessageDao => - _pinnedMessageDao ??= MockPinnedMessageDao(); + PinnedMessageDao get pinnedMessageDao => _pinnedMessageDao ??= MockPinnedMessageDao(); PinnedMessageDao? _pinnedMessageDao; @override @@ -32,8 +31,7 @@ class MockChatDatabase extends Mock implements DriftChatDatabase { ReactionDao? _reactionDao; @override - PinnedMessageReactionDao get pinnedMessageReactionDao => - _pinnedMessageReactionDao ??= MockPinnedMessageReactionDao(); + PinnedMessageReactionDao get pinnedMessageReactionDao => _pinnedMessageReactionDao ??= MockPinnedMessageReactionDao(); PinnedMessageReactionDao? _pinnedMessageReactionDao; @override @@ -41,13 +39,11 @@ class MockChatDatabase extends Mock implements DriftChatDatabase { ReadDao? _readDao; @override - ChannelQueryDao get channelQueryDao => - _channelQueryDao ??= MockChannelQueryDao(); + ChannelQueryDao get channelQueryDao => _channelQueryDao ??= MockChannelQueryDao(); ChannelQueryDao? _channelQueryDao; @override - ConnectionEventDao get connectionEventDao => - _connectionEventDao ??= MockConnectionEventDao(); + ConnectionEventDao get connectionEventDao => _connectionEventDao ??= MockConnectionEventDao(); ConnectionEventDao? _connectionEventDao; @override @@ -59,8 +55,7 @@ class MockChatDatabase extends Mock implements DriftChatDatabase { PollVoteDao? _pollVoteDao; @override - DraftMessageDao get draftMessageDao => - _draftMessageDao ??= MockDraftMessageDao(); + DraftMessageDao get draftMessageDao => _draftMessageDao ??= MockDraftMessageDao(); DraftMessageDao? _draftMessageDao; @override @@ -86,8 +81,7 @@ class MockMemberDao extends Mock implements MemberDao {} class MockReactionDao extends Mock implements ReactionDao {} -class MockPinnedMessageReactionDao extends Mock - implements PinnedMessageReactionDao {} +class MockPinnedMessageReactionDao extends Mock implements PinnedMessageReactionDao {} class MockReadDao extends Mock implements ReadDao {} diff --git a/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart index da974fa31c..44dde6b1f6 100644 --- a/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart @@ -99,13 +99,11 @@ void main() { expect(updatedReads.first.user, dummyUser); // Saving a dummy reaction - final dummyReaction = - Reaction(type: 'type', messageId: messageId, userId: userId); + final dummyReaction = Reaction(type: 'type', messageId: messageId, userId: userId); await database.reactionDao.updateReactions([dummyReaction]); // Should match the dummy reaction - final updatedReactions = - await database.reactionDao.getReactionsByUserId(messageId, userId); + final updatedReactions = await database.reactionDao.getReactionsByUserId(messageId, userId); expect(updatedReactions.length, 1); expect(updatedReactions.first.messageId, messageId); @@ -129,8 +127,7 @@ void main() { expect(reads, isEmpty); // Fetched readtions for passed message id and user id should be empty - final reactions = - await database.reactionDao.getReactionsByUserId(messageId, userId); + final reactions = await database.reactionDao.getReactionsByUserId(messageId, userId); expect(reactions, isEmpty); }); diff --git a/packages/stream_chat_persistence/test/src/dao/draft_message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/draft_message_dao_test.dart index 15c8e48b2f..0a020da477 100644 --- a/packages/stream_chat_persistence/test/src/dao/draft_message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/draft_message_dao_test.dart @@ -62,8 +62,7 @@ void main() { (index) { // When count is 1, use the exact cid provided // Otherwise, create unique cids for each draft to avoid conflicts - final draftChannelCid = - count == 1 ? cid : (withParentMessage ? cid : '$cid$index'); + final draftChannelCid = count == 1 ? cid : (withParentMessage ? cid : '$cid$index'); final draftMessage = DraftMessage( id: 'testDraftId$cid$index', @@ -236,8 +235,7 @@ void main() { await draftMessageDao.updateDraftMessages([firstDraft]); // Verify first draft exists - final firstFetchedDraft = - await draftMessageDao.getDraftMessageByCid(cid); + final firstFetchedDraft = await draftMessageDao.getDraftMessageByCid(cid); expect(firstFetchedDraft, isNotNull); expect(firstFetchedDraft!.message.text, 'First channel draft'); @@ -254,16 +252,13 @@ void main() { await draftMessageDao.updateDraftMessages([secondDraft]); // Verify only the second draft exists - final secondFetchedDraft = - await draftMessageDao.getDraftMessageByCid(cid); + final secondFetchedDraft = await draftMessageDao.getDraftMessageByCid(cid); expect(secondFetchedDraft, isNotNull); expect(secondFetchedDraft!.message.text, 'Second channel draft'); // Verify the first draft no longer exists - final firstDraftAfterUpdate = - await draftMessageDao.getDraftMessageByCid(firstDraft.channelCid); - expect( - firstDraftAfterUpdate!.message.text, isNot('First channel draft')); + final firstDraftAfterUpdate = await draftMessageDao.getDraftMessageByCid(firstDraft.channelCid); + expect(firstDraftAfterUpdate!.message.text, isNot('First channel draft')); // Verify there's only one draft message for this channel final channelDraft = await draftMessageDao.getDraftMessageByCid(cid); @@ -303,8 +298,7 @@ void main() { await draftMessageDao.updateDraftMessages([firstDraft]); // Verify first thread draft exists - final firstFetchedDraft = await draftMessageDao - .getDraftMessageByCid(cid, parentId: firstDraft.parentId); + final firstFetchedDraft = await draftMessageDao.getDraftMessageByCid(cid, parentId: firstDraft.parentId); expect(firstFetchedDraft, isNotNull); expect(firstFetchedDraft!.message.text, 'First thread draft'); @@ -323,20 +317,16 @@ void main() { await draftMessageDao.updateDraftMessages([secondDraft]); // Verify only the second draft exists - final secondFetchedDraft = await draftMessageDao - .getDraftMessageByCid(cid, parentId: secondDraft.parentId); + final secondFetchedDraft = await draftMessageDao.getDraftMessageByCid(cid, parentId: secondDraft.parentId); expect(secondFetchedDraft, isNotNull); expect(secondFetchedDraft!.message.text, 'Second thread draft'); // Verify the first draft no longer exists - final firstDraftAfterUpdate = await draftMessageDao - .getDraftMessageByCid(cid, parentId: firstDraft.parentId); - expect( - firstDraftAfterUpdate!.message.text, isNot('First thread draft')); + final firstDraftAfterUpdate = await draftMessageDao.getDraftMessageByCid(cid, parentId: firstDraft.parentId); + expect(firstDraftAfterUpdate!.message.text, isNot('First thread draft')); // Verify there's only one draft message for this thread - final threadDraft = await draftMessageDao.getDraftMessageByCid(cid, - parentId: parentMessage.id); + final threadDraft = await draftMessageDao.getDraftMessageByCid(cid, parentId: parentMessage.id); expect(threadDraft, isNotNull); expect(threadDraft!.message.text, 'Second thread draft'); }, @@ -387,16 +377,14 @@ void main() { await _prepareTestData(cid, count: 1); // Verify draft exists - final draftBeforeChannelDelete = - await draftMessageDao.getDraftMessageByCid(cid); + final draftBeforeChannelDelete = await draftMessageDao.getDraftMessageByCid(cid); expect(draftBeforeChannelDelete, isNotNull); // Delete the channel await database.channelDao.deleteChannelByCids([cid]); // Verify draft has been deleted (cascade) - final draftAfterChannelDelete = - await draftMessageDao.getDraftMessageByCid(cid); + final draftAfterChannelDelete = await draftMessageDao.getDraftMessageByCid(cid); expect(draftAfterChannelDelete, isNull); }, ); @@ -454,14 +442,12 @@ void main() { ); // Verify drafts exist before channel deletion - final channelDraftBeforeDelete = - await draftMessageDao.getDraftMessageByCid(cid); + final channelDraftBeforeDelete = await draftMessageDao.getDraftMessageByCid(cid); expect(channelDraftBeforeDelete, isNotNull); expect(channelDraftBeforeDelete!.parentId, isNull); for (var i = 0; i < threadDrafts.length; i++) { - final threadDraft = await draftMessageDao.getDraftMessageByCid(cid, - parentId: threadDrafts[i].parentId); + final threadDraft = await draftMessageDao.getDraftMessageByCid(cid, parentId: threadDrafts[i].parentId); expect(threadDraft, isNotNull); expect(threadDraft!.parentId, messages[i].id); } @@ -470,13 +456,11 @@ void main() { await database.channelDao.deleteChannelByCids([cid]); // Verify all drafts have been deleted (cascade) - final channelDraftAfterDelete = - await draftMessageDao.getDraftMessageByCid(cid); + final channelDraftAfterDelete = await draftMessageDao.getDraftMessageByCid(cid); expect(channelDraftAfterDelete, isNull); for (final threadDraft in threadDrafts) { - final draft = await draftMessageDao.getDraftMessageByCid(cid, - parentId: threadDraft.parentId); + final draft = await draftMessageDao.getDraftMessageByCid(cid, parentId: threadDraft.parentId); expect(draft, isNull); } }, @@ -486,21 +470,18 @@ void main() { 'should delete draft messages when referenced parent message is deleted', () async { const cid = 'test:parentRefCascade'; - final testDrafts = - await _prepareTestData(cid, withParentMessage: true, count: 1); + final testDrafts = await _prepareTestData(cid, withParentMessage: true, count: 1); final parentId = testDrafts.first.parentId!; // Verify draft with parent exists - final draftBeforeMessageDelete = - await draftMessageDao.getDraftMessageByCid(cid, parentId: parentId); + final draftBeforeMessageDelete = await draftMessageDao.getDraftMessageByCid(cid, parentId: parentId); expect(draftBeforeMessageDelete, isNotNull); // Delete the parent message await database.messageDao.deleteMessageByIds([parentId]); // Verify draft has been deleted (cascade) - final draftAfterMessageDelete = - await draftMessageDao.getDraftMessageByCid(cid, parentId: parentId); + final draftAfterMessageDelete = await draftMessageDao.getDraftMessageByCid(cid, parentId: parentId); expect(draftAfterMessageDelete, isNull); }, ); diff --git a/packages/stream_chat_persistence/test/src/dao/location_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/location_dao_test.dart index 2311ac8d2d..a521d2836a 100644 --- a/packages/stream_chat_persistence/test/src/dao/location_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/location_dao_test.dart @@ -42,9 +42,7 @@ void main() { latitude: 37.7749 + index * 0.001, // San Francisco area longitude: -122.4194 + index * 0.001, createdByDeviceId: 'testDevice$index', - endAt: index.isEven - ? DateTime.now().add(const Duration(hours: 1)) - : null, // Some live, some static + endAt: index.isEven ? DateTime.now().add(const Duration(hours: 1)) : null, // Some live, some static createdAt: DateTime.now(), updatedAt: DateTime.now(), ), @@ -112,10 +110,12 @@ void main() { final fetchedLocations = await locationDao.getLocationsByCid(cid); expect(fetchedLocations.length, insertedLocations.length + 1); expect( - fetchedLocations.any((it) => - it.messageId == newLocation.messageId && - it.latitude == newLocation.latitude && - it.longitude == newLocation.longitude), + fetchedLocations.any( + (it) => + it.messageId == newLocation.messageId && + it.latitude == newLocation.latitude && + it.longitude == newLocation.longitude, + ), isTrue, ); }); @@ -128,8 +128,7 @@ void main() { // Fetched location should not be null final locationToFetch = insertedLocations.first; - final fetchedLocation = - await locationDao.getLocationByMessageId(locationToFetch.messageId!); + final fetchedLocation = await locationDao.getLocationByMessageId(locationToFetch.messageId!); expect(fetchedLocation, isNotNull); expect(fetchedLocation!.messageId, locationToFetch.messageId); expect(fetchedLocation.latitude, locationToFetch.latitude); @@ -140,8 +139,7 @@ void main() { 'getLocationByMessageId should return null for non-existent messageId', () async { // Should return null for non-existent messageId - final fetchedLocation = - await locationDao.getLocationByMessageId('nonExistentMessageId'); + final fetchedLocation = await locationDao.getLocationByMessageId('nonExistentMessageId'); expect(fetchedLocation, isNull); }, ); @@ -171,14 +169,12 @@ void main() { final insertedLocations = await _prepareLocationData(cid: cid); // Deleting the first two locations by their message IDs - final messageIdsToDelete = - insertedLocations.take(2).map((it) => it.messageId!).toList(); + final messageIdsToDelete = insertedLocations.take(2).map((it) => it.messageId!).toList(); await locationDao.deleteLocationsByMessageIds(messageIdsToDelete); // Fetched location list should be one less than inserted locations final fetchedLocations = await locationDao.getLocationsByCid(cid); - expect(fetchedLocations.length, - insertedLocations.length - messageIdsToDelete.length); + expect(fetchedLocations.length, insertedLocations.length - messageIdsToDelete.length); // Deleted locations should not exist in fetched locations expect( @@ -193,10 +189,8 @@ void main() { const cid2 = 'test:Cid2'; // Preparing test data for two channels - final insertedLocations1 = - await _prepareLocationData(cid: cid1, count: 2); - final insertedLocations2 = - await _prepareLocationData(cid: cid2, count: 2); + final insertedLocations1 = await _prepareLocationData(cid: cid1, count: 2); + final insertedLocations2 = await _prepareLocationData(cid: cid2, count: 2); // Verify all locations exist final locations1 = await locationDao.getLocationsByCid(cid1); @@ -205,8 +199,7 @@ void main() { expect(locations2.length, insertedLocations2.length); // Delete only locations from the first channel - final messageIdsToDelete = - insertedLocations1.map((it) => it.messageId!).toList(); + final messageIdsToDelete = insertedLocations1.map((it) => it.messageId!).toList(); await locationDao.deleteLocationsByMessageIds(messageIdsToDelete); // Only locations from cid1 should be deleted @@ -246,32 +239,27 @@ void main() { const cid = 'test:messageRefCascade'; // Prepare test data - final insertedLocations = - await _prepareLocationData(cid: cid, count: 3); + final insertedLocations = await _prepareLocationData(cid: cid, count: 3); final messageToDelete = insertedLocations.first.messageId!; // Verify location exists before message deletion - final locationBeforeDelete = - await locationDao.getLocationByMessageId(messageToDelete); + final locationBeforeDelete = await locationDao.getLocationByMessageId(messageToDelete); expect(locationBeforeDelete, isNotNull); expect(locationBeforeDelete!.messageId, messageToDelete); // Verify all locations exist - final allLocationsBeforeDelete = - await locationDao.getLocationsByCid(cid); + final allLocationsBeforeDelete = await locationDao.getLocationsByCid(cid); expect(allLocationsBeforeDelete.length, 3); // Delete the message await database.messageDao.deleteMessageByIds([messageToDelete]); // Verify the specific location has been deleted (cascade) - final locationAfterDelete = - await locationDao.getLocationByMessageId(messageToDelete); + final locationAfterDelete = await locationDao.getLocationByMessageId(messageToDelete); expect(locationAfterDelete, isNull); // Verify other locations still exist - final allLocationsAfterDelete = - await locationDao.getLocationsByCid(cid); + final allLocationsAfterDelete = await locationDao.getLocationsByCid(cid); expect(allLocationsAfterDelete.length, 2); expect( allLocationsAfterDelete.any((it) => it.messageId == messageToDelete), @@ -286,26 +274,21 @@ void main() { const cid = 'test:multipleMessageRefCascade'; // Prepare test data - final insertedLocations = - await _prepareLocationData(cid: cid, count: 3); - final messageIdsToDelete = - insertedLocations.take(2).map((it) => it.messageId!).toList(); + final insertedLocations = await _prepareLocationData(cid: cid, count: 3); + final messageIdsToDelete = insertedLocations.take(2).map((it) => it.messageId!).toList(); // Verify locations exist before message deletion - final allLocationsBeforeDelete = - await locationDao.getLocationsByCid(cid); + final allLocationsBeforeDelete = await locationDao.getLocationsByCid(cid); expect(allLocationsBeforeDelete.length, 3); // Delete multiple messages await database.messageDao.deleteMessageByIds(messageIdsToDelete); // Verify corresponding locations have been deleted (cascade) - final allLocationsAfterDelete = - await locationDao.getLocationsByCid(cid); + final allLocationsAfterDelete = await locationDao.getLocationsByCid(cid); expect(allLocationsAfterDelete.length, 1); expect( - allLocationsAfterDelete - .any((it) => messageIdsToDelete.contains(it.messageId)), + allLocationsAfterDelete.any((it) => messageIdsToDelete.contains(it.messageId)), isFalse, ); }, diff --git a/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart index 936097fe8f..88f034a793 100644 --- a/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart @@ -129,15 +129,11 @@ void main() { final newFetchedMembers = await memberDao.getMembersByCid(cid); expect(newFetchedMembers.length, fetchedMembers.length + 1); expect( - newFetchedMembers - .firstWhere((it) => it.user!.id == copyMember.user!.id) - .banned, + newFetchedMembers.firstWhere((it) => it.user!.id == copyMember.user!.id).banned, true, ); expect( - newFetchedMembers - .where((it) => it.user!.id == newMember.user!.id) - .isNotEmpty, + newFetchedMembers.where((it) => it.user!.id == newMember.user!.id).isNotEmpty, true, ); }); diff --git a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart index d1b201f720..80472a1a78 100644 --- a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart @@ -85,8 +85,7 @@ void main() { type: 'testType', user: users[index], channelRole: 'channel_member', - parentId: - mapAllThreadToFirstMessage ? messages[0].id : messages[index].id, + parentId: mapAllThreadToFirstMessage ? messages[0].id : messages[index].id, createdAt: DateTime.now(), shadowed: math.Random().nextBool(), replyCount: index, @@ -103,11 +102,7 @@ void main() { }, ), ); - final allMessages = [ - ...messages, - if (quoted) ...quotedMessages, - if (threads) ...threadMessages - ]; + final allMessages = [...messages, if (quoted) ...quotedMessages, if (threads) ...threadMessages]; final reaction = Reaction( type: 'type', messageId: allMessages.first.id, @@ -147,8 +142,7 @@ void main() { expect(newMessages.length, messages.length - 2); // Reaction for the first message should be deleted too - final newReactions = - await database.reactionDao.getReactions(firstMessageId); + final newReactions = await database.reactionDao.getReactions(firstMessageId); expect(newReactions, isEmpty); }); @@ -171,8 +165,7 @@ void main() { // Fetched reactions list should have one reaction for given message id final cid1firstMessageId = cid1Messages.first.id; - final cid1Reactions = - await database.reactionDao.getReactions(cid1firstMessageId); + final cid1Reactions = await database.reactionDao.getReactions(cid1firstMessageId); expect(cid1Reactions.length, 1); // Deleting all the messages of cid1 @@ -185,8 +178,7 @@ void main() { expect(cid2FetchedMessages, isNotEmpty); // Reaction for the first message should be deleted too - final cid1FetchedReactions = - await database.reactionDao.getReactions(cid1firstMessageId); + final cid1FetchedReactions = await database.reactionDao.getReactions(cid1firstMessageId); expect(cid1FetchedReactions, isEmpty); }, ); @@ -206,12 +198,10 @@ void main() { // Fetched reactions list should have one reaction for given message id final cid1FirstMessageId = cid1Messages.first.id; - final cid1Reactions = - await database.reactionDao.getReactions(cid1FirstMessageId); + final cid1Reactions = await database.reactionDao.getReactions(cid1FirstMessageId); expect(cid1Reactions.length, 1); final cid2FirstMessageId = cid2Messages.first.id; - final cid2Reactions = - await database.reactionDao.getReactions(cid2FirstMessageId); + final cid2Reactions = await database.reactionDao.getReactions(cid2FirstMessageId); expect(cid2Reactions.length, 1); // Deleting all the messages of cid1 @@ -224,11 +214,9 @@ void main() { expect(cid2FetchedMessages, isEmpty); // Reaction for the first message should be deleted too - final cid1FetchedReactions = - await database.reactionDao.getReactions(cid1FirstMessageId); + final cid1FetchedReactions = await database.reactionDao.getReactions(cid1FirstMessageId); expect(cid1FetchedReactions, isEmpty); - final cid2FetchedReactions = - await database.reactionDao.getReactions(cid2FirstMessageId); + final cid2FetchedReactions = await database.reactionDao.getReactions(cid2FirstMessageId); expect(cid2FetchedReactions, isEmpty); }, ); @@ -284,8 +272,7 @@ void main() { expect(insertedMessages, isNotEmpty); // Should fetch all the thread messages of parentId - final threadMessages = - await messageDao.getThreadMessagesByParentId(parentId); + final threadMessages = await messageDao.getThreadMessagesByParentId(parentId); expect(threadMessages.length, 1); expect(threadMessages.first.parentId, parentId); }); @@ -454,8 +441,7 @@ void main() { expect(cid2Messages, isNotEmpty); // Count messages from the specific user in cid1 - final cid1UserMessages = - cid1Messages.where((m) => m.user?.id == userId).length; + final cid1UserMessages = cid1Messages.where((m) => m.user?.id == userId).length; expect(cid1UserMessages, greaterThan(0)); // Hard delete messages from user in cid1 only @@ -467,8 +453,7 @@ void main() { // Verify user's messages are deleted from cid1 final cid1MessagesAfter = await messageDao.getMessagesByCid(cid1); - final cid1UserMessagesAfter = - cid1MessagesAfter.where((m) => m.user?.id == userId).length; + final cid1UserMessagesAfter = cid1MessagesAfter.where((m) => m.user?.id == userId).length; expect(cid1UserMessagesAfter, 0); // Verify other users' messages in cid1 are not affected @@ -484,8 +469,7 @@ void main() { await _prepareTestData(cid1); final cid1Messages = await messageDao.getMessagesByCid(cid1); - final cid1UserMessages = - cid1Messages.where((m) => m.user?.id == userId).toList(); + final cid1UserMessages = cid1Messages.where((m) => m.user?.id == userId).toList(); expect(cid1UserMessages, isNotEmpty); // Verify messages are not deleted initially @@ -505,8 +489,7 @@ void main() { // Verify messages are marked as deleted final cid1MessagesAfter = await messageDao.getMessagesByCid(cid1); - final cid1UserMessagesAfter = - cid1MessagesAfter.where((m) => m.user?.id == userId).toList(); + final cid1UserMessagesAfter = cid1MessagesAfter.where((m) => m.user?.id == userId).toList(); // Messages should still exist in DB expect(cid1UserMessagesAfter.length, cid1UserMessages.length); @@ -518,15 +501,13 @@ void main() { } // Other users' messages should not be affected - final otherUserMessages = - cid1MessagesAfter.where((m) => m.user?.id != userId).toList(); + final otherUserMessages = cid1MessagesAfter.where((m) => m.user?.id != userId).toList(); for (final message in otherUserMessages) { expect(message.type, isNot('deleted')); } }); - test('hard deletes user messages across all channels when cid is null', - () async { + test('hard deletes user messages across all channels when cid is null', () async { // Preparing test data for multiple channels await _prepareTestData(cid1); await _prepareTestData(cid2); @@ -534,10 +515,8 @@ void main() { final cid1Messages = await messageDao.getMessagesByCid(cid1); final cid2Messages = await messageDao.getMessagesByCid(cid2); - final cid1UserMessages = - cid1Messages.where((m) => m.user?.id == userId).length; - final cid2UserMessages = - cid2Messages.where((m) => m.user?.id == userId).length; + final cid1UserMessages = cid1Messages.where((m) => m.user?.id == userId).length; + final cid2UserMessages = cid2Messages.where((m) => m.user?.id == userId).length; expect(cid1UserMessages, greaterThan(0)); expect(cid2UserMessages, greaterThan(0)); @@ -566,8 +545,7 @@ void main() { ); }); - test('soft deletes user messages across all channels when cid is null', - () async { + test('soft deletes user messages across all channels when cid is null', () async { // Preparing test data for multiple channels await _prepareTestData(cid1); await _prepareTestData(cid2); @@ -575,10 +553,8 @@ void main() { final cid1Messages = await messageDao.getMessagesByCid(cid1); final cid2Messages = await messageDao.getMessagesByCid(cid2); - final cid1UserMessages = - cid1Messages.where((m) => m.user?.id == userId).length; - final cid2UserMessages = - cid2Messages.where((m) => m.user?.id == userId).length; + final cid1UserMessages = cid1Messages.where((m) => m.user?.id == userId).length; + final cid2UserMessages = cid2Messages.where((m) => m.user?.id == userId).length; // Soft delete all messages from user across all channels await messageDao.deleteMessagesByUser( @@ -590,20 +566,15 @@ void main() { final cid1MessagesAfter = await messageDao.getMessagesByCid(cid1); final cid2MessagesAfter = await messageDao.getMessagesByCid(cid2); - final cid1UserMessagesAfter = - cid1MessagesAfter.where((m) => m.user?.id == userId).toList(); - final cid2UserMessagesAfter = - cid2MessagesAfter.where((m) => m.user?.id == userId).toList(); + final cid1UserMessagesAfter = cid1MessagesAfter.where((m) => m.user?.id == userId).toList(); + final cid2UserMessagesAfter = cid2MessagesAfter.where((m) => m.user?.id == userId).toList(); // Messages should still exist expect(cid1UserMessagesAfter.length, cid1UserMessages); expect(cid2UserMessagesAfter.length, cid2UserMessages); // All user messages should be marked as deleted - for (final message in [ - ...cid1UserMessagesAfter, - ...cid2UserMessagesAfter - ]) { + for (final message in [...cid1UserMessagesAfter, ...cid2UserMessagesAfter]) { expect(message.type, 'deleted'); expect(message.deletedAt, isNotNull); } @@ -615,8 +586,7 @@ void main() { final cid1ThreadMessages = await messageDao.getThreadMessages(cid1); - final userThreadMessages = - cid1ThreadMessages.where((m) => m.user?.id == userId).length; + final userThreadMessages = cid1ThreadMessages.where((m) => m.user?.id == userId).length; expect(userThreadMessages, greaterThan(0)); // Hard delete all messages from user @@ -628,8 +598,7 @@ void main() { // Verify thread messages from user are also deleted final cid1ThreadMessagesAfter = await messageDao.getThreadMessages(cid1); - final userThreadMessagesAfter = - cid1ThreadMessagesAfter.where((m) => m.user?.id == userId).length; + final userThreadMessagesAfter = cid1ThreadMessagesAfter.where((m) => m.user?.id == userId).length; expect(userThreadMessagesAfter, 0); }); }); diff --git a/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart index 3a76a5d953..ca7567b1de 100644 --- a/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart @@ -75,8 +75,7 @@ void main() { type: 'testType', user: users[index], channelRole: 'channel_member', - parentId: - mapAllThreadToFirstMessage ? messages[0].id : messages[index].id, + parentId: mapAllThreadToFirstMessage ? messages[0].id : messages[index].id, createdAt: DateTime.now(), shadowed: math.Random().nextBool(), replyCount: index, @@ -88,11 +87,7 @@ void main() { pinnedBy: User(id: 'testUserId$index'), ), ); - final allMessages = [ - ...messages, - if (quoted) ...quotedMessages, - if (threads) ...threadMessages - ]; + final allMessages = [...messages, if (quoted) ...quotedMessages, if (threads) ...threadMessages]; final reaction = Reaction( type: 'type', messageId: allMessages.first.id, @@ -118,8 +113,7 @@ void main() { final firstMessageId = messages.first.id; // Fetched reactions list should have one reaction for given message id - final reactions = - await database.pinnedMessageReactionDao.getReactions(firstMessageId); + final reactions = await database.pinnedMessageReactionDao.getReactions(firstMessageId); expect(reactions.length, 1); // Deleting 2 messages from DB @@ -133,8 +127,7 @@ void main() { expect(newMessages.length, messages.length - 2); // Reaction for the first message should be deleted too - final newReactions = - await database.pinnedMessageReactionDao.getReactions(firstMessageId); + final newReactions = await database.pinnedMessageReactionDao.getReactions(firstMessageId); expect(newReactions, isEmpty); }); @@ -157,24 +150,20 @@ void main() { // Fetched reactions list should have one reaction for given message id final cid1firstMessageId = cid1Messages.first.id; - final cid1Reactions = await database.pinnedMessageReactionDao - .getReactions(cid1firstMessageId); + final cid1Reactions = await database.pinnedMessageReactionDao.getReactions(cid1firstMessageId); expect(cid1Reactions.length, 1); // Deleting all the messages of cid1 await pinnedMessageDao.deleteMessageByCids([cid1]); // Fetched messages length of only cid1 should be empty - final cid1FetchedMessages = - await pinnedMessageDao.getMessagesByCid(cid1); - final cid2FetchedMessages = - await pinnedMessageDao.getMessagesByCid(cid2); + final cid1FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid1); + final cid2FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid2); expect(cid1FetchedMessages, isEmpty); expect(cid2FetchedMessages, isNotEmpty); // Reaction for the first message should be deleted too - final cid1FetchedReactions = await database.pinnedMessageReactionDao - .getReactions(cid1firstMessageId); + final cid1FetchedReactions = await database.pinnedMessageReactionDao.getReactions(cid1firstMessageId); expect(cid1FetchedReactions, isEmpty); }, ); @@ -194,31 +183,25 @@ void main() { // Fetched reactions list should have one reaction for given message id final cid1FirstMessageId = cid1Messages.first.id; - final cid1Reactions = await database.pinnedMessageReactionDao - .getReactions(cid1FirstMessageId); + final cid1Reactions = await database.pinnedMessageReactionDao.getReactions(cid1FirstMessageId); expect(cid1Reactions.length, 1); final cid2FirstMessageId = cid2Messages.first.id; - final cid2Reactions = await database.pinnedMessageReactionDao - .getReactions(cid2FirstMessageId); + final cid2Reactions = await database.pinnedMessageReactionDao.getReactions(cid2FirstMessageId); expect(cid2Reactions.length, 1); // Deleting all the messages of cid1 await pinnedMessageDao.deleteMessageByCids([cid1, cid2]); // Fetched messages length of both cid1 and cid2 should be empty - final cid1FetchedMessages = - await pinnedMessageDao.getMessagesByCid(cid1); - final cid2FetchedMessages = - await pinnedMessageDao.getMessagesByCid(cid2); + final cid1FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid1); + final cid2FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid2); expect(cid1FetchedMessages, isEmpty); expect(cid2FetchedMessages, isEmpty); // Reaction for the first message should be deleted too - final cid1FetchedReactions = await database.pinnedMessageReactionDao - .getReactions(cid1FirstMessageId); + final cid1FetchedReactions = await database.pinnedMessageReactionDao.getReactions(cid1FirstMessageId); expect(cid1FetchedReactions, isEmpty); - final cid2FetchedReactions = await database.pinnedMessageReactionDao - .getReactions(cid2FirstMessageId); + final cid2FetchedReactions = await database.pinnedMessageReactionDao.getReactions(cid2FirstMessageId); expect(cid2FetchedReactions, isEmpty); }, ); @@ -266,8 +249,7 @@ void main() { const parentId = 'testMessageId${cid}0'; // Messages should be empty initially - final messages = - await pinnedMessageDao.getThreadMessagesByParentId(parentId); + final messages = await pinnedMessageDao.getThreadMessagesByParentId(parentId); expect(messages, isEmpty); // Preparing test data @@ -275,8 +257,7 @@ void main() { expect(insertedMessages, isNotEmpty); // Should fetch all the thread messages of parentId - final threadMessages = - await pinnedMessageDao.getThreadMessagesByParentId(parentId); + final threadMessages = await pinnedMessageDao.getThreadMessagesByParentId(parentId); expect(threadMessages.length, 1); expect(threadMessages.first.parentId, parentId); }); @@ -445,8 +426,7 @@ void main() { expect(cid2Messages, isNotEmpty); // Count messages from the specific user in cid1 - final cid1UserMessages = - cid1Messages.where((m) => m.user?.id == userId).length; + final cid1UserMessages = cid1Messages.where((m) => m.user?.id == userId).length; expect(cid1UserMessages, greaterThan(0)); // Hard delete messages from user in cid1 only @@ -458,8 +438,7 @@ void main() { // Verify user's messages are deleted from cid1 final cid1MessagesAfter = await pinnedMessageDao.getMessagesByCid(cid1); - final cid1UserMessagesAfter = - cid1MessagesAfter.where((m) => m.user?.id == userId).length; + final cid1UserMessagesAfter = cid1MessagesAfter.where((m) => m.user?.id == userId).length; expect(cid1UserMessagesAfter, 0); // Verify other users' messages in cid1 are not affected @@ -475,8 +454,7 @@ void main() { await _prepareTestData(cid1); final cid1Messages = await pinnedMessageDao.getMessagesByCid(cid1); - final cid1UserMessages = - cid1Messages.where((m) => m.user?.id == userId).toList(); + final cid1UserMessages = cid1Messages.where((m) => m.user?.id == userId).toList(); expect(cid1UserMessages, isNotEmpty); // Verify messages are not deleted initially @@ -496,8 +474,7 @@ void main() { // Verify messages are marked as deleted final cid1MessagesAfter = await pinnedMessageDao.getMessagesByCid(cid1); - final cid1UserMessagesAfter = - cid1MessagesAfter.where((m) => m.user?.id == userId).toList(); + final cid1UserMessagesAfter = cid1MessagesAfter.where((m) => m.user?.id == userId).toList(); // Messages should still exist in DB expect(cid1UserMessagesAfter.length, cid1UserMessages.length); @@ -509,15 +486,13 @@ void main() { } // Other users' messages should not be affected - final otherUserMessages = - cid1MessagesAfter.where((m) => m.user?.id != userId).toList(); + final otherUserMessages = cid1MessagesAfter.where((m) => m.user?.id != userId).toList(); for (final message in otherUserMessages) { expect(message.type, isNot('deleted')); } }); - test('hard deletes user pinned messages across all channels when cid null', - () async { + test('hard deletes user pinned messages across all channels when cid null', () async { // Preparing test data for multiple channels await _prepareTestData(cid1); await _prepareTestData(cid2); @@ -525,10 +500,8 @@ void main() { final cid1Messages = await pinnedMessageDao.getMessagesByCid(cid1); final cid2Messages = await pinnedMessageDao.getMessagesByCid(cid2); - final cid1UserMessages = - cid1Messages.where((m) => m.user?.id == userId).length; - final cid2UserMessages = - cid2Messages.where((m) => m.user?.id == userId).length; + final cid1UserMessages = cid1Messages.where((m) => m.user?.id == userId).length; + final cid2UserMessages = cid2Messages.where((m) => m.user?.id == userId).length; expect(cid1UserMessages, greaterThan(0)); expect(cid2UserMessages, greaterThan(0)); @@ -563,8 +536,7 @@ void main() { ); }); - test('soft deletes user pinned messages across all channels when cid null', - () async { + test('soft deletes user pinned messages across all channels when cid null', () async { // Preparing test data for multiple channels await _prepareTestData(cid1); await _prepareTestData(cid2); @@ -572,10 +544,8 @@ void main() { final cid1Messages = await pinnedMessageDao.getMessagesByCid(cid1); final cid2Messages = await pinnedMessageDao.getMessagesByCid(cid2); - final cid1UserMessages = - cid1Messages.where((m) => m.user?.id == userId).length; - final cid2UserMessages = - cid2Messages.where((m) => m.user?.id == userId).length; + final cid1UserMessages = cid1Messages.where((m) => m.user?.id == userId).length; + final cid2UserMessages = cid2Messages.where((m) => m.user?.id == userId).length; // Soft delete all messages from user across all channels await pinnedMessageDao.deleteMessagesByUser( @@ -587,20 +557,15 @@ void main() { final cid1MessagesAfter = await pinnedMessageDao.getMessagesByCid(cid1); final cid2MessagesAfter = await pinnedMessageDao.getMessagesByCid(cid2); - final cid1UserMessagesAfter = - cid1MessagesAfter.where((m) => m.user?.id == userId).toList(); - final cid2UserMessagesAfter = - cid2MessagesAfter.where((m) => m.user?.id == userId).toList(); + final cid1UserMessagesAfter = cid1MessagesAfter.where((m) => m.user?.id == userId).toList(); + final cid2UserMessagesAfter = cid2MessagesAfter.where((m) => m.user?.id == userId).toList(); // Messages should still exist expect(cid1UserMessagesAfter.length, cid1UserMessages); expect(cid2UserMessagesAfter.length, cid2UserMessages); // All user messages should be marked as deleted - for (final message in [ - ...cid1UserMessagesAfter, - ...cid2UserMessagesAfter - ]) { + for (final message in [...cid1UserMessagesAfter, ...cid2UserMessagesAfter]) { expect(message.type, 'deleted'); expect(message.deletedAt, isNotNull); } diff --git a/packages/stream_chat_persistence/test/src/dao/pinned_message_reaction_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/pinned_message_reaction_dao_test.dart index 57482c4317..4b76fe095c 100644 --- a/packages/stream_chat_persistence/test/src/dao/pinned_message_reaction_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/pinned_message_reaction_dao_test.dart @@ -80,8 +80,7 @@ void main() { // Fetched reaction length should match inserted reactions length. // Every reaction messageId should match the provided messageId. - final fetchedReactions = - await pinnedMessageReactionDao.getReactions(messageId); + final fetchedReactions = await pinnedMessageReactionDao.getReactions(messageId); expect(fetchedReactions.length, insertedReactions.length); expect(fetchedReactions.every((it) => it.messageId == messageId), true); @@ -99,20 +98,17 @@ void main() { const userId = 'testUserId'; // Should be empty initially - final reactions = - await pinnedMessageReactionDao.getReactionsByUserId(messageId, userId); + final reactions = await pinnedMessageReactionDao.getReactionsByUserId(messageId, userId); expect(reactions, isEmpty); // Adding sample reactions - final insertedReactions = - await _prepareReactionData(messageId, userId: userId); + final insertedReactions = await _prepareReactionData(messageId, userId: userId); expect(insertedReactions, isNotEmpty); // Fetched reaction length should match inserted reactions length. // Every reaction messageId should match the provided messageId. // Every reaction userId should match the provided userId. - final fetchedReactions = - await pinnedMessageReactionDao.getReactionsByUserId(messageId, userId); + final fetchedReactions = await pinnedMessageReactionDao.getReactionsByUserId(messageId, userId); expect(fetchedReactions.length, insertedReactions.length); expect(fetchedReactions.every((it) => it.messageId == messageId), true); expect(fetchedReactions.every((it) => it.userId == userId), true); @@ -155,8 +151,7 @@ void main() { // Fetched reaction length should be one more than inserted reactions. // copyReaction modified fields should match // Fetched reactions should contain the newReaction. - final fetchedReactions = - await pinnedMessageReactionDao.getReactions(messageId); + final fetchedReactions = await pinnedMessageReactionDao.getReactions(messageId); expect(fetchedReactions.length, reactions.length + 1); final fetchedCopyReaction = fetchedReactions.firstWhere( @@ -186,10 +181,8 @@ void main() { // Fetched reaction list length should match // the inserted reactions list length - final reactions1 = - await pinnedMessageReactionDao.getReactions(messageId1); - final reactions2 = - await pinnedMessageReactionDao.getReactions(messageId2); + final reactions1 = await pinnedMessageReactionDao.getReactions(messageId1); + final reactions2 = await pinnedMessageReactionDao.getReactions(messageId2); expect(reactions1.length, insertedReactions1.length); expect(reactions2.length, insertedReactions2.length); @@ -197,10 +190,8 @@ void main() { await pinnedMessageReactionDao.deleteReactionsByMessageIds([messageId1]); // Fetched reactions length of only messageId1 should be empty - final fetchedReactions1 = - await pinnedMessageReactionDao.getReactions(messageId1); - final fetchedReactions2 = - await pinnedMessageReactionDao.getReactions(messageId2); + final fetchedReactions1 = await pinnedMessageReactionDao.getReactions(messageId1); + final fetchedReactions2 = await pinnedMessageReactionDao.getReactions(messageId2); expect(fetchedReactions1, isEmpty); expect(fetchedReactions2, isNotEmpty); }); @@ -212,22 +203,17 @@ void main() { // Fetched reaction list length should match // the inserted reactions list length - final reactions1 = - await pinnedMessageReactionDao.getReactions(messageId1); - final reactions2 = - await pinnedMessageReactionDao.getReactions(messageId2); + final reactions1 = await pinnedMessageReactionDao.getReactions(messageId1); + final reactions2 = await pinnedMessageReactionDao.getReactions(messageId2); expect(reactions1.length, insertedReactions1.length); expect(reactions2.length, insertedReactions2.length); // Deleting all the reactions of messageId1 and messageId2 - await pinnedMessageReactionDao - .deleteReactionsByMessageIds([messageId1, messageId2]); + await pinnedMessageReactionDao.deleteReactionsByMessageIds([messageId1, messageId2]); // Fetched reactions length of both messages should be empty - final fetchedReactions1 = - await pinnedMessageReactionDao.getReactions(messageId1); - final fetchedReactions2 = - await pinnedMessageReactionDao.getReactions(messageId2); + final fetchedReactions1 = await pinnedMessageReactionDao.getReactions(messageId1); + final fetchedReactions2 = await pinnedMessageReactionDao.getReactions(messageId2); expect(fetchedReactions1, isEmpty); expect(fetchedReactions2, isEmpty); }); diff --git a/packages/stream_chat_persistence/test/src/dao/poll_vote_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/poll_vote_dao_test.dart index 093aac84bf..2c13ee1cdb 100644 --- a/packages/stream_chat_persistence/test/src/dao/poll_vote_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/poll_vote_dao_test.dart @@ -44,9 +44,7 @@ void main() { (key, value) => MapEntry(key, value.length), ); - final users = latestVotesByOption.values - .expand((it) => it.map((it) => it.user!)) - .toList(); + final users = latestVotesByOption.values.expand((it) => it.map((it) => it.user!)).toList(); final poll = Poll( id: pollId, @@ -114,11 +112,13 @@ void main() { final fetchedPollVotes = await pollVoteDao.getPollVotes(pollId); expect(fetchedPollVotes.length, pollVotes.length + 1); expect( - fetchedPollVotes.any((it) => - it.id == newPollVote.id && - it.pollId == newPollVote.pollId && - it.optionId == newPollVote.optionId && - it.answerText == newPollVote.answerText), + fetchedPollVotes.any( + (it) => + it.id == newPollVote.id && + it.pollId == newPollVote.pollId && + it.optionId == newPollVote.optionId && + it.answerText == newPollVote.answerText, + ), true, ); }); diff --git a/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart index ddd01f09ff..2252641bc1 100644 --- a/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart @@ -102,15 +102,13 @@ void main() { expect(reactions, isEmpty); // Adding sample reactions - final insertedReactions = - await _prepareReactionData(messageId, userId: userId); + final insertedReactions = await _prepareReactionData(messageId, userId: userId); expect(insertedReactions, isNotEmpty); // Fetched reaction length should match inserted reactions length. // Every reaction messageId should match the provided messageId. // Every reaction userId should match the provided userId. - final fetchedReactions = - await reactionDao.getReactionsByUserId(messageId, userId); + final fetchedReactions = await reactionDao.getReactionsByUserId(messageId, userId); expect(fetchedReactions.length, insertedReactions.length); expect(fetchedReactions.every((it) => it.messageId == messageId), true); expect(fetchedReactions.every((it) => it.userId == userId), true); diff --git a/packages/stream_chat_persistence/test/src/dao/read_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/read_dao_test.dart index 142daf4155..473e2de82f 100644 --- a/packages/stream_chat_persistence/test/src/dao/read_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/read_dao_test.dart @@ -56,10 +56,8 @@ void main() { expect(fetchedRead.user.id, insertedRead.user.id); expect(fetchedRead.lastRead, isSameDateAs(insertedRead.lastRead)); expect(fetchedRead.unreadMessages, insertedRead.unreadMessages); - expect(fetchedRead.lastDeliveredAt, - isSameDateAs(insertedRead.lastDeliveredAt)); - expect(fetchedRead.lastDeliveredMessageId, - insertedRead.lastDeliveredMessageId); + expect(fetchedRead.lastDeliveredAt, isSameDateAs(insertedRead.lastDeliveredAt)); + expect(fetchedRead.lastDeliveredMessageId, insertedRead.lastDeliveredMessageId); } }); @@ -89,16 +87,12 @@ void main() { final fetchedReads = await readDao.getReadsByCid(cid); expect(fetchedReads.length, insertedReads.length + 1); expect( - fetchedReads - .firstWhere((it) => it.user.id == copyRead.user.id) - .unreadMessages, + fetchedReads.firstWhere((it) => it.user.id == copyRead.user.id).unreadMessages, 33, ); expect( fetchedReads - .where((it) => - it.user.id == newRead.user.id && - it.unreadMessages == newRead.unreadMessages) + .where((it) => it.user.id == newRead.user.id && it.unreadMessages == newRead.unreadMessages) .isNotEmpty, true, ); diff --git a/packages/stream_chat_persistence/test/src/db/drift_chat_database_test.dart b/packages/stream_chat_persistence/test/src/db/drift_chat_database_test.dart index 996c152fb1..dd14acf8c1 100644 --- a/packages/stream_chat_persistence/test/src/db/drift_chat_database_test.dart +++ b/packages/stream_chat_persistence/test/src/db/drift_chat_database_test.dart @@ -4,8 +4,7 @@ import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_persistence/src/db/drift_chat_database.dart'; -DatabaseConnection _backgroundConnection() => - DatabaseConnection(NativeDatabase.memory()); +DatabaseConnection _backgroundConnection() => DatabaseConnection(NativeDatabase.memory()); void main() { test( diff --git a/packages/stream_chat_persistence/test/src/mapper/location_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/location_mapper_test.dart index 2d87c6cfd9..ba728659d2 100644 --- a/packages/stream_chat_persistence/test/src/mapper/location_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/location_mapper_test.dart @@ -92,8 +92,7 @@ void main() { expect(convertedLocation.messageId, originalLocation.messageId); expect(convertedLocation.latitude, originalLocation.latitude); expect(convertedLocation.longitude, originalLocation.longitude); - expect(convertedLocation.createdByDeviceId, - originalLocation.createdByDeviceId); + expect(convertedLocation.createdByDeviceId, originalLocation.createdByDeviceId); expect(convertedLocation.endAt, originalLocation.endAt); expect(convertedLocation.createdAt, originalLocation.createdAt); expect(convertedLocation.updatedAt, originalLocation.updatedAt); diff --git a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart index c4682b5e9e..be7cc74e9b 100644 --- a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart @@ -115,8 +115,7 @@ void main() { expect(message.shadowed, entity.shadowed); expect(message.showInChannel, entity.showInChannel); for (var i = 0; i < message.mentionedUsers.length; i++) { - final entityMentionedUser = - User.fromJson(jsonDecode(entity.mentionedUsers[i])); + final entityMentionedUser = User.fromJson(jsonDecode(entity.mentionedUsers[i])); expect(message.mentionedUsers[i].id, entityMentionedUser.id); } expect(message.replyCount, entity.replyCount); @@ -249,8 +248,7 @@ void main() { expect(entity.shadowed, message.shadowed); expect(entity.showInChannel, message.showInChannel); expect(entity.replyCount, message.replyCount); - expect( - entity.mentionedUsers, message.mentionedUsers.map(jsonEncode).toList()); + expect(entity.mentionedUsers, message.mentionedUsers.map(jsonEncode).toList()); expect(entity.state, jsonEncode(message.state)); expect(entity.localUpdatedAt, isSameDateAs(message.localUpdatedAt)); expect(entity.remoteUpdatedAt, isSameDateAs(message.remoteUpdatedAt)); diff --git a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart index 9a9171887e..170026665c 100644 --- a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart @@ -115,8 +115,7 @@ void main() { expect(message.shadowed, entity.shadowed); expect(message.showInChannel, entity.showInChannel); for (var i = 0; i < message.mentionedUsers.length; i++) { - final entityMentionedUser = - User.fromJson(jsonDecode(entity.mentionedUsers[i])); + final entityMentionedUser = User.fromJson(jsonDecode(entity.mentionedUsers[i])); expect(message.mentionedUsers[i].id, entityMentionedUser.id); } expect(message.replyCount, entity.replyCount); @@ -249,8 +248,7 @@ void main() { expect(entity.shadowed, message.shadowed); expect(entity.showInChannel, message.showInChannel); expect(entity.replyCount, message.replyCount); - expect( - entity.mentionedUsers, message.mentionedUsers.map(jsonEncode).toList()); + expect(entity.mentionedUsers, message.mentionedUsers.map(jsonEncode).toList()); expect(entity.state, jsonEncode(message.state)); expect(entity.localUpdatedAt, isSameDateAs(message.localUpdatedAt)); expect(entity.remoteUpdatedAt, isSameDateAs(message.remoteUpdatedAt)); diff --git a/packages/stream_chat_persistence/test/src/utils/date_matcher.dart b/packages/stream_chat_persistence/test/src/utils/date_matcher.dart index d19d25f2a6..1d49e4a719 100644 --- a/packages/stream_chat_persistence/test/src/utils/date_matcher.dart +++ b/packages/stream_chat_persistence/test/src/utils/date_matcher.dart @@ -1,7 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; -Matcher isSameDateAs(DateTime? targetDate) => - _IsSameDateAs(targetDate: targetDate); +Matcher isSameDateAs(DateTime? targetDate) => _IsSameDateAs(targetDate: targetDate); class _IsSameDateAs extends Matcher { const _IsSameDateAs({required this.targetDate}); @@ -19,6 +18,5 @@ class _IsSameDateAs extends Matcher { } @override - Description describe(Description description) => - description.add('is same date as $targetDate'); + Description describe(Description description) => description.add('is same date as $targetDate'); } diff --git a/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart index cf25e4c68b..3fe63bef88 100644 --- a/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart +++ b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart @@ -115,20 +115,16 @@ void main() { const parentId = 'testParentId'; final replies = List.generate(3, (index) => Message(id: 'testId$index')); - when(() => mockDatabase.messageDao.getThreadMessagesByParentId(parentId)) - .thenAnswer((_) async => replies); + when(() => mockDatabase.messageDao.getThreadMessagesByParentId(parentId)).thenAnswer((_) async => replies); final fetchedReplies = await client.getReplies(parentId); expect(fetchedReplies.length, replies.length); - verify(() => - mockDatabase.messageDao.getThreadMessagesByParentId(parentId)) - .called(1); + verify(() => mockDatabase.messageDao.getThreadMessagesByParentId(parentId)).called(1); }); test('getConnectionInfo', () async { final event = Event(); - when(() => mockDatabase.connectionEventDao.connectionEvent) - .thenAnswer((_) async => event); + when(() => mockDatabase.connectionEventDao.connectionEvent).thenAnswer((_) async => event); final fetchedEvent = await client.getConnectionInfo(); expect(fetchedEvent, isNotNull); @@ -138,8 +134,7 @@ void main() { test('getLastSyncAt', () async { final lastSync = DateTime.now(); - when(() => mockDatabase.connectionEventDao.lastSyncAt) - .thenAnswer((_) async => lastSync); + when(() => mockDatabase.connectionEventDao.lastSyncAt).thenAnswer((_) async => lastSync); final fetchedLastSync = await client.getLastSyncAt(); expect(fetchedLastSync, isSameDateAs(lastSync)); @@ -148,28 +143,23 @@ void main() { test('updateConnectionInfo', () async { final event = Event(); - when(() => mockDatabase.connectionEventDao.updateConnectionEvent(event)) - .thenAnswer((_) async => 1); + when(() => mockDatabase.connectionEventDao.updateConnectionEvent(event)).thenAnswer((_) async => 1); await client.updateConnectionInfo(event); - verify(() => mockDatabase.connectionEventDao.updateConnectionEvent(event)) - .called(1); + verify(() => mockDatabase.connectionEventDao.updateConnectionEvent(event)).called(1); }); test('updateLastSyncAt', () async { final lastSync = DateTime.now(); - when(() => mockDatabase.connectionEventDao.updateLastSyncAt(lastSync)) - .thenAnswer((_) async => 1); + when(() => mockDatabase.connectionEventDao.updateLastSyncAt(lastSync)).thenAnswer((_) async => 1); await client.updateLastSyncAt(lastSync); - verify(() => mockDatabase.connectionEventDao.updateLastSyncAt(lastSync)) - .called(1); + verify(() => mockDatabase.connectionEventDao.updateLastSyncAt(lastSync)).called(1); }); test('getChannelCids', () async { final channelCids = List.generate(3, (index) => 'testCid$index'); - when(() => mockDatabase.channelDao.cids) - .thenAnswer((_) async => channelCids); + when(() => mockDatabase.channelDao.cids).thenAnswer((_) async => channelCids); final fetchedChannelCids = await client.getChannelCids(); expect(fetchedChannelCids.length, channelCids.length); @@ -179,8 +169,7 @@ void main() { test('getChannelByCid', () async { const cid = 'testType:testId'; final channelModel = ChannelModel(cid: cid); - when(() => mockDatabase.channelDao.getChannelByCid(cid)) - .thenAnswer((_) async => channelModel); + when(() => mockDatabase.channelDao.getChannelByCid(cid)).thenAnswer((_) async => channelModel); final fetchedChannelModel = await client.getChannelByCid(cid); expect(fetchedChannelModel, isNotNull); @@ -191,8 +180,7 @@ void main() { test('getMembersByCid', () async { const cid = 'testCid'; final members = List.generate(3, (index) => Member()); - when(() => mockDatabase.memberDao.getMembersByCid(cid)) - .thenAnswer((_) async => members); + when(() => mockDatabase.memberDao.getMembersByCid(cid)).thenAnswer((_) async => members); final fetchedMembers = await client.getMembersByCid(cid); expect(fetchedMembers.length, members.length); @@ -209,8 +197,7 @@ void main() { lastReadMessageId: 'lastMessageId$index', ), ); - when(() => mockDatabase.readDao.getReadsByCid(cid)) - .thenAnswer((_) async => reads); + when(() => mockDatabase.readDao.getReadsByCid(cid)).thenAnswer((_) async => reads); final fetchedReads = await client.getReadsByCid(cid); expect(fetchedReads.length, reads.length); @@ -220,8 +207,7 @@ void main() { test('getMessagesByCid', () async { const cid = 'testCid'; final messages = List.generate(3, (index) => Message()); - when(() => mockDatabase.messageDao.getMessagesByCid(cid)) - .thenAnswer((_) async => messages); + when(() => mockDatabase.messageDao.getMessagesByCid(cid)).thenAnswer((_) async => messages); final fetchedMessages = await client.getMessagesByCid(cid); expect(fetchedMessages.length, messages.length); @@ -231,13 +217,11 @@ void main() { test('getPinnedMessagesByCid', () async { const cid = 'testCid'; final messages = List.generate(3, (index) => Message()); - when(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)) - .thenAnswer((_) async => messages); + when(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)).thenAnswer((_) async => messages); final fetchedMessages = await client.getPinnedMessagesByCid(cid); expect(fetchedMessages.length, messages.length); - verify(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)) - .called(1); + verify(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)).called(1); }); test('getChannelStateByCid', () async { @@ -262,21 +246,14 @@ void main() { ), ); - when(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)) - .thenAnswer((_) async => draft); - - when(() => mockDatabase.memberDao.getMembersByCid(cid)) - .thenAnswer((_) async => members); - when(() => mockDatabase.readDao.getReadsByCid(cid)) - .thenAnswer((_) async => reads); - when(() => mockDatabase.channelDao.getChannelByCid(cid)) - .thenAnswer((_) async => channel); - when(() => mockDatabase.messageDao.getMessagesByCid(cid)) - .thenAnswer((_) async => messages); - when(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)) - .thenAnswer((_) async => messages); - when(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)) - .thenAnswer((_) async => draft); + when(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)).thenAnswer((_) async => draft); + + when(() => mockDatabase.memberDao.getMembersByCid(cid)).thenAnswer((_) async => members); + when(() => mockDatabase.readDao.getReadsByCid(cid)).thenAnswer((_) async => reads); + when(() => mockDatabase.channelDao.getChannelByCid(cid)).thenAnswer((_) async => channel); + when(() => mockDatabase.messageDao.getMessagesByCid(cid)).thenAnswer((_) async => messages); + when(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)).thenAnswer((_) async => messages); + when(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)).thenAnswer((_) async => draft); final fetchedChannelState = await client.getChannelStateByCid(cid); expect(fetchedChannelState.messages?.length, messages.length); @@ -290,10 +267,8 @@ void main() { verify(() => mockDatabase.readDao.getReadsByCid(cid)).called(1); verify(() => mockDatabase.channelDao.getChannelByCid(cid)).called(1); verify(() => mockDatabase.messageDao.getMessagesByCid(cid)).called(1); - verify(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)) - .called(1); - verify(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)) - .called(1); + verify(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)).called(1); + verify(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)).called(1); }); group('getChannelState', () { @@ -323,21 +298,15 @@ void main() { ) .toList(growable: false); - when(() => mockDatabase.channelQueryDao.getChannels()) - .thenAnswer((_) async => channels); - when(() => mockDatabase.memberDao.getMembersByCid(cid)) - .thenAnswer((_) async => members); - when(() => mockDatabase.readDao.getReadsByCid(cid)) - .thenAnswer((_) async => reads); - when(() => mockDatabase.channelDao.getChannelByCid(cid)) - .thenAnswer((_) async => channel); - when(() => mockDatabase.messageDao.getMessagesByCid(cid, - messagePagination: any(named: 'messagePagination'))) - .thenAnswer((_) async => messages); - when(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)) - .thenAnswer((_) async => messages); - when(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)) - .thenAnswer((_) async => null); + when(() => mockDatabase.channelQueryDao.getChannels()).thenAnswer((_) async => channels); + when(() => mockDatabase.memberDao.getMembersByCid(cid)).thenAnswer((_) async => members); + when(() => mockDatabase.readDao.getReadsByCid(cid)).thenAnswer((_) async => reads); + when(() => mockDatabase.channelDao.getChannelByCid(cid)).thenAnswer((_) async => channel); + when( + () => mockDatabase.messageDao.getMessagesByCid(cid, messagePagination: any(named: 'messagePagination')), + ).thenAnswer((_) async => messages); + when(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)).thenAnswer((_) async => messages); + when(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)).thenAnswer((_) async => null); final fetchedChannelStates = await client.getChannelStates(); expect(fetchedChannelStates.length, channelStates.length); @@ -347,8 +316,7 @@ void main() { final fetched = fetchedChannelStates[i]; expect(fetched.members?.length, original.members?.length); expect(fetched.messages?.length, original.messages?.length); - expect( - fetched.pinnedMessages?.length, original.pinnedMessages?.length); + expect(fetched.pinnedMessages?.length, original.pinnedMessages?.length); expect(fetched.read?.length, original.read?.length); expect(fetched.channel!.cid, original.channel!.cid); } @@ -357,93 +325,74 @@ void main() { verify(() => mockDatabase.memberDao.getMembersByCid(cid)).called(3); verify(() => mockDatabase.readDao.getReadsByCid(cid)).called(3); verify(() => mockDatabase.channelDao.getChannelByCid(cid)).called(3); - verify(() => mockDatabase.messageDao.getMessagesByCid(cid, - messagePagination: any(named: 'messagePagination'))).called(3); - verify(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)) - .called(3); - verify(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)) - .called(3); + verify( + () => mockDatabase.messageDao.getMessagesByCid(cid, messagePagination: any(named: 'messagePagination')), + ).called(3); + verify(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)).called(3); + verify(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)).called(3); }); }); test('updateChannelQueries', () async { final filter = Filter.in_('members', const ['testUserId']); const cids = []; - when(() => - mockDatabase.channelQueryDao.updateChannelQueries(filter, cids)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.channelQueryDao.updateChannelQueries(filter, cids)).thenAnswer((_) => Future.value()); await client.updateChannelQueries(filter, cids); - verify(() => - mockDatabase.channelQueryDao.updateChannelQueries(filter, cids)) - .called(1); + verify(() => mockDatabase.channelQueryDao.updateChannelQueries(filter, cids)).called(1); }); test('deleteMessageById', () async { const messageId = 'testMessageId'; - when(() => mockDatabase.messageDao.deleteMessageByIds([messageId])) - .thenAnswer((_) async => 1); + when(() => mockDatabase.messageDao.deleteMessageByIds([messageId])).thenAnswer((_) async => 1); await client.deleteMessageById(messageId); - verify(() => mockDatabase.messageDao.deleteMessageByIds([messageId])) - .called(1); + verify(() => mockDatabase.messageDao.deleteMessageByIds([messageId])).called(1); }); test('deletePinnedMessageById', () async { const messageId = 'testMessageId'; - when(() => mockDatabase.pinnedMessageDao.deleteMessageByIds([messageId])) - .thenAnswer((_) async => 1); + when(() => mockDatabase.pinnedMessageDao.deleteMessageByIds([messageId])).thenAnswer((_) async => 1); await client.deletePinnedMessageById(messageId); - verify(() => - mockDatabase.pinnedMessageDao.deleteMessageByIds([messageId])) - .called(1); + verify(() => mockDatabase.pinnedMessageDao.deleteMessageByIds([messageId])).called(1); }); test('deleteMessageByIds', () async { const messageIds = []; - when(() => mockDatabase.messageDao.deleteMessageByIds(messageIds)) - .thenAnswer((_) async => 1); + when(() => mockDatabase.messageDao.deleteMessageByIds(messageIds)).thenAnswer((_) async => 1); await client.deleteMessageByIds(messageIds); - verify(() => mockDatabase.messageDao.deleteMessageByIds(messageIds)) - .called(1); + verify(() => mockDatabase.messageDao.deleteMessageByIds(messageIds)).called(1); }); test('deletePinnedMessageByIds', () async { const messageIds = []; - when(() => mockDatabase.pinnedMessageDao.deleteMessageByIds(messageIds)) - .thenAnswer((_) async => 1); + when(() => mockDatabase.pinnedMessageDao.deleteMessageByIds(messageIds)).thenAnswer((_) async => 1); await client.deletePinnedMessageByIds(messageIds); - verify(() => mockDatabase.pinnedMessageDao.deleteMessageByIds(messageIds)) - .called(1); + verify(() => mockDatabase.pinnedMessageDao.deleteMessageByIds(messageIds)).called(1); }); test('deleteMessageByCid', () async { const cid = 'testCid'; - when(() => mockDatabase.messageDao.deleteMessageByCids([cid])) - .thenAnswer((_) async => 1); + when(() => mockDatabase.messageDao.deleteMessageByCids([cid])).thenAnswer((_) async => 1); await client.deleteMessageByCid(cid); - verify(() => mockDatabase.messageDao.deleteMessageByCids([cid])) - .called(1); + verify(() => mockDatabase.messageDao.deleteMessageByCids([cid])).called(1); }); test('deletePinnedMessageByCid', () async { const cid = 'testCid'; - when(() => mockDatabase.pinnedMessageDao.deleteMessageByCids([cid])) - .thenAnswer((_) async => 1); + when(() => mockDatabase.pinnedMessageDao.deleteMessageByCids([cid])).thenAnswer((_) async => 1); await client.deletePinnedMessageByCid(cid); - verify(() => mockDatabase.pinnedMessageDao.deleteMessageByCids([cid])) - .called(1); + verify(() => mockDatabase.pinnedMessageDao.deleteMessageByCids([cid])).called(1); }); test('deleteMessageByCids', () async { const cids = []; - when(() => mockDatabase.messageDao.deleteMessageByCids(cids)) - .thenAnswer((_) async => 1); + when(() => mockDatabase.messageDao.deleteMessageByCids(cids)).thenAnswer((_) async => 1); await client.deleteMessageByCids(cids); verify(() => mockDatabase.messageDao.deleteMessageByCids(cids)).called(1); @@ -451,18 +400,15 @@ void main() { test('deletePinnedMessageByCids', () async { const cids = []; - when(() => mockDatabase.pinnedMessageDao.deleteMessageByCids(cids)) - .thenAnswer((_) async => 1); + when(() => mockDatabase.pinnedMessageDao.deleteMessageByCids(cids)).thenAnswer((_) async => 1); await client.deletePinnedMessageByCids(cids); - verify(() => mockDatabase.pinnedMessageDao.deleteMessageByCids(cids)) - .called(1); + verify(() => mockDatabase.pinnedMessageDao.deleteMessageByCids(cids)).called(1); }); test('deleteChannels', () async { const cids = []; - when(() => mockDatabase.channelDao.deleteChannelByCids(cids)) - .thenAnswer((_) async => 1); + when(() => mockDatabase.channelDao.deleteChannelByCids(cids)).thenAnswer((_) async => 1); await client.deleteChannels(cids); verify(() => mockDatabase.channelDao.deleteChannelByCids(cids)).called(1); @@ -472,12 +418,10 @@ void main() { const cid = 'testCid'; final messages = List.generate(3, (index) => Message()); - when(() => mockDatabase.messageDao.bulkUpdateMessages({cid: messages})) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.messageDao.bulkUpdateMessages({cid: messages})).thenAnswer((_) => Future.value()); await client.updateMessages(cid, messages); - verify(() => mockDatabase.messageDao.bulkUpdateMessages({cid: messages})) - .called(1); + verify(() => mockDatabase.messageDao.bulkUpdateMessages({cid: messages})).called(1); }); test('updatePinnedMessages', () async { @@ -495,8 +439,7 @@ void main() { test('getChannelThreads', () async { const cid = 'testCid'; - final messages = - List.generate(3, (index) => Message(parentId: 'testParentId$index')); + final messages = List.generate(3, (index) => Message(parentId: 'testParentId$index')); final threads = messages.fold>>( {}, (prev, curr) => prev @@ -506,8 +449,7 @@ void main() { ifAbsent: () => [], ), ); - when(() => mockDatabase.messageDao.getThreadMessages(cid)) - .thenAnswer((realInvocation) async => messages); + when(() => mockDatabase.messageDao.getThreadMessages(cid)).thenAnswer((realInvocation) async => messages); final fetchedThreads = await client.getChannelThreads(cid); expect(fetchedThreads.length, threads.length); @@ -523,8 +465,7 @@ void main() { test('updateChannels', () async { const cid = 'testType:testId'; final channels = List.generate(3, (index) => ChannelModel(cid: cid)); - when(() => mockDatabase.channelDao.updateChannels(channels)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.channelDao.updateChannels(channels)).thenAnswer((_) => Future.value()); await client.updateChannels(channels); verify(() => mockDatabase.channelDao.updateChannels(channels)).called(1); @@ -533,10 +474,8 @@ void main() { test('updatePolls', () async { const name = 'testPollName'; final options = List.generate(3, (index) => PollOption(text: '$index')); - final polls = - List.generate(3, (index) => Poll(name: name, options: options)); - when(() => mockDatabase.pollDao.updatePolls(polls)) - .thenAnswer((_) => Future.value()); + final polls = List.generate(3, (index) => Poll(name: name, options: options)); + when(() => mockDatabase.pollDao.updatePolls(polls)).thenAnswer((_) => Future.value()); await client.updatePolls(polls); verify(() => mockDatabase.pollDao.updatePolls(polls)).called(1); @@ -544,43 +483,35 @@ void main() { test('deletePollsByIds', () async { final pollIds = ['testPollId']; - when(() => mockDatabase.pollDao.deletePollsByIds(pollIds)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.pollDao.deletePollsByIds(pollIds)).thenAnswer((_) => Future.value()); await client.deletePollsByIds(pollIds); verify(() => mockDatabase.pollDao.deletePollsByIds(pollIds)).called(1); }); test('updatePollVotes', () async { - final pollVotes = List.generate( - 3, (index) => PollVote(id: '$index', optionId: 'testOptionId$index')); - when(() => mockDatabase.pollVoteDao.updatePollVotes(pollVotes)) - .thenAnswer((_) => Future.value()); + final pollVotes = List.generate(3, (index) => PollVote(id: '$index', optionId: 'testOptionId$index')); + when(() => mockDatabase.pollVoteDao.updatePollVotes(pollVotes)).thenAnswer((_) => Future.value()); await client.updatePollVotes(pollVotes); - verify(() => mockDatabase.pollVoteDao.updatePollVotes(pollVotes)) - .called(1); + verify(() => mockDatabase.pollVoteDao.updatePollVotes(pollVotes)).called(1); }); test('deletePollVotesByPollIds', () async { final pollIds = ['testPollId']; - when(() => mockDatabase.pollVoteDao.deletePollVotesByPollIds(pollIds)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.pollVoteDao.deletePollVotesByPollIds(pollIds)).thenAnswer((_) => Future.value()); await client.deletePollVotesByPollIds(pollIds); - verify(() => mockDatabase.pollVoteDao.deletePollVotesByPollIds(pollIds)) - .called(1); + verify(() => mockDatabase.pollVoteDao.deletePollVotesByPollIds(pollIds)).called(1); }); test('updateMembers', () async { const cid = 'testCid'; final members = List.generate(3, (index) => Member()); - when(() => mockDatabase.memberDao.bulkUpdateMembers({cid: members})) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.memberDao.bulkUpdateMembers({cid: members})).thenAnswer((_) => Future.value()); await client.updateMembers(cid, members); - verify(() => mockDatabase.memberDao.bulkUpdateMembers({cid: members})) - .called(1); + verify(() => mockDatabase.memberDao.bulkUpdateMembers({cid: members})).called(1); }); test('updateReads', () async { @@ -593,18 +524,15 @@ void main() { lastReadMessageId: 'lastMessageId$index', ), ); - when(() => mockDatabase.readDao.bulkUpdateReads({cid: reads})) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.readDao.bulkUpdateReads({cid: reads})).thenAnswer((_) => Future.value()); await client.updateReads(cid, reads); - verify(() => mockDatabase.readDao.bulkUpdateReads({cid: reads})) - .called(1); + verify(() => mockDatabase.readDao.bulkUpdateReads({cid: reads})).called(1); }); test('updateUsers', () async { final users = List.generate(3, (index) => User(id: 'testUserId$index')); - when(() => mockDatabase.userDao.updateUsers(users)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.userDao.updateUsers(users)).thenAnswer((_) => Future.value()); await client.updateUsers(users); verify(() => mockDatabase.userDao.updateUsers(users)).called(1); @@ -615,12 +543,10 @@ void main() { 3, (index) => Reaction(type: 'testType$index'), ); - when(() => mockDatabase.reactionDao.updateReactions(reactions)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.reactionDao.updateReactions(reactions)).thenAnswer((_) => Future.value()); await client.updateReactions(reactions); - verify(() => mockDatabase.reactionDao.updateReactions(reactions)) - .called(1); + verify(() => mockDatabase.reactionDao.updateReactions(reactions)).called(1); }); test('updatePinnedMessageReactions', () async { @@ -628,43 +554,33 @@ void main() { 3, (index) => Reaction(type: 'testType$index'), ); - when(() => - mockDatabase.pinnedMessageReactionDao.updateReactions(reactions)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.pinnedMessageReactionDao.updateReactions(reactions)).thenAnswer((_) => Future.value()); await client.updatePinnedMessageReactions(reactions); - verify(() => - mockDatabase.pinnedMessageReactionDao.updateReactions(reactions)) - .called(1); + verify(() => mockDatabase.pinnedMessageReactionDao.updateReactions(reactions)).called(1); }); test('deleteReactionsByMessageId', () async { final messageIds = []; - when(() => - mockDatabase.reactionDao.deleteReactionsByMessageIds(messageIds)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.reactionDao.deleteReactionsByMessageIds(messageIds)).thenAnswer((_) => Future.value()); await client.deleteReactionsByMessageId(messageIds); - verify(() => - mockDatabase.reactionDao.deleteReactionsByMessageIds(messageIds)) - .called(1); + verify(() => mockDatabase.reactionDao.deleteReactionsByMessageIds(messageIds)).called(1); }); test('deletePinnedMessageReactionsByMessageId', () async { final messageIds = []; - when(() => mockDatabase.pinnedMessageReactionDao - .deleteReactionsByMessageIds(messageIds)) - .thenAnswer((_) => Future.value()); + when( + () => mockDatabase.pinnedMessageReactionDao.deleteReactionsByMessageIds(messageIds), + ).thenAnswer((_) => Future.value()); await client.deletePinnedMessageReactionsByMessageId(messageIds); - verify(() => mockDatabase.pinnedMessageReactionDao - .deleteReactionsByMessageIds(messageIds)).called(1); + verify(() => mockDatabase.pinnedMessageReactionDao.deleteReactionsByMessageIds(messageIds)).called(1); }); test('deleteMembersByCids', () async { final cids = []; - when(() => mockDatabase.memberDao.deleteMemberByCids(cids)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.memberDao.deleteMemberByCids(cids)).thenAnswer((_) => Future.value()); await client.deleteMembersByCids(cids); verify(() => mockDatabase.memberDao.deleteMemberByCids(cids)).called(1); @@ -672,12 +588,10 @@ void main() { test('deleteDraftMessagesByCids', () async { final cids = []; - when(() => mockDatabase.draftMessageDao.deleteDraftMessagesByCids(cids)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.draftMessageDao.deleteDraftMessagesByCids(cids)).thenAnswer((_) => Future.value()); await client.deleteDraftMessagesByCids(cids); - verify(() => mockDatabase.draftMessageDao.deleteDraftMessagesByCids(cids)) - .called(1); + verify(() => mockDatabase.draftMessageDao.deleteDraftMessagesByCids(cids)).called(1); }); test('getDraftMessageByCid', () async { @@ -693,16 +607,14 @@ void main() { ), ); - when(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)) - .thenAnswer((_) async => draft); + when(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)).thenAnswer((_) async => draft); final fetchedDraft = await client.getDraftMessageByCid(cid); expect(fetchedDraft, isNotNull); expect(fetchedDraft!.channelCid, cid); expect(fetchedDraft.message.id, draft.message.id); expect(fetchedDraft.message.text, draft.message.text); - verify(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)) - .called(1); + verify(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)).called(1); }); test('updateDraftMessages', () async { @@ -718,24 +630,22 @@ void main() { ), ); - when(() => mockDatabase.draftMessageDao.updateDraftMessages(drafts)) - .thenAnswer((_) async {}); + when(() => mockDatabase.draftMessageDao.updateDraftMessages(drafts)).thenAnswer((_) async {}); await client.updateDraftMessages(drafts); - verify(() => mockDatabase.draftMessageDao.updateDraftMessages(drafts)) - .called(1); + verify(() => mockDatabase.draftMessageDao.updateDraftMessages(drafts)).called(1); }); test('deleteDraftMessageByCid', () async { const cid = 'testCid'; const parentId = 'testParentId'; - when(() => mockDatabase.draftMessageDao.deleteDraftMessageByCid(cid, - parentId: parentId)).thenAnswer((_) async {}); + when( + () => mockDatabase.draftMessageDao.deleteDraftMessageByCid(cid, parentId: parentId), + ).thenAnswer((_) async {}); await client.deleteDraftMessageByCid(cid, parentId: parentId); - verify(() => mockDatabase.draftMessageDao - .deleteDraftMessageByCid(cid, parentId: parentId)).called(1); + verify(() => mockDatabase.draftMessageDao.deleteDraftMessageByCid(cid, parentId: parentId)).called(1); }); test('getLocationsByCid', () async { @@ -754,8 +664,7 @@ void main() { ), ); - when(() => mockDatabase.locationDao.getLocationsByCid(cid)) - .thenAnswer((_) async => locations); + when(() => mockDatabase.locationDao.getLocationsByCid(cid)).thenAnswer((_) async => locations); final fetchedLocations = await client.getLocationsByCid(cid); expect(fetchedLocations.length, locations.length); @@ -775,14 +684,12 @@ void main() { updatedAt: DateTime.now(), ); - when(() => mockDatabase.locationDao.getLocationByMessageId(messageId)) - .thenAnswer((_) async => location); + when(() => mockDatabase.locationDao.getLocationByMessageId(messageId)).thenAnswer((_) async => location); final fetchedLocation = await client.getLocationByMessageId(messageId); expect(fetchedLocation, isNotNull); expect(fetchedLocation!.messageId, messageId); - verify(() => mockDatabase.locationDao.getLocationByMessageId(messageId)) - .called(1); + verify(() => mockDatabase.locationDao.getLocationByMessageId(messageId)).called(1); }); test('updateLocations', () async { @@ -800,22 +707,18 @@ void main() { ), ); - when(() => mockDatabase.locationDao.updateLocations(locations)) - .thenAnswer((_) async {}); + when(() => mockDatabase.locationDao.updateLocations(locations)).thenAnswer((_) async {}); await client.updateLocations(locations); - verify(() => mockDatabase.locationDao.updateLocations(locations)) - .called(1); + verify(() => mockDatabase.locationDao.updateLocations(locations)).called(1); }); test('deleteLocationsByCid', () async { const cid = 'testCid'; - when(() => mockDatabase.locationDao.deleteLocationsByCid(cid)) - .thenAnswer((_) async {}); + when(() => mockDatabase.locationDao.deleteLocationsByCid(cid)).thenAnswer((_) async {}); await client.deleteLocationsByCid(cid); - verify(() => mockDatabase.locationDao.deleteLocationsByCid(cid)) - .called(1); + verify(() => mockDatabase.locationDao.deleteLocationsByCid(cid)).called(1); }); test('deleteLocationsByMessageIds', () async { @@ -834,21 +737,24 @@ void main() { const userId = 'testUserId'; const cid = 'testCid'; - test('calls deleteMessagesByUser on both DAOs with hard delete', - () async { - when(() => mockDatabase.messageDao.deleteMessagesByUser( - cid: cid, - userId: userId, - hardDelete: true, - deletedAt: any(named: 'deletedAt'), - )).thenAnswer((_) async => 1); - - when(() => mockDatabase.pinnedMessageDao.deleteMessagesByUser( - cid: cid, - userId: userId, - hardDelete: true, - deletedAt: any(named: 'deletedAt'), - )).thenAnswer((_) async => 1); + test('calls deleteMessagesByUser on both DAOs with hard delete', () async { + when( + () => mockDatabase.messageDao.deleteMessagesByUser( + cid: cid, + userId: userId, + hardDelete: true, + deletedAt: any(named: 'deletedAt'), + ), + ).thenAnswer((_) async => 1); + + when( + () => mockDatabase.pinnedMessageDao.deleteMessagesByUser( + cid: cid, + userId: userId, + hardDelete: true, + deletedAt: any(named: 'deletedAt'), + ), + ).thenAnswer((_) async => 1); await client.deleteMessagesFromUser( cid: cid, @@ -856,38 +762,45 @@ void main() { hardDelete: true, ); - verify(() => mockDatabase.messageDao.deleteMessagesByUser( - cid: cid, - userId: userId, - hardDelete: true, - deletedAt: any(named: 'deletedAt'), - )).called(1); - - verify(() => mockDatabase.pinnedMessageDao.deleteMessagesByUser( - cid: cid, - userId: userId, - hardDelete: true, - deletedAt: any(named: 'deletedAt'), - )).called(1); + verify( + () => mockDatabase.messageDao.deleteMessagesByUser( + cid: cid, + userId: userId, + hardDelete: true, + deletedAt: any(named: 'deletedAt'), + ), + ).called(1); + + verify( + () => mockDatabase.pinnedMessageDao.deleteMessagesByUser( + cid: cid, + userId: userId, + hardDelete: true, + deletedAt: any(named: 'deletedAt'), + ), + ).called(1); }); - test('calls deleteMessagesByUser on both DAOs with soft delete', - () async { + test('calls deleteMessagesByUser on both DAOs with soft delete', () async { final deletedAt = DateTime.now(); - when(() => mockDatabase.messageDao.deleteMessagesByUser( - cid: cid, - userId: userId, - hardDelete: false, - deletedAt: deletedAt, - )).thenAnswer((_) async => 1); - - when(() => mockDatabase.pinnedMessageDao.deleteMessagesByUser( - cid: cid, - userId: userId, - hardDelete: false, - deletedAt: deletedAt, - )).thenAnswer((_) async => 1); + when( + () => mockDatabase.messageDao.deleteMessagesByUser( + cid: cid, + userId: userId, + hardDelete: false, + deletedAt: deletedAt, + ), + ).thenAnswer((_) async => 1); + + when( + () => mockDatabase.pinnedMessageDao.deleteMessagesByUser( + cid: cid, + userId: userId, + hardDelete: false, + deletedAt: deletedAt, + ), + ).thenAnswer((_) async => 1); await client.deleteMessagesFromUser( cid: cid, @@ -896,50 +809,62 @@ void main() { deletedAt: deletedAt, ); - verify(() => mockDatabase.messageDao.deleteMessagesByUser( - cid: cid, - userId: userId, - hardDelete: false, - deletedAt: deletedAt, - )).called(1); - - verify(() => mockDatabase.pinnedMessageDao.deleteMessagesByUser( - cid: cid, - userId: userId, - hardDelete: false, - deletedAt: deletedAt, - )).called(1); + verify( + () => mockDatabase.messageDao.deleteMessagesByUser( + cid: cid, + userId: userId, + hardDelete: false, + deletedAt: deletedAt, + ), + ).called(1); + + verify( + () => mockDatabase.pinnedMessageDao.deleteMessagesByUser( + cid: cid, + userId: userId, + hardDelete: false, + deletedAt: deletedAt, + ), + ).called(1); }); test('calls deleteMessagesByUser without cid when cid is null', () async { - when(() => mockDatabase.messageDao.deleteMessagesByUser( - userId: userId, - hardDelete: true, - deletedAt: any(named: 'deletedAt'), - )).thenAnswer((_) async => 1); - - when(() => mockDatabase.pinnedMessageDao.deleteMessagesByUser( - userId: userId, - hardDelete: true, - deletedAt: any(named: 'deletedAt'), - )).thenAnswer((_) async => 1); + when( + () => mockDatabase.messageDao.deleteMessagesByUser( + userId: userId, + hardDelete: true, + deletedAt: any(named: 'deletedAt'), + ), + ).thenAnswer((_) async => 1); + + when( + () => mockDatabase.pinnedMessageDao.deleteMessagesByUser( + userId: userId, + hardDelete: true, + deletedAt: any(named: 'deletedAt'), + ), + ).thenAnswer((_) async => 1); await client.deleteMessagesFromUser( userId: userId, hardDelete: true, ); - verify(() => mockDatabase.messageDao.deleteMessagesByUser( - userId: userId, - hardDelete: true, - deletedAt: any(named: 'deletedAt'), - )).called(1); - - verify(() => mockDatabase.pinnedMessageDao.deleteMessagesByUser( - userId: userId, - hardDelete: true, - deletedAt: any(named: 'deletedAt'), - )).called(1); + verify( + () => mockDatabase.messageDao.deleteMessagesByUser( + userId: userId, + hardDelete: true, + deletedAt: any(named: 'deletedAt'), + ), + ).called(1); + + verify( + () => mockDatabase.pinnedMessageDao.deleteMessagesByUser( + userId: userId, + hardDelete: true, + deletedAt: any(named: 'deletedAt'), + ), + ).called(1); }); }); diff --git a/pubspec.lock b/pubspec.lock index 9eed33197b..b4283bfbc5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f + sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" url: "https://pub.dev" source: hosted - version: "82.0.0" + version: "93.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "13c1e6c6fd460522ea840abec3f677cc226f5fec7872c04ad7b425517ccf54f7" + sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b url: "https://pub.dev" source: hosted - version: "7.4.4" + version: "10.0.1" ansi_styles: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: built_value - sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4 + sha256: "7931c90b84bc573fef103548e354258ae4c9d28d140e41961df6843c5d60d4d8" url: "https://pub.dev" source: hosted - version: "8.9.5" + version: "8.12.3" charcode: dependency: transitive description: @@ -77,18 +77,18 @@ packages: dependency: transitive description: name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.0.4" cli_launcher: dependency: transitive description: name: cli_launcher - sha256: "5e7e0282b79e8642edd6510ee468ae2976d847a0a29b3916e85f5fa1bfe24005" + sha256: "17d2744fb9a254c49ec8eda582536abe714ea0131533e24389843a4256f82eac" url: "https://pub.dev" source: hosted - version: "0.3.1" + version: "0.3.2+1" cli_util: dependency: transitive description: @@ -97,22 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.2" - clock: - dependency: transitive - description: - name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" - source: hosted - version: "1.1.2" code_builder: dependency: "direct dev" description: name: code_builder - sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" url: "https://pub.dev" source: hosted - version: "4.10.1" + version: "4.11.1" collection: dependency: transitive description: @@ -125,10 +117,10 @@ packages: dependency: transitive description: name: conventional_commit - sha256: fad254feb6fb8eace2be18855176b0a4b97e0d50e416ff0fe590d5ba83735d34 + sha256: c40b1b449ce2a63fa2ce852f35e3890b1e182f5951819934c0e4a66254bc0dc3 url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.6.1+1" convert: dependency: transitive description: @@ -141,18 +133,18 @@ packages: dependency: transitive description: name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" dart_style: dependency: "direct dev" description: name: dart_style - sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" + sha256: "15a7db352c8fc6a4d2bc475ba901c25b39fe7157541da4c16eacce6f8be83e49" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.5" file: dependency: transitive description: @@ -189,10 +181,10 @@ packages: dependency: transitive description: name: http - sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.6.0" http_parser: dependency: transitive description: @@ -201,14 +193,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" - intl: - dependency: transitive - description: - name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf - url: "https://pub.dev" - source: hosted - version: "0.19.0" io: dependency: transitive description: @@ -221,42 +205,42 @@ packages: dependency: transitive description: name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + sha256: "805fa86df56383000f640384b282ce0cb8431f1a7a2396de92fb66186d8c57df" url: "https://pub.dev" source: hosted - version: "4.9.0" + version: "4.10.0" matcher: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.18" melos: dependency: "direct dev" description: name: melos - sha256: "3f3ab3f902843d1e5a1b1a4dd39a4aca8ba1056f2d32fd8995210fa2843f646f" + sha256: "4280dc46bd5b741887cce1e67e5c1a6aaf3c22310035cf5bd33dceeeda62ed22" url: "https://pub.dev" source: hosted - version: "6.3.2" + version: "6.3.3" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "9f29b9bcc8ee287b1a31e0d01be0eae99a930dbffdaecf04b3f3d82a969f296f" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.18.1" mustache_template: dependency: transitive description: name: mustache_template - sha256: a46e26f91445bfb0b60519be280555b06792460b27b19e2b19ad5b9740df5d1c + sha256: "4326d0002ff58c74b9486990ccbdab08157fca3c996fe9e197aff9d61badf307" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.3" package_config: dependency: transitive description: @@ -285,18 +269,18 @@ packages: dependency: transitive description: name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" url: "https://pub.dev" source: hosted - version: "1.5.1" + version: "1.5.2" process: dependency: transitive description: name: process - sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" + sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744 url: "https://pub.dev" source: hosted - version: "5.0.3" + version: "5.0.5" prompts: dependency: transitive description: @@ -317,10 +301,10 @@ packages: dependency: transitive description: name: pub_updater - sha256: "54e8dc865349059ebe7f163d6acce7c89eb958b8047e6d6e80ce93b13d7c9e60" + sha256: "739a0161d73a6974c0675b864fb0cf5147305f7b077b7f03a58fa7a9ab3e7e7d" url: "https://pub.dev" source: hosted - version: "0.4.0" + version: "0.5.0" pubspec_parse: dependency: transitive description: @@ -381,10 +365,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.9" typed_data: dependency: transitive description: @@ -397,10 +381,10 @@ packages: dependency: transitive description: name: watcher - sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.1" web: dependency: transitive description: @@ -421,9 +405,9 @@ packages: dependency: transitive description: name: yaml_edit - sha256: fb38626579fb345ad00e674e2af3a5c9b0cc4b9bfb8fd7f7ff322c7c9e62aef5 + sha256: ec709065bb2c911b336853b67f3732dd13e0336bd065cc2f1061d7610ddf45e3 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" sdks: - dart: ">=3.6.2 <4.0.0" + dart: ">=3.10.0 <4.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index a65d06f2b8..2ac3430d00 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter_workspace environment: - sdk: ^3.6.2 + sdk: ^3.10.0 dev_dependencies: code_builder: ^4.10.1 diff --git a/sample_app/lib/app.dart b/sample_app/lib/app.dart index 59b097cf55..7732b93fb4 100644 --- a/sample_app/lib/app.dart +++ b/sample_app/lib/app.dart @@ -7,8 +7,7 @@ import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart' hide Priority; -import 'package:flutter_local_notifications/flutter_local_notifications.dart' - hide Message; +import 'package:flutter_local_notifications/flutter_local_notifications.dart' hide Message; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; @@ -43,8 +42,7 @@ const notificationChannelDescription = 'Notifications for Stream messages'; const bool kIsIOS = bool.fromEnvironment('dart.io.is_ios'); // Initialize FlutterLocalNotificationsPlugin for background messages -final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = - FlutterLocalNotificationsPlugin(); +final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); /// Constructs callback for background notification handling. /// @@ -179,8 +177,7 @@ Future _showAndroidNotification({ await flutterLocalNotificationsPlugin.initialize( initializationSettings, onDidReceiveNotificationResponse: (NotificationResponse response) async { - debugPrint( - '[onBackgroundMessage] #firebase; notification clicked: ${response.payload}'); + debugPrint('[onBackgroundMessage] #firebase; notification clicked: ${response.payload}'); // The payload contains the channel information (channelType:channelId) // This will be handled when the app is opened }, @@ -199,10 +196,10 @@ Future _showAndroidNotification({ ); debugPrint( - '[onBackgroundMessage] #firebase; android notification shown successfully: ID=$notificationId, Title="$title"'); + '[onBackgroundMessage] #firebase; android notification shown successfully: ID=$notificationId, Title="$title"', + ); } catch (e) { - debugPrint( - '[onBackgroundMessage] #firebase; failed to show notification: $e'); + debugPrint('[onBackgroundMessage] #firebase; failed to show notification: $e'); } } @@ -220,21 +217,17 @@ Future _createNotificationChannel() async { ); // Create the channel - final androidPlugin = - flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>(); + final androidPlugin = flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation(); if (androidPlugin != null) { await androidPlugin.createNotificationChannel(channel); - debugPrint( - '[onBackgroundMessage] #firebase; notification channel created'); + debugPrint('[onBackgroundMessage] #firebase; notification channel created'); } else { - debugPrint( - '[onBackgroundMessage] #firebase; failed to resolve Android plugin'); + debugPrint('[onBackgroundMessage] #firebase; failed to resolve Android plugin'); } } catch (e) { - debugPrint( - '[onBackgroundMessage] #firebase; notification channel failed: $e'); + debugPrint('[onBackgroundMessage] #firebase; notification channel failed: $e'); } } @@ -315,10 +308,7 @@ class _StreamChatSampleAppState extends State Future _initFirebaseMessaging(StreamChatClient client) async { userIdSubscription?.cancel(); - userIdSubscription = client.state.currentUserStream - .map((it) => it?.id) - .distinct() - .listen((userId) async { + userIdSubscription = client.state.currentUserStream.map((it) => it?.id).distinct().listen((userId) async { // User logged in if (userId != null) { // Requests notification permission. @@ -326,14 +316,11 @@ class _StreamChatSampleAppState extends State // Sets callback for background messages. FirebaseMessaging.onBackgroundMessage(_onFirebaseBackgroundMessage); // Sets callback for the notification click event. - firebaseSubscriptions.add(FirebaseMessaging.onMessageOpenedApp - .listen(_onFirebaseMessageOpenedApp(client))); + firebaseSubscriptions.add(FirebaseMessaging.onMessageOpenedApp.listen(_onFirebaseMessageOpenedApp(client))); // Sets callback for foreground messages - firebaseSubscriptions.add(FirebaseMessaging.onMessage - .listen(_onFirebaseForegroundMessage(client))); + firebaseSubscriptions.add(FirebaseMessaging.onMessage.listen(_onFirebaseForegroundMessage(client))); // Sets callback for the token refresh event. - firebaseSubscriptions.add(FirebaseMessaging.instance.onTokenRefresh - .listen(_onFirebaseTokenRefresh(client))); + firebaseSubscriptions.add(FirebaseMessaging.instance.onTokenRefresh.listen(_onFirebaseTokenRefresh(client))); final token = await FirebaseMessaging.instance.getToken(); debugPrint('[onTokenInit] #firebase; token: $token'); @@ -386,8 +373,7 @@ class _StreamChatSampleAppState extends State /// Constructs callback for foreground notification handling. OnRemoteMessage _onFirebaseForegroundMessage(StreamChatClient client) { return (message) async { - debugPrint( - '[onForegroundMessage] #firebase; message: ${message.toMap()}'); + debugPrint('[onForegroundMessage] #firebase; message: ${message.toMap()}'); }; } @@ -449,8 +435,7 @@ class _StreamChatSampleAppState extends State if (localNotificationObserver != null) { localNotificationObserver!.dispose(); } - localNotificationObserver = LocalNotificationObserver( - _initNotifier.initData!.client, _navigatorKey); + localNotificationObserver = LocalNotificationObserver(_initNotifier.initData!.client, _navigatorKey); return router ??= GoRouter( refreshListenable: _initNotifier, @@ -458,10 +443,9 @@ class _StreamChatSampleAppState extends State navigatorKey: _navigatorKey, observers: [localNotificationObserver!], redirect: (context, state) { - final loggedIn = - _initNotifier.initData?.client.state.currentUser != null; - final loggingIn = state.matchedLocation == Routes.CHOOSE_USER.path || - state.matchedLocation == Routes.ADVANCED_OPTIONS.path; + final loggedIn = _initNotifier.initData?.client.state.currentUser != null; + final loggingIn = + state.matchedLocation == Routes.CHOOSE_USER.path || state.matchedLocation == Routes.ADVANCED_OPTIONS.path; if (!loggedIn) { return loggingIn ? null : Routes.CHOOSE_USER.path; diff --git a/sample_app/lib/firebase_options.dart b/sample_app/lib/firebase_options.dart index 683caab1ce..9fd43a439e 100644 --- a/sample_app/lib/firebase_options.dart +++ b/sample_app/lib/firebase_options.dart @@ -1,8 +1,7 @@ // File generated by FlutterFire CLI. // ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; -import 'package:flutter/foundation.dart' - show defaultTargetPlatform, kIsWeb, TargetPlatform; +import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform; /// Default [FirebaseOptions] for use with your Firebase apps. /// @@ -70,10 +69,8 @@ class DefaultFirebaseOptions { projectId: 'stream-chat-internal', databaseURL: 'https://stream-chat-internal.firebaseio.com', storageBucket: 'stream-chat-internal.appspot.com', - androidClientId: - '674907137625-0aa50j6b2i35ef9c52lsbk1v16otl492.apps.googleusercontent.com', - iosClientId: - '674907137625-flarfn9cefu4lermgpbc4b8rm8l15ian.apps.googleusercontent.com', + androidClientId: '674907137625-0aa50j6b2i35ef9c52lsbk1v16otl492.apps.googleusercontent.com', + iosClientId: '674907137625-flarfn9cefu4lermgpbc4b8rm8l15ian.apps.googleusercontent.com', iosBundleId: 'io.getstream.flutter', ); @@ -84,10 +81,8 @@ class DefaultFirebaseOptions { projectId: 'stream-chat-internal', databaseURL: 'https://stream-chat-internal.firebaseio.com', storageBucket: 'stream-chat-internal.appspot.com', - androidClientId: - '674907137625-0aa50j6b2i35ef9c52lsbk1v16otl492.apps.googleusercontent.com', - iosClientId: - '674907137625-p3msks3snq0h22l7ekpqcf0frr0vt8mg.apps.googleusercontent.com', + androidClientId: '674907137625-0aa50j6b2i35ef9c52lsbk1v16otl492.apps.googleusercontent.com', + iosClientId: '674907137625-p3msks3snq0h22l7ekpqcf0frr0vt8mg.apps.googleusercontent.com', iosBundleId: 'io.getstream.streamChatV1', ); } diff --git a/sample_app/lib/pages/advanced_options_page.dart b/sample_app/lib/pages/advanced_options_page.dart index 81ab732c02..b30811f5f8 100644 --- a/sample_app/lib/pages/advanced_options_page.dart +++ b/sample_app/lib/pages/advanced_options_page.dart @@ -132,8 +132,9 @@ class _AdvancedOptionsPageState extends State { centerTitle: true, title: Text( AppLocalizations.of(context).advancedOptions, - style: StreamChatTheme.of(context).textTheme.headlineBold.copyWith( - color: StreamChatTheme.of(context).colorTheme.textHighEmphasis), + style: StreamChatTheme.of( + context, + ).textTheme.headlineBold.copyWith(color: StreamChatTheme.of(context).colorTheme.textHighEmphasis), ), leading: IconButton( icon: const StreamSvgIcon(icon: StreamSvgIcons.left), @@ -164,9 +165,7 @@ class _AdvancedOptionsPageState extends State { validator: (value) { if (value!.isEmpty) { setState(() { - _apiKeyError = AppLocalizations.of(context) - .apiKeyError - .toUpperCase(); + _apiKeyError = AppLocalizations.of(context).apiKeyError.toUpperCase(); }); return _apiKeyError; } @@ -174,9 +173,7 @@ class _AdvancedOptionsPageState extends State { }, style: TextStyle( fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, ), decoration: InputDecoration( errorStyle: const TextStyle(height: 0, fontSize: 0), @@ -185,9 +182,7 @@ class _AdvancedOptionsPageState extends State { fontWeight: FontWeight.bold, color: _apiKeyError != null ? StreamChatTheme.of(context).colorTheme.accentError - : StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + : StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), border: UnderlineInputBorder( borderRadius: BorderRadius.circular(8), @@ -214,9 +209,7 @@ class _AdvancedOptionsPageState extends State { validator: (value) { if (value!.isEmpty) { setState(() { - _userIdError = AppLocalizations.of(context) - .userIdError - .toUpperCase(); + _userIdError = AppLocalizations.of(context).userIdError.toUpperCase(); }); return _userIdError; } @@ -224,9 +217,7 @@ class _AdvancedOptionsPageState extends State { }, style: TextStyle( fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, ), textInputAction: TextInputAction.next, decoration: InputDecoration( @@ -236,9 +227,7 @@ class _AdvancedOptionsPageState extends State { fontSize: 14, color: _userIdError != null ? StreamChatTheme.of(context).colorTheme.accentError - : StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + : StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), border: UnderlineInputBorder( borderRadius: BorderRadius.circular(8), @@ -264,9 +253,7 @@ class _AdvancedOptionsPageState extends State { validator: (value) { if (value!.isEmpty) { setState(() { - _userTokenError = AppLocalizations.of(context) - .userTokenError - .toUpperCase(); + _userTokenError = AppLocalizations.of(context).userTokenError.toUpperCase(); }); return _userTokenError; } @@ -274,9 +261,7 @@ class _AdvancedOptionsPageState extends State { }, style: TextStyle( fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, ), textInputAction: TextInputAction.next, decoration: InputDecoration( @@ -286,9 +271,7 @@ class _AdvancedOptionsPageState extends State { fontSize: 14, color: _userTokenError != null ? StreamChatTheme.of(context).colorTheme.accentError - : StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + : StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), border: UnderlineInputBorder( borderRadius: BorderRadius.circular(8), @@ -309,9 +292,7 @@ class _AdvancedOptionsPageState extends State { labelStyle: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), border: UnderlineInputBorder( borderRadius: BorderRadius.circular(8), @@ -326,14 +307,12 @@ class _AdvancedOptionsPageState extends State { ElevatedButton( style: ButtonStyle( backgroundColor: WidgetStateProperty.all( - Theme.of(context).brightness == Brightness.light - ? StreamChatTheme.of(context) - .colorTheme - .accentPrimary - : Colors.white), + Theme.of(context).brightness == Brightness.light + ? StreamChatTheme.of(context).colorTheme.accentPrimary + : Colors.white, + ), elevation: WidgetStateProperty.all(0), - padding: WidgetStateProperty.all( - const EdgeInsets.symmetric(vertical: 16)), + padding: WidgetStateProperty.all(const EdgeInsets.symmetric(vertical: 16)), shape: WidgetStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(26), @@ -346,9 +325,7 @@ class _AdvancedOptionsPageState extends State { style: TextStyle( fontSize: 16, color: Theme.of(context).brightness != Brightness.light - ? StreamChatTheme.of(context) - .colorTheme - .accentPrimary + ? StreamChatTheme.of(context).colorTheme.accentPrimary : Colors.white, ), ), diff --git a/sample_app/lib/pages/channel_file_display_screen.dart b/sample_app/lib/pages/channel_file_display_screen.dart index a3228ca68f..edf0705412 100644 --- a/sample_app/lib/pages/channel_file_display_screen.dart +++ b/sample_app/lib/pages/channel_file_display_screen.dart @@ -13,8 +13,7 @@ class ChannelFileDisplayScreen extends StatefulWidget { final StreamMessageThemeData messageTheme; @override - State createState() => - _ChannelFileDisplayScreenState(); + State createState() => _ChannelFileDisplayScreenState(); } class _ChannelFileDisplayScreenState extends State { @@ -55,88 +54,82 @@ class _ChannelFileDisplayScreenState extends State { ), body: ValueListenableBuilder( valueListenable: controller, - builder: ( - BuildContext context, - PagedValue value, - Widget? child, - ) { - return value.when( - (items, nextPageKey, error) { - if (items.isEmpty) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - StreamSvgIcon( - icon: StreamSvgIcons.files, - size: 136, - color: StreamChatTheme.of(context).colorTheme.disabled, + builder: + ( + BuildContext context, + PagedValue value, + Widget? child, + ) { + return value.when( + (items, nextPageKey, error) { + if (items.isEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + StreamSvgIcon( + icon: StreamSvgIcons.files, + size: 136, + color: StreamChatTheme.of(context).colorTheme.disabled, + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context).noFiles, + style: TextStyle( + fontSize: 14, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, + ), + ), + const SizedBox(height: 8), + Text( + AppLocalizations.of(context).filesAppearHere, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + ), + ), + ], ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context).noFiles, - style: TextStyle( - fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, - ), - ), - const SizedBox(height: 8), - Text( - AppLocalizations.of(context).filesAppearHere, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), - ), - ), - ], - ), - ); - } - final media = {}; - - for (final item in items) { - item.message.attachments - .where((e) => e.type == 'file') - .forEach((e) { - media[e] = item.message; - }); - } + ); + } + final media = {}; - return LazyLoadScrollView( - onEndOfPage: () async { - if (nextPageKey != null) { - controller.loadMore(nextPageKey); + for (final item in items) { + item.message.attachments.where((e) => e.type == 'file').forEach((e) { + media[e] = item.message; + }); } + + return LazyLoadScrollView( + onEndOfPage: () async { + if (nextPageKey != null) { + controller.loadMore(nextPageKey); + } + }, + child: ListView.builder( + itemBuilder: (context, position) { + return Padding( + padding: const EdgeInsets.all(1), + child: Padding( + padding: const EdgeInsets.all(8), + child: StreamFileAttachment( + message: media.values.toList()[position], + file: media.keys.toList()[position], + ), + ), + ); + }, + itemCount: media.length, + ), + ); }, - child: ListView.builder( - itemBuilder: (context, position) { - return Padding( - padding: const EdgeInsets.all(1), - child: Padding( - padding: const EdgeInsets.all(8), - child: StreamFileAttachment( - message: media.values.toList()[position], - file: media.keys.toList()[position], - ), - ), - ); - }, - itemCount: media.length, + loading: () => const Center( + child: CircularProgressIndicator(), ), + error: (_) => const Offstage(), ); }, - loading: () => const Center( - child: CircularProgressIndicator(), - ), - error: (_) => const Offstage(), - ); - }, ), ); } diff --git a/sample_app/lib/pages/channel_list_page.dart b/sample_app/lib/pages/channel_list_page.dart index 8355dd0413..5ffc5f9a07 100644 --- a/sample_app/lib/pages/channel_list_page.dart +++ b/sample_app/lib/pages/channel_list_page.dart @@ -40,9 +40,7 @@ class _ChannelListPageState extends State { children: [ StreamSvgIcon( icon: StreamSvgIcons.message, - color: _isSelected(0) - ? StreamChatTheme.of(context).colorTheme.textHighEmphasis - : Colors.grey, + color: _isSelected(0) ? StreamChatTheme.of(context).colorTheme.textHighEmphasis : Colors.grey, ), const PositionedDirectional( top: -4, @@ -56,9 +54,7 @@ class _ChannelListPageState extends State { BottomNavigationBarItem( icon: StreamSvgIcon( icon: StreamSvgIcons.mentions, - color: _isSelected(1) - ? StreamChatTheme.of(context).colorTheme.textHighEmphasis - : Colors.grey, + color: _isSelected(1) ? StreamChatTheme.of(context).colorTheme.textHighEmphasis : Colors.grey, ), label: AppLocalizations.of(context).mentions, ), @@ -68,9 +64,7 @@ class _ChannelListPageState extends State { children: [ Icon( Icons.message_outlined, - color: _isSelected(2) - ? StreamChatTheme.of(context).colorTheme.textHighEmphasis - : Colors.grey, + color: _isSelected(2) ? StreamChatTheme.of(context).colorTheme.textHighEmphasis : Colors.grey, ), PositionedDirectional( top: -4, @@ -84,18 +78,14 @@ class _ChannelListPageState extends State { BottomNavigationBarItem( icon: Icon( Icons.edit_note_rounded, - color: _isSelected(3) - ? StreamChatTheme.of(context).colorTheme.textHighEmphasis - : Colors.grey, + color: _isSelected(3) ? StreamChatTheme.of(context).colorTheme.textHighEmphasis : Colors.grey, ), label: 'Drafts', ), BottomNavigationBarItem( icon: Icon( Icons.bookmark_border_rounded, - color: _isSelected(4) - ? StreamChatTheme.of(context).colorTheme.textHighEmphasis - : Colors.grey, + color: _isSelected(4) ? StreamChatTheme.of(context).colorTheme.textHighEmphasis : Colors.grey, ), label: 'Reminders', ), @@ -118,8 +108,7 @@ class _ChannelListPageState extends State { onNewChatButtonTap: () { GoRouter.of(context).pushNamed(Routes.NEW_CHAT.name); }, - preNavigationCallback: () => - FocusScope.of(context).requestFocus(FocusNode()), + preNavigationCallback: () => FocusScope.of(context).requestFocus(FocusNode()), ), drawer: LeftDrawer( user: user, @@ -130,11 +119,9 @@ class _ChannelListPageState extends State { currentIndex: _currentIndex, items: _navBarItems, selectedLabelStyle: StreamChatTheme.of(context).textTheme.footnoteBold, - unselectedLabelStyle: - StreamChatTheme.of(context).textTheme.footnoteBold, + unselectedLabelStyle: StreamChatTheme.of(context).textTheme.footnoteBold, type: BottomNavigationBarType.fixed, - selectedItemColor: - StreamChatTheme.of(context).colorTheme.textHighEmphasis, + selectedItemColor: StreamChatTheme.of(context).colorTheme.textHighEmphasis, unselectedItemColor: Colors.grey, onTap: (index) { setState(() => _currentIndex = index); @@ -159,11 +146,7 @@ class _ChannelListPageState extends State { void initState() { super.initState(); if (!kIsWeb) { - badgeListener = StreamChat.of(context) - .client - .state - .totalUnreadCountStream - .listen((count) { + badgeListener = StreamChat.of(context).client.state.totalUnreadCountStream.listen((count) { if (count > 0) { FlutterAppBadger.updateBadgeCount(count); } else { @@ -213,8 +196,7 @@ class LeftDrawer extends StatelessWidget { StreamUserAvatar( user: user, showOnlineStatus: false, - constraints: - BoxConstraints.tight(const Size.fromRadius(20)), + constraints: BoxConstraints.tight(const Size.fromRadius(20)), ), Padding( padding: const EdgeInsets.only(left: 16), @@ -232,10 +214,7 @@ class LeftDrawer extends StatelessWidget { ListTile( leading: StreamSvgIcon( icon: StreamSvgIcons.penWrite, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.5), ), onTap: () { Navigator.of(context).pop(); @@ -250,10 +229,7 @@ class LeftDrawer extends StatelessWidget { ), ListTile( leading: StreamSvgIcon( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.5), icon: StreamSvgIcons.contacts, ), onTap: () { @@ -286,10 +262,7 @@ class LeftDrawer extends StatelessWidget { }, leading: StreamSvgIcon( icon: StreamSvgIcons.user, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.5), ), title: Text( AppLocalizations.of(context).signOut, @@ -300,9 +273,7 @@ class LeftDrawer extends StatelessWidget { trailing: IconButton( iconSize: 24, icon: const StreamSvgIcon(icon: StreamSvgIcons.moon), - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, onPressed: () async { final theme = Theme.of(context); final sp = await StreamingSharedPreferences.instance; diff --git a/sample_app/lib/pages/channel_media_display_screen.dart b/sample_app/lib/pages/channel_media_display_screen.dart index 2504e3ae00..cfbe915fbe 100644 --- a/sample_app/lib/pages/channel_media_display_screen.dart +++ b/sample_app/lib/pages/channel_media_display_screen.dart @@ -15,8 +15,7 @@ class ChannelMediaDisplayScreen extends StatefulWidget { final StreamMessageThemeData messageTheme; @override - State createState() => - _ChannelMediaDisplayScreenState(); + State createState() => _ChannelMediaDisplayScreenState(); } class _ChannelMediaDisplayScreenState extends State { @@ -57,8 +56,7 @@ class _ChannelMediaDisplayScreenState extends State { ), body: ValueListenableBuilder( valueListenable: controller, - builder: (BuildContext context, - PagedValue value, Widget? child) { + builder: (BuildContext context, PagedValue value, Widget? child) { return value.when( (items, nextPageKey, error) { if (items.isEmpty) { @@ -76,22 +74,16 @@ class _ChannelMediaDisplayScreenState extends State { AppLocalizations.of(context).noMedia, style: TextStyle( fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, ), ), const SizedBox(height: 8), Text( - AppLocalizations.of(context) - .photosOrVideosWillAppearHere, + AppLocalizations.of(context).photosOrVideosWillAppearHere, textAlign: TextAlign.center, style: TextStyle( fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), ], @@ -102,25 +94,23 @@ class _ChannelMediaDisplayScreenState extends State { for (final item in value.asSuccess.items) { item.message.attachments - .where((e) => - (e.type == 'image' || e.type == 'video') && - e.ogScrapeUrl == null) + .where((e) => (e.type == 'image' || e.type == 'video') && e.ogScrapeUrl == null) .forEach((e) { - VideoPlayerController? controller; - if (e.type == 'video') { - final cachedController = controllerCache[e.assetUrl]; + VideoPlayerController? controller; + if (e.type == 'video') { + final cachedController = controllerCache[e.assetUrl]; - if (cachedController == null) { - final url = Uri.parse(e.assetUrl!); - controller = VideoPlayerController.networkUrl(url); - controller.initialize(); - controllerCache[e.assetUrl] = controller; - } else { - controller = cachedController; - } - } - media.add(_AssetPackage(e, item.message, controller)); - }); + if (cachedController == null) { + final url = Uri.parse(e.assetUrl!); + controller = VideoPlayerController.networkUrl(url); + controller.initialize(); + controllerCache[e.assetUrl] = controller; + } else { + controller = cachedController; + } + } + media.add(_AssetPackage(e, item.message, controller)); + }); } return LazyLoadScrollView( @@ -130,8 +120,7 @@ class _ChannelMediaDisplayScreenState extends State { } }, child: GridView.builder( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), itemBuilder: (context, position) { final channel = StreamChannel.of(context).channel; return Padding( @@ -161,10 +150,8 @@ class _ChannelMediaDisplayScreenState extends State { } router.pushNamed( Routes.CHANNEL_PAGE.name, - pathParameters: - Routes.CHANNEL_PAGE.params(channel), - queryParameters: - Routes.CHANNEL_PAGE.queryParams(m), + pathParameters: Routes.CHANNEL_PAGE.params(channel), + queryParameters: Routes.CHANNEL_PAGE.queryParams(m), ); }, ), diff --git a/sample_app/lib/pages/channel_page.dart b/sample_app/lib/pages/channel_page.dart index 18d831528b..ba52c7cb8f 100644 --- a/sample_app/lib/pages/channel_page.dart +++ b/sample_app/lib/pages/channel_page.dart @@ -133,8 +133,7 @@ class _ChannelPageState extends State { enableVoiceRecording: true, allowedAttachmentPickerTypes: [ ...AttachmentPickerType.values, - if (config?.sharedLocations == true && channel.canShareLocation) - const LocationPickerType(), + if (config?.sharedLocations == true && channel.canShareLocation) const LocationPickerType(), ], onAttachmentPickerResult: (result) { return _onCustomAttachmentPickerResult(channel, result); diff --git a/sample_app/lib/pages/chat_info_screen.dart b/sample_app/lib/pages/chat_info_screen.dart index 81ac6dbfe5..e9dfbd3dc8 100644 --- a/sample_app/lib/pages/chat_info_screen.dart +++ b/sample_app/lib/pages/chat_info_screen.dart @@ -79,8 +79,7 @@ class _ChatInfoScreenState extends State { ), Text( widget.user!.name, - style: const TextStyle( - fontSize: 16, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 7), _buildConnectedTitleState(), @@ -93,11 +92,9 @@ class _ChatInfoScreenState extends State { child: Text( widget.user!.name, style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), - fontSize: 16), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + fontSize: 16, + ), ), ), onTap: () {}, @@ -122,46 +119,45 @@ class _ChatInfoScreenState extends State { return Column( children: [ StreamBuilder( - stream: StreamChannel.of(context).channel.isMutedStream, - builder: (context, snapshot) { - mutedBool.value = snapshot.data; - - return StreamOptionListTile( - tileColor: StreamChatTheme.of(context).colorTheme.appBg, - title: AppLocalizations.of(context).muteUser, - titleTextStyle: StreamChatTheme.of(context).textTheme.body, - leading: Padding( - padding: const EdgeInsets.symmetric(horizontal: 22), - child: StreamSvgIcon( - icon: StreamSvgIcons.mute, - size: 24, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), - ), + stream: StreamChannel.of(context).channel.isMutedStream, + builder: (context, snapshot) { + mutedBool.value = snapshot.data; + + return StreamOptionListTile( + tileColor: StreamChatTheme.of(context).colorTheme.appBg, + title: AppLocalizations.of(context).muteUser, + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 22), + child: StreamSvgIcon( + icon: StreamSvgIcons.mute, + size: 24, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), - trailing: snapshot.data == null - ? const CircularProgressIndicator() - : ValueListenableBuilder( - valueListenable: mutedBool, - builder: (context, value, _) { - return CupertinoSwitch( - value: value!, - onChanged: (val) { - mutedBool.value = val; - - if (snapshot.data!) { - channel.channel.unmute(); - } else { - channel.channel.mute(); - } - }, - ); - }), - onTap: () {}, - ); - }), + ), + trailing: snapshot.data == null + ? const CircularProgressIndicator() + : ValueListenableBuilder( + valueListenable: mutedBool, + builder: (context, value, _) { + return CupertinoSwitch( + value: value!, + onChanged: (val) { + mutedBool.value = val; + + if (snapshot.data!) { + channel.channel.unmute(); + } else { + channel.channel.mute(); + } + }, + ); + }, + ), + onTap: () {}, + ); + }, + ), StreamOptionListTile( title: AppLocalizations.of(context).pinnedMessages, tileColor: StreamChatTheme.of(context).colorTheme.appBg, @@ -171,10 +167,7 @@ class _ChatInfoScreenState extends State { child: StreamSvgIcon( icon: StreamSvgIcons.pin, size: 24, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), trailing: StreamSvgIcon( @@ -204,10 +197,7 @@ class _ChatInfoScreenState extends State { child: StreamSvgIcon( icon: StreamSvgIcons.pictures, size: 36, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), trailing: StreamSvgIcon( @@ -239,10 +229,7 @@ class _ChatInfoScreenState extends State { child: StreamSvgIcon( icon: StreamSvgIcons.files, size: 32, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), trailing: StreamSvgIcon( @@ -274,10 +261,7 @@ class _ChatInfoScreenState extends State { child: StreamSvgIcon( icon: StreamSvgIcons.group, size: 24, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), trailing: StreamSvgIcon( @@ -286,10 +270,11 @@ class _ChatInfoScreenState extends State { ), onTap: () { Navigator.push( - context, - MaterialPageRoute( - builder: (context) => _SharedGroupsScreen( - StreamChat.of(context).currentUser, widget.user))); + context, + MaterialPageRoute( + builder: (context) => _SharedGroupsScreen(StreamChat.of(context).currentUser, widget.user), + ), + ); }, ), ], @@ -301,8 +286,8 @@ class _ChatInfoScreenState extends State { title: 'Delete Conversation', tileColor: StreamChatTheme.of(context).colorTheme.appBg, titleTextStyle: StreamChatTheme.of(context).textTheme.body.copyWith( - color: StreamChatTheme.of(context).colorTheme.accentError, - ), + color: StreamChatTheme.of(context).colorTheme.accentError, + ), leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 22), child: StreamSvgIcon( @@ -347,20 +332,12 @@ class _ChatInfoScreenState extends State { if (otherMember.online) { alternativeWidget = Text( AppLocalizations.of(context).online, - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5)), + style: TextStyle(color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5)), ); } else { alternativeWidget = Text( '${AppLocalizations.of(context).lastSeen} ${Jiffy.parseFromDateTime(otherMember.lastActive!).fromNow()}', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5)), + style: TextStyle(color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5)), ); } } @@ -415,9 +392,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { centerTitle: true, title: Text( AppLocalizations.of(context).sharedGroups, - style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, - fontSize: 16), + style: TextStyle(color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, fontSize: 16), ), leading: const StreamBackButton(), backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, @@ -451,9 +426,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { AppLocalizations.of(context).noSharedGroups, style: TextStyle( fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, ), ), const SizedBox(height: 8), @@ -462,10 +435,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { textAlign: TextAlign.center, style: TextStyle( fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), ], @@ -474,11 +444,11 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { } final channels = snapshot.data! - .where((c) => - c.state!.members.any((m) => - m.userId != widget.mainUser!.id && - m.userId != widget.otherUser!.id) || - !c.isDistinct) + .where( + (c) => + c.state!.members.any((m) => m.userId != widget.mainUser!.id && m.userId != widget.otherUser!.id) || + !c.isDistinct, + ) .toList(); return ListView.builder( @@ -507,8 +477,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { builder: (context, constraints) { String? title; if (extraData['name'] == null) { - final otherMembers = members.where((member) => - member.userId != StreamChat.of(context).currentUser!.id); + final otherMembers = members.where((member) => member.userId != StreamChat.of(context).currentUser!.id); if (otherMembers.isNotEmpty) { final maxWidth = constraints.maxWidth; final maxChars = maxWidth / textStyle.fontSize!; @@ -522,8 +491,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { } } - final exceedingMembers = - otherMembers.length - currentMembers.length; + final exceedingMembers = otherMembers.length - currentMembers.length; title = '${currentMembers.map((e) => e.user!.name).join(', ')} ${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; } else { @@ -542,35 +510,30 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { padding: const EdgeInsets.all(8), child: StreamChannelAvatar( channel: channel, - constraints: - const BoxConstraints(maxWidth: 40, maxHeight: 40), + constraints: const BoxConstraints(maxWidth: 40, maxHeight: 40), ), ), Expanded( - child: Text( - title, - style: textStyle, - )), + child: Text( + title, + style: textStyle, + ), + ), Padding( padding: const EdgeInsets.all(8), child: Text( '${channel.memberCount} ${AppLocalizations.of(context).members.toLowerCase()}', style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5)), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + ), ), - ) + ), ], ), ), Container( height: 1, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.08), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.08), ), ], ); diff --git a/sample_app/lib/pages/choose_user_page.dart b/sample_app/lib/pages/choose_user_page.dart index b2aa66391a..37300f6b91 100644 --- a/sample_app/lib/pages/choose_user_page.dart +++ b/sample_app/lib/pages/choose_user_page.dart @@ -76,16 +76,12 @@ class ChooseUserPage extends StatelessWidget { showDialog( barrierDismissible: false, context: context, - barrierColor: StreamChatTheme.of(context) - .colorTheme - .overlay, + barrierColor: StreamChatTheme.of(context).colorTheme.overlay, builder: (context) => Center( child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), - color: StreamChatTheme.of(context) - .colorTheme - .barsBg, + color: StreamChatTheme.of(context).colorTheme.barsBg, ), height: 100, width: 100, @@ -96,8 +92,7 @@ class ChooseUserPage extends StatelessWidget { ), ); - final client = - context.read().initData!.client; + final client = context.read().initData!.client; final router = GoRouter.of(context); @@ -134,39 +129,27 @@ class ChooseUserPage extends StatelessWidget { ), title: Text( user.name, - style: - StreamChatTheme.of(context).textTheme.bodyBold, + style: StreamChatTheme.of(context).textTheme.bodyBold, ), subtitle: Text( AppLocalizations.of(context).streamTestAccount, - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, - ), + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, + ), ), trailing: StreamSvgIcon( icon: StreamSvgIcons.arrowRight, - color: StreamChatTheme.of(context) - .colorTheme - .accentPrimary, + color: StreamChatTheme.of(context).colorTheme.accentPrimary, ), ); }), ListTile( - onTap: () => GoRouter.of(context) - .pushNamed(Routes.ADVANCED_OPTIONS.name), + onTap: () => GoRouter.of(context).pushNamed(Routes.ADVANCED_OPTIONS.name), leading: CircleAvatar( - backgroundColor: - StreamChatTheme.of(context).colorTheme.borders, + backgroundColor: StreamChatTheme.of(context).colorTheme.borders, child: StreamSvgIcon( icon: StreamSvgIcons.settings, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, ), ), title: Text( @@ -175,14 +158,9 @@ class ChooseUserPage extends StatelessWidget { ), subtitle: Text( AppLocalizations.of(context).customSettings, - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, - ), + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, + ), ), trailing: SvgPicture.asset( 'assets/icon_arrow_right.svg', diff --git a/sample_app/lib/pages/draft_list_page.dart b/sample_app/lib/pages/draft_list_page.dart index ffdda5fec7..d908593295 100644 --- a/sample_app/lib/pages/draft_list_page.dart +++ b/sample_app/lib/pages/draft_list_page.dart @@ -71,8 +71,8 @@ class _DraftListPageState extends State { initialMessageId: draft.parentId, child: switch (draft.parentMessage) { final parent? => ThreadPage( - parent: parent.copyWith(draft: draft), - ), + parent: parent.copyWith(draft: draft), + ), _ => const ChannelPage(), }, ); diff --git a/sample_app/lib/pages/group_chat_details_screen.dart b/sample_app/lib/pages/group_chat_details_screen.dart index 5df7ae5075..666a49bd54 100644 --- a/sample_app/lib/pages/group_chat_details_screen.dart +++ b/sample_app/lib/pages/group_chat_details_screen.dart @@ -19,8 +19,7 @@ class GroupChatDetailsScreen extends StatefulWidget { } class _GroupChatDetailsScreenState extends State { - late final TextEditingController _groupNameController = - TextEditingController()..addListener(_groupNameListener); + late final TextEditingController _groupNameController = TextEditingController()..addListener(_groupNameListener); bool _isGroupNameEmpty = true; @@ -74,9 +73,7 @@ class _GroupChatDetailsScreenState extends State { AppLocalizations.of(context).name.toUpperCase(), style: TextStyle( fontSize: 12, - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), ), const SizedBox(width: 16), @@ -91,13 +88,10 @@ class _GroupChatDetailsScreenState extends State { errorBorder: InputBorder.none, disabledBorder: InputBorder.none, contentPadding: EdgeInsets.zero, - hintText: - AppLocalizations.of(context).chooseAGroupChatName, + hintText: AppLocalizations.of(context).chooseAGroupChatName, hintStyle: TextStyle( fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), ), ), @@ -122,16 +116,17 @@ class _GroupChatDetailsScreenState extends State { final groupName = _groupNameController.text; final client = StreamChat.of(context).client; final router = GoRouter.of(context); - final channel = client.channel('messaging', - id: const Uuid().v4(), - extraData: { - 'members': [ - client.state.currentUser!.id, - ...widget.groupChatState.users - .map((e) => e.id), - ], - 'name': groupName, - }); + final channel = client.channel( + 'messaging', + id: const Uuid().v4(), + extraData: { + 'members': [ + client.state.currentUser!.id, + ...widget.groupChatState.users.map((e) => e.id), + ], + 'name': groupName, + }, + ); await channel.watch(); router.goNamed( Routes.CHANNEL_PAGE.name, @@ -172,8 +167,7 @@ class _GroupChatDetailsScreenState extends State { Container( width: double.maxFinite, decoration: BoxDecoration( - gradient: - StreamChatTheme.of(context).colorTheme.bgGradient, + gradient: StreamChatTheme.of(context).colorTheme.bgGradient, ), child: Padding( padding: const EdgeInsets.symmetric( @@ -183,80 +177,70 @@ class _GroupChatDetailsScreenState extends State { child: Text( '$_totalUsers ${_totalUsers > 1 ? AppLocalizations.of(context).members : AppLocalizations.of(context).member}', style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), ), ), ), AnimatedBuilder( - animation: widget.groupChatState, - builder: (context, child) { - return Expanded( - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onPanDown: (_) => FocusScope.of(context).unfocus(), - child: ListView.separated( - itemCount: widget.groupChatState.users.length + 1, - separatorBuilder: (_, __) => Container( - height: 1, - color: StreamChatTheme.of(context) - .colorTheme - .borders, - ), - itemBuilder: (_, index) { - if (index == - widget.groupChatState.users.length) { - return Container( - height: 1, - color: StreamChatTheme.of(context) - .colorTheme - .borders, - ); - } - final user = widget.groupChatState.users - .elementAt(index); - return ListTile( - key: ObjectKey(user), - leading: StreamUserAvatar( - user: user, - constraints: const BoxConstraints.tightFor( - width: 40, - height: 40, - ), - ), - title: Text( - user.name, - style: const TextStyle( - fontWeight: FontWeight.bold), - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, + animation: widget.groupChatState, + builder: (context, child) { + return Expanded( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onPanDown: (_) => FocusScope.of(context).unfocus(), + child: ListView.separated( + itemCount: widget.groupChatState.users.length + 1, + separatorBuilder: (_, __) => Container( + height: 1, + color: StreamChatTheme.of(context).colorTheme.borders, + ), + itemBuilder: (_, index) { + if (index == widget.groupChatState.users.length) { + return Container( + height: 1, + color: StreamChatTheme.of(context).colorTheme.borders, + ); + } + final user = widget.groupChatState.users.elementAt(index); + return ListTile( + key: ObjectKey(user), + leading: StreamUserAvatar( + user: user, + constraints: const BoxConstraints.tightFor( + width: 40, + height: 40, ), - trailing: IconButton( - icon: Icon( - Icons.clear_rounded, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, - ), - padding: EdgeInsets.zero, - splashRadius: 24, - onPressed: () { - widget.groupChatState.removeUser(user); - if (widget.groupChatState.users.isEmpty) { - GoRouter.of(context).pop(); - } - }, + ), + title: Text( + user.name, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + trailing: IconButton( + icon: Icon( + Icons.clear_rounded, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, ), - ); - }, - ), + padding: EdgeInsets.zero, + splashRadius: 24, + onPressed: () { + widget.groupChatState.removeUser(user); + if (widget.groupChatState.users.isEmpty) { + GoRouter.of(context).pop(); + } + }, + ), + ); + }, ), - ); - }), + ), + ); + }, + ), ], ), ); @@ -271,10 +255,11 @@ class _GroupChatDetailsScreenState extends State { backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, context: context, shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - )), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), builder: (context) { return Column( mainAxisSize: MainAxisSize.min, @@ -302,10 +287,7 @@ class _GroupChatDetailsScreenState extends State { height: 36, ), Container( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.08), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.08), height: 1, ), Row( @@ -315,13 +297,9 @@ class _GroupChatDetailsScreenState extends State { onPressed: GoRouter.of(context).pop, child: Text( AppLocalizations.of(context).ok, - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentPrimary), + style: StreamChatTheme.of( + context, + ).textTheme.bodyBold.copyWith(color: StreamChatTheme.of(context).colorTheme.accentPrimary), ), ), ], diff --git a/sample_app/lib/pages/group_info_screen.dart b/sample_app/lib/pages/group_info_screen.dart index 1a5e6997ad..f64e1481d9 100644 --- a/sample_app/lib/pages/group_info_screen.dart +++ b/sample_app/lib/pages/group_info_screen.dart @@ -25,13 +25,11 @@ class GroupInfoScreen extends StatefulWidget { } class _GroupInfoScreenState extends State { - late final TextEditingController _nameController = - TextEditingController.fromValue( + late final TextEditingController _nameController = TextEditingController.fromValue( TextEditingValue(text: (channel.extraData['name'] as String?) ?? ''), ); - late final TextEditingController _searchController = TextEditingController() - ..addListener(_userNameListener); + late final TextEditingController _searchController = TextEditingController()..addListener(_userNameListener); String _userNameQuery = ''; @@ -62,13 +60,10 @@ class _GroupInfoScreenState extends State { _userNameQuery = _searchController.text; _userListController.filter = Filter.and( [ - if (_searchController.text.isNotEmpty) - Filter.autoComplete('name', _userNameQuery), + if (_searchController.text.isNotEmpty) Filter.autoComplete('name', _userNameQuery), Filter.notIn('id', [ StreamChat.of(context).currentUser!.id, - ...channel.state!.members - .map((e) => e.userId) - .whereType(), + ...channel.state!.members.map((e) => e.userId).whereType(), ]), ], ); @@ -94,13 +89,10 @@ class _GroupInfoScreenState extends State { limit: 25, filter: Filter.and( [ - if (_searchController.text.isNotEmpty) - Filter.autoComplete('name', _userNameQuery), + if (_searchController.text.isNotEmpty) Filter.autoComplete('name', _userNameQuery), Filter.notIn('id', [ StreamChat.of(context).currentUser!.id, - ...channel.state!.members - .map((e) => e.userId) - .whereType(), + ...channel.state!.members.map((e) => e.userId).whereType(), ]), ], ), @@ -122,106 +114,100 @@ class _GroupInfoScreenState extends State { @override Widget build(BuildContext context) { return StreamBuilder>( - stream: channel.state!.membersStream, - builder: (context, snapshot) { - if (!snapshot.hasData) { - return ColoredBox( - color: StreamChatTheme.of(context).colorTheme.disabled, - child: const Center(child: CircularProgressIndicator()), - ); - } + stream: channel.state!.membersStream, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return ColoredBox( + color: StreamChatTheme.of(context).colorTheme.disabled, + child: const Center(child: CircularProgressIndicator()), + ); + } - return Scaffold( - backgroundColor: StreamChatTheme.of(context).colorTheme.appBg, - appBar: AppBar( - elevation: 1, - toolbarHeight: 56, - backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, - leading: const StreamBackButton(), - title: Column( - children: [ - StreamBuilder( - stream: channel.state?.channelStateStream, - builder: (context, state) { - if (!state.hasData) { - return Text( - AppLocalizations.of(context).loading, - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, - fontSize: 16, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ); - } + return Scaffold( + backgroundColor: StreamChatTheme.of(context).colorTheme.appBg, + appBar: AppBar( + elevation: 1, + toolbarHeight: 56, + backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, + leading: const StreamBackButton(), + title: Column( + children: [ + StreamBuilder( + stream: channel.state?.channelStateStream, + builder: (context, state) { + if (!state.hasData) { + return Text( + AppLocalizations.of(context).loading, + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, + fontSize: 16, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + } - return Text( - _getChannelName( - 2 * MediaQuery.of(context).size.width / 3, - members: snapshot.data, - extraData: state.data!.channel!.extraData, - maxFontSize: 16, - )!, - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, - fontSize: 16, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ); - }), - const SizedBox( - height: 3, - ), - Text( - '${channel.memberCount} ${AppLocalizations.of(context).members}, ${snapshot.data?.where((e) => e.user!.online).length ?? 0} ${AppLocalizations.of(context).online}', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), - fontSize: 12, - ), - ), - ], - ), - centerTitle: true, - actions: [ - if (channel.canUpdateChannelMembers) - StreamNeumorphicButton( - child: InkWell( - onTap: () { - _buildAddUserModal(context); - }, - child: Padding( - padding: const EdgeInsets.all(8), - child: StreamSvgIcon( - icon: StreamSvgIcons.userAdd, - color: StreamChatTheme.of(context) - .colorTheme - .accentPrimary), + return Text( + _getChannelName( + 2 * MediaQuery.of(context).size.width / 3, + members: snapshot.data, + extraData: state.data!.channel!.extraData, + maxFontSize: 16, + )!, + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, + fontSize: 16, ), - ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + }, + ), + const SizedBox( + height: 3, + ), + Text( + '${channel.memberCount} ${AppLocalizations.of(context).members}, ${snapshot.data?.where((e) => e.user!.online).length ?? 0} ${AppLocalizations.of(context).online}', + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + fontSize: 12, ), - ], - ), - body: ListView( - children: [ - _buildMembers(snapshot.data!), - Container( - height: 8, - color: StreamChatTheme.of(context).colorTheme.disabled, ), - if (channel.canUpdateChannel) _buildNameTile(), - _buildOptionListTiles(), ], ), - ); - }); + centerTitle: true, + actions: [ + if (channel.canUpdateChannelMembers) + StreamNeumorphicButton( + child: InkWell( + onTap: () { + _buildAddUserModal(context); + }, + child: Padding( + padding: const EdgeInsets.all(8), + child: StreamSvgIcon( + icon: StreamSvgIcons.userAdd, + color: StreamChatTheme.of(context).colorTheme.accentPrimary, + ), + ), + ), + ), + ], + ), + body: ListView( + children: [ + _buildMembers(snapshot.data!), + Container( + height: 8, + color: StreamChatTheme.of(context).colorTheme.disabled, + ), + if (channel.canUpdateChannel) _buildNameTile(), + _buildOptionListTiles(), + ], + ), + ); + }, + ); } Widget _buildMembers(List members) { @@ -254,8 +240,7 @@ class _GroupInfoScreenState extends State { final userMember = groupMembers.firstWhereOrNull( (e) => e.user!.id == StreamChat.of(context).currentUser!.id, ); - _showUserInfoModal( - member.user, userMember?.userId == channel.createdBy?.id); + _showUserInfoModal(member.user, userMember?.userId == channel.createdBy?.id); }, child: SizedBox( height: 65, @@ -283,8 +268,7 @@ class _GroupInfoScreenState extends State { children: [ Text( member.user!.name, - style: const TextStyle( - fontWeight: FontWeight.bold), + style: const TextStyle(fontWeight: FontWeight.bold), ), const SizedBox( height: 1, @@ -292,10 +276,8 @@ class _GroupInfoScreenState extends State { Text( _getLastSeen(member.user!), style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5)), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + ), ), ], ), @@ -303,14 +285,10 @@ class _GroupInfoScreenState extends State { Padding( padding: const EdgeInsets.all(8), child: Text( - member.userId == channel.createdBy?.id - ? AppLocalizations.of(context).owner - : '', + member.userId == channel.createdBy?.id ? AppLocalizations.of(context).owner : '', style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5)), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + ), ), ), ], @@ -343,13 +321,10 @@ class _GroupInfoScreenState extends State { child: Row( children: [ Padding( - padding: const EdgeInsets.symmetric( - horizontal: 21, vertical: 12), + padding: const EdgeInsets.symmetric(horizontal: 21, vertical: 12), child: StreamSvgIcon( icon: StreamSvgIcons.down, - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), ), Expanded( @@ -359,10 +334,7 @@ class _GroupInfoScreenState extends State { children: [ Text( '${members.length - groupMembersLength} ${AppLocalizations.of(context).more}', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis), + style: TextStyle(color: StreamChatTheme.of(context).colorTheme.textLowEmphasis), ), ], ), @@ -398,10 +370,8 @@ class _GroupInfoScreenState extends State { child: Text( AppLocalizations.of(context).name.toUpperCase(), style: StreamChatTheme.of(context).textTheme.footnote.copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5)), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + ), ), ), const SizedBox( @@ -412,18 +382,13 @@ class _GroupInfoScreenState extends State { enabled: channel.canUpdateChannel, focusNode: _focusNode, controller: _nameController, - cursorColor: - StreamChatTheme.of(context).colorTheme.textHighEmphasis, + cursorColor: StreamChatTheme.of(context).colorTheme.textHighEmphasis, decoration: InputDecoration.collapsed( - hintText: AppLocalizations.of(context).addAGroupName, - hintStyle: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5))), + hintText: AppLocalizations.of(context).addAGroupName, + hintStyle: StreamChatTheme.of(context).textTheme.bodyBold.copyWith( + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + ), + ), style: const TextStyle( fontWeight: FontWeight.bold, height: 0.82, @@ -572,10 +537,7 @@ class _GroupInfoScreenState extends State { child: StreamSvgIcon( icon: StreamSvgIcons.userRemove, size: 24, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), trailing: const SizedBox( @@ -590,8 +552,7 @@ class _GroupInfoScreenState extends State { context, title: AppLocalizations.of(context).leaveConversation, okText: AppLocalizations.of(context).leave.toUpperCase(), - question: - AppLocalizations.of(context).leaveConversationAreYouSure, + question: AppLocalizations.of(context).leaveConversationAreYouSure, cancelText: AppLocalizations.of(context).cancel.toUpperCase(), icon: StreamSvgIcon( icon: StreamSvgIcons.userRemove, @@ -658,13 +619,10 @@ class _GroupInfoScreenState extends State { child: StreamSvgIcon( icon: StreamSvgIcons.search, size: 96, - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), ), - Text(AppLocalizations.of(context) - .noUserMatchesTheseKeywords), + Text(AppLocalizations.of(context).noUserMatchesTheseKeywords), ], ), ), @@ -716,10 +674,11 @@ class _GroupInfoScreenState extends State { ), ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(24), - borderSide: BorderSide( - color: theme.colorTheme.borders, - )), + borderRadius: BorderRadius.circular(24), + borderSide: BorderSide( + color: theme.colorTheme.borders, + ), + ), contentPadding: EdgeInsets.zero, ), ), @@ -729,7 +688,7 @@ class _GroupInfoScreenState extends State { icon: const StreamSvgIcon(icon: StreamSvgIcons.closeSmall), color: theme.colorTheme.textHighEmphasis, onPressed: () => Navigator.pop(context), - ) + ), ], ); } @@ -784,9 +743,7 @@ class _GroupInfoScreenState extends State { _buildModalListTile( context, StreamSvgIcon( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, size: 24, icon: StreamSvgIcons.user, ), @@ -795,12 +752,15 @@ class _GroupInfoScreenState extends State { final client = StreamChat.of(context).client; final router = GoRouter.of(context); - final c = client.channel('messaging', extraData: { - 'members': [ - user.id, - StreamChat.of(context).currentUser!.id, - ], - }); + final c = client.channel( + 'messaging', + extraData: { + 'members': [ + user.id, + StreamChat.of(context).currentUser!.id, + ], + }, + ); await c.watch(); @@ -815,9 +775,7 @@ class _GroupInfoScreenState extends State { _buildModalListTile( context, StreamSvgIcon( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, size: 24, icon: StreamSvgIcons.message, ), @@ -826,12 +784,15 @@ class _GroupInfoScreenState extends State { final client = StreamChat.of(context).client; final router = GoRouter.of(context); - final c = client.channel('messaging', extraData: { - 'members': [ - user.id, - StreamChat.of(context).currentUser!.id, - ], - }); + final c = client.channel( + 'messaging', + extraData: { + 'members': [ + user.id, + StreamChat.of(context).currentUser!.id, + ], + }, + ); await c.watch(); @@ -841,44 +802,36 @@ class _GroupInfoScreenState extends State { ); }, ), - if (!channel.isDistinct && - StreamChat.of(context).currentUser!.id != user.id && - isUserAdmin) + if (!channel.isDistinct && StreamChat.of(context).currentUser!.id != user.id && isUserAdmin) _buildModalListTile( - context, - StreamSvgIcon( - color: StreamChatTheme.of(context) - .colorTheme - .accentError, - size: 24, - icon: StreamSvgIcons.userRemove, - ), - AppLocalizations.of(context).removeFromGroup, () async { - final router = GoRouter.of(context); - final res = await showConfirmationBottomSheet( - context, - title: AppLocalizations.of(context).removeMember, - okText: - AppLocalizations.of(context).remove.toUpperCase(), - question: - AppLocalizations.of(context).removeMemberAreYouSure, - cancelText: - AppLocalizations.of(context).cancel.toUpperCase(), - ); + context, + StreamSvgIcon( + color: StreamChatTheme.of(context).colorTheme.accentError, + size: 24, + icon: StreamSvgIcons.userRemove, + ), + AppLocalizations.of(context).removeFromGroup, + () async { + final router = GoRouter.of(context); + final res = await showConfirmationBottomSheet( + context, + title: AppLocalizations.of(context).removeMember, + okText: AppLocalizations.of(context).remove.toUpperCase(), + question: AppLocalizations.of(context).removeMemberAreYouSure, + cancelText: AppLocalizations.of(context).cancel.toUpperCase(), + ); - if (res == true) { - await channel.removeMembers([user.id]); - } - router.pop(); - }, - color: - StreamChatTheme.of(context).colorTheme.accentError), + if (res == true) { + await channel.removeMembers([user.id]); + } + router.pop(); + }, + color: StreamChatTheme.of(context).colorTheme.accentError, + ), _buildModalListTile( context, StreamSvgIcon( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, size: 24, icon: StreamSvgIcons.closeSmall, ), @@ -911,20 +864,12 @@ class _GroupInfoScreenState extends State { if (otherMember.online) { alternativeWidget = Text( AppLocalizations.of(context).online, - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5)), + style: TextStyle(color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5)), ); } else { alternativeWidget = Text( '${AppLocalizations.of(context).lastSeen} ${Jiffy.parseFromDateTime(otherMember.lastActive!).fromNow()}', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5)), + style: TextStyle(color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5)), ); } } @@ -932,9 +877,7 @@ class _GroupInfoScreenState extends State { return alternativeWidget; } - Widget _buildModalListTile( - BuildContext context, Widget leading, String title, VoidCallback onTap, - {Color? color}) { + Widget _buildModalListTile(BuildContext context, Widget leading, String title, VoidCallback onTap, {Color? color}) { color ??= StreamChatTheme.of(context).colorTheme.textHighEmphasis; return Material( @@ -958,10 +901,9 @@ class _GroupInfoScreenState extends State { Expanded( child: Text( title, - style: - TextStyle(color: color, fontWeight: FontWeight.bold), + style: TextStyle(color: color, fontWeight: FontWeight.bold), ), - ) + ), ], ), ), @@ -980,8 +922,7 @@ class _GroupInfoScreenState extends State { String? title; final client = StreamChat.of(context); if (extraData['name'] == null) { - final otherMembers = - members!.where((member) => member.user!.id != client.currentUser!.id); + final otherMembers = members!.where((member) => member.user!.id != client.currentUser!.id); if (otherMembers.isNotEmpty) { final maxWidth = width; final maxChars = maxWidth / maxFontSize!; @@ -1040,46 +981,45 @@ class _GroupInfoToggle extends StatelessWidget { @override Widget build(BuildContext context) { return StreamBuilder( - stream: channelStream, - builder: (context, snapshot) { - localNotifier.value = snapshot.data; + stream: channelStream, + builder: (context, snapshot) { + localNotifier.value = snapshot.data; - return StreamOptionListTile( - tileColor: StreamChatTheme.of(context).colorTheme.appBg, - separatorColor: StreamChatTheme.of(context).colorTheme.disabled, - title: title, - titleTextStyle: StreamChatTheme.of(context).textTheme.body, - leading: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: StreamSvgIcon( - icon: icon, - size: 24, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), - ), + return StreamOptionListTile( + tileColor: StreamChatTheme.of(context).colorTheme.appBg, + separatorColor: StreamChatTheme.of(context).colorTheme.disabled, + title: title, + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: StreamSvgIcon( + icon: icon, + size: 24, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), - trailing: snapshot.data == null - ? const CircularProgressIndicator() - : ValueListenableBuilder( - valueListenable: localNotifier, - builder: (context, value, _) { - return CupertinoSwitch( - value: value!, - onChanged: (val) { - localNotifier.value = val; - if (snapshot.data!) { - onTurnOff(); - } else { - onTurnOn(); - } - }, - ); - }), - onTap: () {}, - ); - }); + ), + trailing: snapshot.data == null + ? const CircularProgressIndicator() + : ValueListenableBuilder( + valueListenable: localNotifier, + builder: (context, value, _) { + return CupertinoSwitch( + value: value!, + onChanged: (val) { + localNotifier.value = val; + if (snapshot.data!) { + onTurnOff(); + } else { + onTurnOn(); + } + }, + ); + }, + ), + onTap: () {}, + ); + }, + ); } } @@ -1109,10 +1049,7 @@ class _GroupInfoListTile extends StatelessWidget { child: StreamSvgIcon( icon: icon, size: iconSize, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), trailing: StreamSvgIcon( diff --git a/sample_app/lib/pages/new_chat_screen.dart b/sample_app/lib/pages/new_chat_screen.dart index fa162f28aa..e084465ecf 100644 --- a/sample_app/lib/pages/new_chat_screen.dart +++ b/sample_app/lib/pages/new_chat_screen.dart @@ -17,8 +17,7 @@ class NewChatScreen extends StatefulWidget { } class _NewChatScreenState extends State { - final _chipInputTextFieldStateKey = - GlobalKey>(); + final _chipInputTextFieldStateKey = GlobalKey>(); late TextEditingController _controller; @@ -33,8 +32,7 @@ class _NewChatScreenState extends State { ], ); - ChipInputTextFieldState? get _chipInputTextFieldState => - _chipInputTextFieldStateKey.currentState; + ChipInputTextFieldState? get _chipInputTextFieldState => _chipInputTextFieldStateKey.currentState; String _userNameQuery = ''; @@ -61,8 +59,7 @@ class _NewChatScreenState extends State { }); userListController.filter = Filter.and([ - if (_userNameQuery.isNotEmpty) - Filter.autoComplete('name', _userNameQuery), + if (_userNameQuery.isNotEmpty) Filter.autoComplete('name', _userNameQuery), Filter.notEqual('id', StreamChat.of(context).currentUser!.id), ]); userListController.doInitialLoad(); @@ -91,13 +88,15 @@ class _NewChatScreenState extends State { final res = await chatState.client.queryChannelsOnline( state: false, watch: false, - filter: Filter.raw(value: { - 'members': [ - ..._selectedUsers.map((e) => e.id), - chatState.currentUser!.id, - ], - 'distinct': true, - }), + filter: Filter.raw( + value: { + 'members': [ + ..._selectedUsers.map((e) => e.id), + chatState.currentUser!.id, + ], + 'distinct': true, + }, + ), messageLimit: 0, paginationParams: const PaginationParams( limit: 1, @@ -148,8 +147,9 @@ class _NewChatScreenState extends State { leading: const StreamBackButton(), title: Text( AppLocalizations.of(context).newChat, - style: StreamChatTheme.of(context).textTheme.headlineBold.copyWith( - color: StreamChatTheme.of(context).colorTheme.textHighEmphasis), + style: StreamChatTheme.of( + context, + ).textTheme.headlineBold.copyWith(color: StreamChatTheme.of(context).colorTheme.textHighEmphasis), ), centerTitle: true, ), @@ -197,9 +197,7 @@ class _NewChatScreenState extends State { children: [ Container( decoration: BoxDecoration( - color: StreamChatTheme.of(context) - .colorTheme - .disabled, + color: StreamChatTheme.of(context).colorTheme.disabled, borderRadius: BorderRadius.circular(12), ), padding: const EdgeInsets.only(left: 24), @@ -209,18 +207,14 @@ class _NewChatScreenState extends State { user.name, maxLines: 1, style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, ), ), ), ), Container( foregroundDecoration: BoxDecoration( - color: StreamChatTheme.of(context) - .colorTheme - .overlay, + color: StreamChatTheme.of(context).colorTheme.overlay, shape: BoxShape.circle, ), child: StreamUserAvatar( @@ -258,9 +252,7 @@ class _NewChatScreenState extends State { StreamNeumorphicButton( child: Center( child: StreamSvgIcon( - color: StreamChatTheme.of(context) - .colorTheme - .accentPrimary, + color: StreamChatTheme.of(context).colorTheme.accentPrimary, size: 24, icon: StreamSvgIcons.contacts, ), @@ -269,9 +261,7 @@ class _NewChatScreenState extends State { const SizedBox(width: 8), Text( AppLocalizations.of(context).createAGroup, - style: StreamChatTheme.of(context) - .textTheme - .bodyBold, + style: StreamChatTheme.of(context).textTheme.bodyBold, ), ], ), @@ -281,8 +271,7 @@ class _NewChatScreenState extends State { Container( width: double.maxFinite, decoration: BoxDecoration( - gradient: - StreamChatTheme.of(context).colorTheme.bgGradient, + gradient: StreamChatTheme.of(context).colorTheme.bgGradient, ), child: Padding( padding: const EdgeInsets.symmetric( @@ -290,17 +279,13 @@ class _NewChatScreenState extends State { horizontal: 8, ), child: Text( - _isSearchActive - ? '${AppLocalizations.of(context).matchesFor} "$_userNameQuery"' - : AppLocalizations.of(context).onThePlatorm, - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.5))), + _isSearchActive + ? '${AppLocalizations.of(context).matchesFor} "$_userNameQuery"' + : AppLocalizations.of(context).onThePlatorm, + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.5), + ), + ), ), ), Expanded( @@ -320,27 +305,25 @@ class _NewChatScreenState extends State { _chipInputTextFieldState!.removeItem(user); } }, - itemBuilder: ( - context, - users, - index, - defaultWidget, - ) { - return defaultWidget.copyWith( - selected: - _selectedUsers.contains(users[index]), - ); - }, + itemBuilder: + ( + context, + users, + index, + defaultWidget, + ) { + return defaultWidget.copyWith( + selected: _selectedUsers.contains(users[index]), + ); + }, emptyBuilder: (_) { return LayoutBuilder( builder: (context, viewportConstraints) { return SingleChildScrollView( - physics: - const AlwaysScrollableScrollPhysics(), + physics: const AlwaysScrollableScrollPhysics(), child: ConstrainedBox( constraints: BoxConstraints( - minHeight: - viewportConstraints.maxHeight, + minHeight: viewportConstraints.maxHeight, ), child: Center( child: Column( @@ -354,18 +337,12 @@ class _NewChatScreenState extends State { ), ), Text( - AppLocalizations.of(context) - .noUserMatchesTheseKeywords, - style: StreamChatTheme.of( - context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme - .of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.5)), + AppLocalizations.of(context).noUserMatchesTheseKeywords, + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of( + context, + ).colorTheme.textHighEmphasis.withOpacity(.5), + ), ), ], ), @@ -389,10 +366,7 @@ class _NewChatScreenState extends State { AppLocalizations.of(context).noChatsHereYet, style: TextStyle( fontSize: 12, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.5), ), ), ); diff --git a/sample_app/lib/pages/new_group_chat_screen.dart b/sample_app/lib/pages/new_group_chat_screen.dart index d9fd969568..ffbc4cdd1a 100644 --- a/sample_app/lib/pages/new_group_chat_screen.dart +++ b/sample_app/lib/pages/new_group_chat_screen.dart @@ -16,8 +16,7 @@ class NewGroupChatScreen extends StatefulWidget { } class _NewGroupChatScreenState extends State { - late final TextEditingController _controller = TextEditingController() - ..addListener(_userNameListener); + late final TextEditingController _controller = TextEditingController()..addListener(_userNameListener); String _userNameQuery = ''; @@ -45,8 +44,7 @@ class _NewGroupChatScreenState extends State { _isSearchActive = _userNameQuery.isNotEmpty; }); userListController.filter = Filter.and([ - if (_userNameQuery.isNotEmpty) - Filter.autoComplete('name', _userNameQuery), + if (_userNameQuery.isNotEmpty) Filter.autoComplete('name', _userNameQuery), Filter.notEqual('id', StreamChat.of(context).currentUser!.id), ]); userListController.doInitialLoad(); @@ -94,7 +92,7 @@ class _NewGroupChatScreenState extends State { extra: state, ); }, - ) + ), ], ), body: StreamConnectionStatusBuilder( @@ -121,8 +119,7 @@ class _NewGroupChatScreenState extends State { message: statusString, child: NestedScrollView( floatHeaderSlivers: true, - headerSliverBuilder: - (BuildContext context, bool innerBoxIsScrolled) { + headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { return [ SliverToBoxAdapter( child: SearchTextField( @@ -138,8 +135,7 @@ class _NewGroupChatScreenState extends State { scrollDirection: Axis.horizontal, itemCount: state.users.length, padding: const EdgeInsets.all(8), - separatorBuilder: (_, __) => - const SizedBox(width: 16), + separatorBuilder: (_, __) => const SizedBox(width: 16), itemBuilder: (_, index) { final user = state.users.elementAt(index); return Column( @@ -147,13 +143,10 @@ class _NewGroupChatScreenState extends State { Stack( children: [ StreamUserAvatar( - onlineIndicatorAlignment: - const Alignment(0.9, 0.9), + onlineIndicatorAlignment: const Alignment(0.9, 0.9), user: user, - borderRadius: - BorderRadius.circular(32), - constraints: - const BoxConstraints.tightFor( + borderRadius: BorderRadius.circular(32), + constraints: const BoxConstraints.tightFor( height: 64, width: 64, ), @@ -167,29 +160,20 @@ class _NewGroupChatScreenState extends State { }, child: DecoratedBox( decoration: BoxDecoration( - color: - StreamChatTheme.of(context) - .colorTheme - .appBg, + color: StreamChatTheme.of(context).colorTheme.appBg, shape: BoxShape.circle, border: Border.all( - color: StreamChatTheme.of( - context) - .colorTheme - .appBg, + color: StreamChatTheme.of(context).colorTheme.appBg, ), ), child: StreamSvgIcon( - color: - StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, size: 24, icon: StreamSvgIcons.close, ), ), ), - ) + ), ], ), const SizedBox(height: 4), @@ -213,9 +197,7 @@ class _NewGroupChatScreenState extends State { child: Container( width: double.maxFinite, decoration: BoxDecoration( - gradient: StreamChatTheme.of(context) - .colorTheme - .bgGradient, + gradient: StreamChatTheme.of(context).colorTheme.bgGradient, ), child: Padding( padding: const EdgeInsets.symmetric( @@ -227,9 +209,7 @@ class _NewGroupChatScreenState extends State { ? '${AppLocalizations.of(context).matchesFor} "$_userNameQuery"' : AppLocalizations.of(context).onThePlatorm, style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), ), ), @@ -266,22 +246,14 @@ class _NewGroupChatScreenState extends State { child: StreamSvgIcon( icon: StreamSvgIcons.search, size: 96, - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), ), Text( - AppLocalizations.of(context) - .noUserMatchesTheseKeywords, - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, - ), + AppLocalizations.of(context).noUserMatchesTheseKeywords, + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, + ), ), ], ), @@ -312,8 +284,7 @@ class _HeaderDelegate extends SliverPersistentHeaderDelegate { final double height; @override - Widget build( - BuildContext context, double shrinkOffset, bool overlapsContent) { + Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { return ColoredBox( color: StreamChatTheme.of(context).colorTheme.barsBg, child: child, diff --git a/sample_app/lib/pages/pinned_messages_screen.dart b/sample_app/lib/pages/pinned_messages_screen.dart index 01c9ac5075..dcd4e47290 100644 --- a/sample_app/lib/pages/pinned_messages_screen.dart +++ b/sample_app/lib/pages/pinned_messages_screen.dart @@ -64,37 +64,32 @@ class _PinnedMessagesScreenState extends State { AppLocalizations.of(context).noPinnedItems, style: TextStyle( fontSize: 17, - color: - StreamChatTheme.of(context).colorTheme.textHighEmphasis, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), RichText( textAlign: TextAlign.center, - text: TextSpan(children: [ - TextSpan( - text: '${AppLocalizations.of(context).longPressMessage} ', - style: TextStyle( - fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + text: TextSpan( + children: [ + TextSpan( + text: '${AppLocalizations.of(context).longPressMessage} ', + style: TextStyle( + fontSize: 14, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + ), ), - ), - TextSpan( - text: AppLocalizations.of(context).pinToConversation, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + TextSpan( + text: AppLocalizations.of(context).pinToConversation, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + ), ), - ), - ]), + ], + ), ), ], ), diff --git a/sample_app/lib/pages/reminders_page.dart b/sample_app/lib/pages/reminders_page.dart index 18507e9e83..ca75dea89f 100644 --- a/sample_app/lib/pages/reminders_page.dart +++ b/sample_app/lib/pages/reminders_page.dart @@ -15,20 +15,21 @@ class RemindersPage extends StatefulWidget { } class _RemindersPageState extends State { - late final controller = StreamMessageReminderListController( - client: StreamChat.of(context).client, - )..eventListener = (event) { - if (event.type == EventType.connectionRecovered || - event.type == EventType.notificationReminderDue) { - // This will create the query filter with the updated current date - // and time, so that the reminders list is updated with the new - // reminders that are due. - onFilterChanged(_currentFilter); - } - - // Returning false as we also want the controller to handle the event. - return false; - }; + late final controller = + StreamMessageReminderListController( + client: StreamChat.of(context).client, + ) + ..eventListener = (event) { + if (event.type == EventType.connectionRecovered || event.type == EventType.notificationReminderDue) { + // This will create the query filter with the updated current date + // and time, so that the reminders list is updated with the new + // reminders that are due. + onFilterChanged(_currentFilter); + } + + // Returning false as we also want the controller to handle the event. + return false; + }; @override void dispose() { @@ -209,7 +210,8 @@ enum MessageRemindersFilter { overdue('Overdue'), upcoming('Upcoming'), scheduled('Scheduled'), - savedForLater('Saved for later'); + savedForLater('Saved for later') + ; const MessageRemindersFilter(this.label); final String label; @@ -238,12 +240,10 @@ class MessageRemindersFilterSelection extends StatefulWidget { final ValueSetter onSelected; @override - State createState() => - _MessageRemindersFilterSelectionState(); + State createState() => _MessageRemindersFilterSelectionState(); } -class _MessageRemindersFilterSelectionState - extends State { +class _MessageRemindersFilterSelectionState extends State { final _filterKeys = {}; @override diff --git a/sample_app/lib/pages/splash_screen.dart b/sample_app/lib/pages/splash_screen.dart index 0bbdcd9ebe..a7fb8c9269 100644 --- a/sample_app/lib/pages/splash_screen.dart +++ b/sample_app/lib/pages/splash_screen.dart @@ -3,8 +3,7 @@ import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; -mixin SplashScreenStateMixin on State - implements TickerProvider { +mixin SplashScreenStateMixin on State implements TickerProvider { late final _animationController = AnimationController( vsync: this, duration: const Duration( @@ -20,29 +19,38 @@ mixin SplashScreenStateMixin on State ), ); - late final _circleAnimation = Tween( - begin: 0, - end: 1000, - ).animate(CurvedAnimation( - parent: _animationController, - curve: Curves.easeInOut, - )); + late final _circleAnimation = + Tween( + begin: 0, + end: 1000, + ).animate( + CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + ), + ); - late final _colorAnimation = ColorTween( - begin: const Color(0xff005FFF), - end: Colors.transparent, - ).animate(CurvedAnimation( - parent: _animationController, - curve: Curves.easeInOut, - )); + late final _colorAnimation = + ColorTween( + begin: const Color(0xff005FFF), + end: Colors.transparent, + ).animate( + CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + ), + ); - late final _scaleAnimation = Tween( - begin: 1, - end: 1.5, - ).animate(CurvedAnimation( - parent: _scaleAnimationController, - curve: Curves.easeInOutCubic, - )); + late final _scaleAnimation = + Tween( + begin: 1, + end: 1.5, + ).animate( + CurvedAnimation( + parent: _scaleAnimationController, + curve: Curves.easeInOutCubic, + ), + ); bool animationCompleted = false; @@ -75,50 +83,46 @@ mixin SplashScreenStateMixin on State } Widget buildAnimation() => Stack( - clipBehavior: Clip.none, - alignment: Alignment.center, - children: [ - AnimatedBuilder( - animation: _scaleAnimation, - builder: (context, child) => - Transform.scale(scale: _scaleAnimation.value, child: child), - child: AnimatedBuilder( - animation: _colorAnimation, - builder: (context, child) { - return DecoratedBox( - decoration: BoxDecoration(color: _colorAnimation.value), - child: Center( - child: !_animationController.isAnimating - ? child - : const SizedBox(), - ), - ); - }, - child: RepaintBoundary( - child: Lottie.asset( - 'assets/floating_boat.json', - alignment: Alignment.center, - ), + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + AnimatedBuilder( + animation: _scaleAnimation, + builder: (context, child) => Transform.scale(scale: _scaleAnimation.value, child: child), + child: AnimatedBuilder( + animation: _colorAnimation, + builder: (context, child) { + return DecoratedBox( + decoration: BoxDecoration(color: _colorAnimation.value), + child: Center( + child: !_animationController.isAnimating ? child : const SizedBox(), ), + ); + }, + child: RepaintBoundary( + child: Lottie.asset( + 'assets/floating_boat.json', + alignment: Alignment.center, ), ), - AnimatedBuilder( - animation: _circleAnimation, - builder: (context, snapshot) { - return Transform.scale( - scale: _circleAnimation.value, - child: Container( - width: 1, - height: 1, - decoration: BoxDecoration( - color: Colors.white - .withOpacity(1 - _animationController.value), - shape: BoxShape.circle, - ), - ), - ); - }, - ), - ], - ); + ), + ), + AnimatedBuilder( + animation: _circleAnimation, + builder: (context, snapshot) { + return Transform.scale( + scale: _circleAnimation.value, + child: Container( + width: 1, + height: 1, + decoration: BoxDecoration( + color: Colors.white.withOpacity(1 - _animationController.value), + shape: BoxShape.circle, + ), + ), + ); + }, + ), + ], + ); } diff --git a/sample_app/lib/pages/thread_list_page.dart b/sample_app/lib/pages/thread_list_page.dart index 8dc69ef3df..f730dde1f3 100644 --- a/sample_app/lib/pages/thread_list_page.dart +++ b/sample_app/lib/pages/thread_list_page.dart @@ -29,9 +29,7 @@ class _ThreadListPageState extends State { valueListenable: controller.unseenThreadIds, builder: (_, unreadThreads, __) => StreamUnreadThreadsBanner( unreadThreads: unreadThreads, - onTap: () => controller - .refresh(resetValue: false) - .then((_) => controller.clearUnseenThreadIds()), + onTap: () => controller.refresh(resetValue: false).then((_) => controller.clearUnseenThreadIds()), ), ), Expanded( @@ -41,9 +39,9 @@ class _ThreadListPageState extends State { final channelCid = thread.channelCid; final channel = StreamChat.of(context).client.channel( - channelCid.split(':')[0], - id: channelCid.split(':')[1], - ); + channelCid.split(':')[0], + id: channelCid.split(':')[1], + ); Navigator.of(context).push( MaterialPageRoute( diff --git a/sample_app/lib/pages/thread_page.dart b/sample_app/lib/pages/thread_page.dart index 3e8376eb44..a607d94fd7 100644 --- a/sample_app/lib/pages/thread_page.dart +++ b/sample_app/lib/pages/thread_page.dart @@ -80,17 +80,18 @@ class _ThreadPageState extends State { onReplyTap: _reply, showEditMessage: message.sharedLocation == null, attachmentBuilders: [locationAttachmentBuilder], - bottomRowBuilderWithDefaultWidget: ( - context, - message, - defaultWidget, - ) { - return defaultWidget.copyWith( - deletedBottomRowBuilder: (context, message) { - return const StreamVisibleFootnote(); + bottomRowBuilderWithDefaultWidget: + ( + context, + message, + defaultWidget, + ) { + return defaultWidget.copyWith( + deletedBottomRowBuilder: (context, message) { + return const StreamVisibleFootnote(); + }, + ); }, - ); - }, ); }, ), diff --git a/sample_app/lib/pages/user_mentions_page.dart b/sample_app/lib/pages/user_mentions_page.dart index d4bb19e273..c4cb48fd35 100644 --- a/sample_app/lib/pages/user_mentions_page.dart +++ b/sample_app/lib/pages/user_mentions_page.dart @@ -44,18 +44,14 @@ class _UserMentionsPageState extends State { child: StreamSvgIcon( icon: StreamSvgIcons.mentions, size: 96, - color: - StreamChatTheme.of(context).colorTheme.disabled, + color: StreamChatTheme.of(context).colorTheme.disabled, ), ), Text( AppLocalizations.of(context).noMentionsExistYet, - style: - StreamChatTheme.of(context).textTheme.body.copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, - ), + style: StreamChatTheme.of(context).textTheme.body.copyWith( + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, + ), ), ], ), diff --git a/sample_app/lib/routes/app_routes.dart b/sample_app/lib/routes/app_routes.dart index 094060eb44..ef0f41dce5 100644 --- a/sample_app/lib/routes/app_routes.dart +++ b/sample_app/lib/routes/app_routes.dart @@ -19,24 +19,19 @@ final appRoutes = [ GoRoute( name: Routes.CHANNEL_LIST_PAGE.name, path: Routes.CHANNEL_LIST_PAGE.path, - builder: (BuildContext context, GoRouterState state) => - const ChannelListPage(), + builder: (BuildContext context, GoRouterState state) => const ChannelListPage(), routes: [ GoRoute( name: Routes.CHANNEL_PAGE.name, path: Routes.CHANNEL_PAGE.path, builder: (context, state) { - final channel = StreamChat.of(context) - .client - .state - .channels[state.pathParameters['cid']]; + final channel = StreamChat.of(context).client.state.channels[state.pathParameters['cid']]; final messageId = state.uri.queryParameters['mid']; final parentId = state.uri.queryParameters['pid']; Message? parentMessage; if (parentId != null) { - parentMessage = channel?.state!.messages - .firstWhereOrNull((it) => it.id == parentId); + parentMessage = channel?.state!.messages.firstWhereOrNull((it) => it.id == parentId); } return StreamChannel( @@ -58,10 +53,7 @@ final appRoutes = [ name: Routes.CHAT_INFO_SCREEN.name, path: Routes.CHAT_INFO_SCREEN.path, builder: (BuildContext context, GoRouterState state) { - final channel = StreamChat.of(context) - .client - .state - .channels[state.pathParameters['cid']]; + final channel = StreamChat.of(context).client.state.channels[state.pathParameters['cid']]; return StreamChannel( channel: channel!, child: ChatInfoScreen( @@ -75,10 +67,7 @@ final appRoutes = [ name: Routes.GROUP_INFO_SCREEN.name, path: Routes.GROUP_INFO_SCREEN.path, builder: (BuildContext context, GoRouterState state) { - final channel = StreamChat.of(context) - .client - .state - .channels[state.pathParameters['cid']]; + final channel = StreamChat.of(context).client.state.channels[state.pathParameters['cid']]; return StreamChannel( channel: channel!, child: GroupInfoScreen( @@ -116,13 +105,11 @@ final appRoutes = [ GoRoute( name: Routes.CHOOSE_USER.name, path: Routes.CHOOSE_USER.path, - builder: (BuildContext context, GoRouterState state) => - const ChooseUserPage(), + builder: (BuildContext context, GoRouterState state) => const ChooseUserPage(), ), GoRoute( name: Routes.ADVANCED_OPTIONS.name, path: Routes.ADVANCED_OPTIONS.path, - builder: (BuildContext context, GoRouterState state) => - const AdvancedOptionsPage(), + builder: (BuildContext context, GoRouterState state) => const AdvancedOptionsPage(), ), ]; diff --git a/sample_app/lib/routes/routes.dart b/sample_app/lib/routes/routes.dart index d60383edd2..ac62189d37 100644 --- a/sample_app/lib/routes/routes.dart +++ b/sample_app/lib/routes/routes.dart @@ -4,24 +4,24 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Application routes abstract class Routes { - static const RouteConfig CHOOSE_USER = - RouteConfig(name: 'choose_user', path: '/users'); - static const RouteConfig ADVANCED_OPTIONS = - RouteConfig(name: 'advanced_options', path: '/options'); - static const ChannelRouteConfig CHANNEL_PAGE = - ChannelRouteConfig(name: 'channel_page', path: 'channel/:cid'); - static const RouteConfig NEW_CHAT = - RouteConfig(name: 'new_chat', path: '/new_chat'); - static const RouteConfig NEW_GROUP_CHAT = - RouteConfig(name: 'new_group_chat', path: '/new_group_chat'); + static const RouteConfig CHOOSE_USER = RouteConfig(name: 'choose_user', path: '/users'); + static const RouteConfig ADVANCED_OPTIONS = RouteConfig(name: 'advanced_options', path: '/options'); + static const ChannelRouteConfig CHANNEL_PAGE = ChannelRouteConfig(name: 'channel_page', path: 'channel/:cid'); + static const RouteConfig NEW_CHAT = RouteConfig(name: 'new_chat', path: '/new_chat'); + static const RouteConfig NEW_GROUP_CHAT = RouteConfig(name: 'new_group_chat', path: '/new_group_chat'); static const RouteConfig NEW_GROUP_CHAT_DETAILS = RouteConfig( - name: 'new_group_chat_details', path: '/new_group_chat_details'); - static const ChannelRouteConfig CHAT_INFO_SCREEN = - ChannelRouteConfig(name: 'chat_info_screen', path: 'chat_info_screen'); - static const ChannelRouteConfig GROUP_INFO_SCREEN = - ChannelRouteConfig(name: 'group_info_screen', path: 'group_info_screen'); - static const RouteConfig CHANNEL_LIST_PAGE = - RouteConfig(name: 'channel_list_page', path: '/channels'); + name: 'new_group_chat_details', + path: '/new_group_chat_details', + ); + static const ChannelRouteConfig CHAT_INFO_SCREEN = ChannelRouteConfig( + name: 'chat_info_screen', + path: 'chat_info_screen', + ); + static const ChannelRouteConfig GROUP_INFO_SCREEN = ChannelRouteConfig( + name: 'group_info_screen', + path: 'group_info_screen', + ); + static const RouteConfig CHANNEL_LIST_PAGE = RouteConfig(name: 'channel_list_page', path: '/channels'); } class RouteConfig { @@ -36,7 +36,7 @@ class ChannelRouteConfig extends RouteConfig { Map params(Channel channel) => {'cid': channel.cid!}; Map queryParams(Message message) => { - 'mid': message.id, - if (message.parentId != null) 'pid': message.parentId! - }; + 'mid': message.id, + if (message.parentId != null) 'pid': message.parentId!, + }; } diff --git a/sample_app/lib/state/init_data.dart b/sample_app/lib/state/init_data.dart index 7f23896afd..3bb7fc4f1b 100644 --- a/sample_app/lib/state/init_data.dart +++ b/sample_app/lib/state/init_data.dart @@ -31,6 +31,5 @@ class InitData { final StreamChatClient client; final StreamingSharedPreferences preferences; - InitData copyWith({required StreamChatClient client}) => - InitData(client, preferences); + InitData copyWith({required StreamChatClient client}) => InitData(client, preferences); } diff --git a/sample_app/lib/utils/app_config.dart b/sample_app/lib/utils/app_config.dart index e006cddfee..d6443747e6 100644 --- a/sample_app/lib/utils/app_config.dart +++ b/sample_app/lib/utils/app_config.dart @@ -1,81 +1,68 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -const sentryDsn = - 'https://6381ef88de4140db8f5e25ab37e0f08c@o1213503.ingest.sentry.io/6352870'; +const sentryDsn = 'https://6381ef88de4140db8f5e25ab37e0f08c@o1213503.ingest.sentry.io/6352870'; const kDefaultStreamApiKey = 'kv7mcsxr24p8'; final defaultUsers = { 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FsdmF0b3JlIn0.pgiJz7sIc7iP29BHKFwe3nLm5-OaR_1l2P-SlgiC9a8': User( - id: 'salvatore', - extraData: const { - 'name': 'Salvatore Giordano', - 'image': - 'https://avatars.githubusercontent.com/u/20601437?s=460&u=3f66c22a7483980624804054ae7f357cf102c784&v=4', - }, - ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FoaWwifQ.WnIUoB5gR2kcAsFhiDvkiD6zdHXZ-VSU2aQWWkhsvfo': - User( + id: 'salvatore', + extraData: const { + 'name': 'Salvatore Giordano', + 'image': + 'https://avatars.githubusercontent.com/u/20601437?s=460&u=3f66c22a7483980624804054ae7f357cf102c784&v=4', + }, + ), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FoaWwifQ.WnIUoB5gR2kcAsFhiDvkiD6zdHXZ-VSU2aQWWkhsvfo': User( id: 'sahil', extraData: const { 'name': 'Sahil Kumar', - 'image': - 'https://avatars.githubusercontent.com/u/25670178?s=400&u=30ded3784d8d2310c5748f263fd5e6433c119aa1&v=4', + 'image': 'https://avatars.githubusercontent.com/u/25670178?s=400&u=30ded3784d8d2310c5748f263fd5e6433c119aa1&v=4', }, ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYmVuIn0.nAz2sNFGQwY7rl2Og2z3TGHUsdpnN53tOsUglJFvLmg': - User( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYmVuIn0.nAz2sNFGQwY7rl2Og2z3TGHUsdpnN53tOsUglJFvLmg': User( id: 'ben', extraData: const { 'name': 'Ben Golden', 'image': 'https://avatars.githubusercontent.com/u/1581974?s=400&v=4', }, ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidGhpZXJyeSJ9.lEq6TrZtHzjoNtf7HHRufUPyGo_pa8vg4_XhEBp4ckY': - User( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidGhpZXJyeSJ9.lEq6TrZtHzjoNtf7HHRufUPyGo_pa8vg4_XhEBp4ckY': User( id: 'thierry', extraData: const { 'name': 'Thierry Schellenbach', - 'image': - 'https://avatars.githubusercontent.com/u/265409?s=400&u=2d0e3bb1820db992066196bff7b004f0eee8e28d&v=4', + 'image': 'https://avatars.githubusercontent.com/u/265409?s=400&u=2d0e3bb1820db992066196bff7b004f0eee8e28d&v=4', }, ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidG9tbWFzbyJ9.GLSI0ESshERMo2WjUpysD709NEtn1zmGimUN2an7g9o': - User( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidG9tbWFzbyJ9.GLSI0ESshERMo2WjUpysD709NEtn1zmGimUN2an7g9o': User( id: 'tommaso', extraData: const { 'name': 'Tommaso Barbugli', 'image': 'https://avatars.githubusercontent.com/u/88735?s=400&v=4', }, ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiZGV2ZW4ifQ.z3zI4PqJnNhc-1o-VKcmb6BnnQ0oxFNCRHwEulHqcWc': - User( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiZGV2ZW4ifQ.z3zI4PqJnNhc-1o-VKcmb6BnnQ0oxFNCRHwEulHqcWc': User( id: 'deven', extraData: const { 'name': 'Deven Joshi', - 'image': - 'https://avatars.githubusercontent.com/u/26357843?s=400&u=0c61d890866e67bf69f58878be58915e9bfd39ee&v=4', + 'image': 'https://avatars.githubusercontent.com/u/26357843?s=400&u=0c61d890866e67bf69f58878be58915e9bfd39ee&v=4', }, ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoibmVldmFzaCJ9.3EdHegTxibrz3A9cTiKmpEyawwcCVB8FXnoFzr4eKvw': - User( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoibmVldmFzaCJ9.3EdHegTxibrz3A9cTiKmpEyawwcCVB8FXnoFzr4eKvw': User( id: 'neevash', extraData: const { 'name': 'Neevash Ramdial', - 'image': - 'https://avatars.githubusercontent.com/u/25674767?s=400&u=1d7333baf7dd9d143db8bfcdb31a838b89cfff9c&v=4', + 'image': 'https://avatars.githubusercontent.com/u/25674767?s=400&u=1d7333baf7dd9d143db8bfcdb31a838b89cfff9c&v=4', }, ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MSJ9.fnelU7HcP7QoEEsCGteNlF1fppofzNlrnpDQuIgeKCU': - User( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MSJ9.fnelU7HcP7QoEEsCGteNlF1fppofzNlrnpDQuIgeKCU': User( id: 'qatest1', extraData: const { 'name': 'QA test 1', }, ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MiJ9.vSCqAEbs2WVmMWsOsa7065Fsjq-rsTih6qsHPynl7XM': - User( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MiJ9.vSCqAEbs2WVmMWsOsa7065Fsjq-rsTih6qsHPynl7XM': User( id: 'qatest2', extraData: const { 'name': 'QA test 2', diff --git a/sample_app/lib/utils/local_notification_observer.dart b/sample_app/lib/utils/local_notification_observer.dart index 4b2fc12a69..7be9674c63 100644 --- a/sample_app/lib/utils/local_notification_observer.dart +++ b/sample_app/lib/utils/local_notification_observer.dart @@ -12,18 +12,17 @@ class LocalNotificationObserver extends NavigatorObserver { ) { _subscription = client .on( - EventType.messageNew, - EventType.notificationMessageNew, - ) + EventType.messageNew, + EventType.notificationMessageNew, + ) .listen((event) { - _handleEvent(event, client, navigatorKey); - }); + _handleEvent(event, client, navigatorKey); + }); } Route? currentRoute; late final StreamSubscription _subscription; - void _handleEvent(Event event, StreamChatClient client, - GlobalKey navigatorKey) { + void _handleEvent(Event event, StreamChatClient client, GlobalKey navigatorKey) { if (event.message?.user?.id == client.state.currentUser?.id) { return; } diff --git a/sample_app/lib/utils/localizations.dart b/sample_app/lib/utils/localizations.dart index 7ce1005291..2d761d8501 100644 --- a/sample_app/lib/utils/localizations.dart +++ b/sample_app/lib/utils/localizations.dart @@ -20,20 +20,17 @@ class AppLocalizations { 'create_a_group': 'Create a Group', 'custom_settings': 'Custom settings', 'delete': 'Delete', - 'delete_conversation_are_you_sure': - 'Are you sure you want to delete this conversation?', + 'delete_conversation_are_you_sure': 'Are you sure you want to delete this conversation?', 'delete_conversation_title': 'Delete Conversation', 'disconnected': 'Disconnected', 'error_connecting': 'Error connecting, retry', 'files': 'Files', 'files_appear_here': 'Files sent in this chat will appear here', - 'group_shared_with_user_appear_here': - 'Group shared with User will appear here.', + 'group_shared_with_user_appear_here': 'Group shared with User will appear here.', 'last_seen': 'Last seen', 'leave': 'Leave', 'leave_conversation': 'Leave conversation', - 'leave_conversation_are_you_sure': - 'Are you sure you want to leave this conversation?', + 'leave_conversation_are_you_sure': 'Are you sure you want to leave this conversation?', 'leave_group': 'Leave Group', 'loading': 'Loading...', 'login': 'Login', @@ -65,21 +62,18 @@ class AppLocalizations { 'ok': 'OK', 'online': 'Online', 'on_the_platform': 'On the platform', - 'operation_could_not_be_completed': - "The operation couldn't be completed.", + 'operation_could_not_be_completed': "The operation couldn't be completed.", 'owner': 'Owner', 'pin_group': 'Pin group', 'photos_and_videos': 'Photos & Videos', - 'photos_or_videos_will_appear_here': - 'Photos or videos sent in this chat will \nappear here', + 'photos_or_videos_will_appear_here': 'Photos or videos sent in this chat will \nappear here', 'pinned_messages': 'Pinned Messages', 'pin_to_conversation': 'Pin to conversation', 'reconnecting': 'Reconnecting...', 'remove': 'Remove', 'remove_from_group': 'Remove From Group', 'remove_member': 'Remove member', - 'remove_member_are_you_sure': - 'Are you sure you want to remove this member?', + 'remove_member_are_you_sure': 'Are you sure you want to remove this member?', 'search': 'Search', 'select_user_to_try_flutter_sdk': 'Select a user to try the Flutter SDK', 'shared_groups': 'Shared Groups', @@ -113,20 +107,17 @@ class AppLocalizations { 'create_a_group': 'Crea un Gruppo', 'custom_settings': 'Opzioni Personalizzate', 'delete': 'Cancella', - 'delete_conversation_are_you_sure': - 'Sei sicuro di voler eliminare la conversazione?', + 'delete_conversation_are_you_sure': 'Sei sicuro di voler eliminare la conversazione?', 'delete_conversation_title': 'Elimina Conversazione', 'disconnected': 'Disconnesso', 'error_connecting': 'Errore durante la connessione, riprova', 'files': 'File', 'files_appear_here': 'I file inviati in questa chat compariranno qui', - 'group_shared_with_user_appear_here': - "I gruppi in comune con quest'utente compariranno qui", + 'group_shared_with_user_appear_here': "I gruppi in comune con quest'utente compariranno qui", 'last_seen': 'Ultimo accesso', 'leave': 'Lascia', 'leave_conversation': 'Lascia conversazione', - 'leave_conversation_are_you_sure': - 'Sei sicuro di voler lasciare questa conversazione?', + 'leave_conversation_are_you_sure': 'Sei sicuro di voler lasciare questa conversazione?', 'leave_group': 'Lascia Gruppo', 'loading': 'Caricamento...', 'login': 'Login', @@ -158,12 +149,10 @@ class AppLocalizations { 'ok': 'OK', 'online': 'Online', 'on_the_platform': 'Sulla piattaforma', - 'operation_could_not_be_completed': - "Non é stato possibile completare l'operazione.", + 'operation_could_not_be_completed': "Non é stato possibile completare l'operazione.", 'owner': 'Proprietario', 'photos_and_videos': 'Foto & Video', - 'photos_or_videos_will_appear_here': - 'Foto or video inviati in questa chat \ncompariranno qui', + 'photos_or_videos_will_appear_here': 'Foto or video inviati in questa chat \ncompariranno qui', 'pinned_messages': 'Messaggi in evidenza', 'pin_group': 'Gruppo di evidenziazione', 'pin_to_conversation': 'Metti in evidenza', @@ -171,11 +160,9 @@ class AppLocalizations { 'remove': 'Rimuovi', 'remove_from_group': 'Rimuovi Dal Gruppo', 'remove_member': 'Rimuovi membro', - 'remove_member_are_you_sure': - 'Sei sicuro di voler rimuovere questo membro?', + 'remove_member_are_you_sure': 'Sei sicuro di voler rimuovere questo membro?', 'search': 'Cerca', - 'select_user_to_try_flutter_sdk': - "Seleziona un utente per provare l'SDK Flutter", + 'select_user_to_try_flutter_sdk': "Seleziona un utente per provare l'SDK Flutter", 'shared_groups': 'Gruppi in comune', 'sign_out': 'Sign out', 'something_went_wrong_error_message': 'Qualcosa é andato storto', @@ -256,8 +243,7 @@ class AppLocalizations { } String get deleteConversationAreYouSure { - return _localizedValues[locale.languageCode]![ - 'delete_conversation_are_you_sure']!; + return _localizedValues[locale.languageCode]!['delete_conversation_are_you_sure']!; } String get deleteConversationTitle { @@ -281,8 +267,7 @@ class AppLocalizations { } String get groupSharedWithUserAppearHere { - return _localizedValues[locale.languageCode]![ - 'group_shared_with_user_appear_here']!; + return _localizedValues[locale.languageCode]!['group_shared_with_user_appear_here']!; } String get lastSeen { @@ -298,8 +283,7 @@ class AppLocalizations { } String get leaveConversationAreYouSure { - return _localizedValues[locale.languageCode]![ - 'leave_conversation_are_you_sure']!; + return _localizedValues[locale.languageCode]!['leave_conversation_are_you_sure']!; } String get leaveGroup { @@ -339,8 +323,7 @@ class AppLocalizations { } String get messageChannelDescription { - return _localizedValues[locale.languageCode]![ - 'message_channel_description']!; + return _localizedValues[locale.languageCode]!['message_channel_description']!; } String get messageChannelName { @@ -412,8 +395,7 @@ class AppLocalizations { } String get noUserMatchesTheseKeywords { - return _localizedValues[locale.languageCode]![ - 'no_user_matches_these_keywords']!; + return _localizedValues[locale.languageCode]!['no_user_matches_these_keywords']!; } String get ok { @@ -429,8 +411,7 @@ class AppLocalizations { } String get operationCouldNotBeCompleted { - return _localizedValues[locale.languageCode]![ - 'operation_could_not_be_completed']!; + return _localizedValues[locale.languageCode]!['operation_could_not_be_completed']!; } String get owner { @@ -442,8 +423,7 @@ class AppLocalizations { } String get photosOrVideosWillAppearHere { - return _localizedValues[locale.languageCode]![ - 'photos_or_videos_will_appear_here']!; + return _localizedValues[locale.languageCode]!['photos_or_videos_will_appear_here']!; } String get pinGroup { @@ -475,8 +455,7 @@ class AppLocalizations { } String get removeMemberAreYouSure { - return _localizedValues[locale.languageCode]![ - 'remove_member_are_you_sure']!; + return _localizedValues[locale.languageCode]!['remove_member_are_you_sure']!; } String get search { @@ -484,8 +463,7 @@ class AppLocalizations { } String get selectUserToTryFlutterSDK { - return _localizedValues[locale.languageCode]![ - 'select_user_to_try_flutter_sdk']!; + return _localizedValues[locale.languageCode]!['select_user_to_try_flutter_sdk']!; } String get sharedGroups { @@ -497,8 +475,7 @@ class AppLocalizations { } String get somethingWentWrongErrorMessage { - return _localizedValues[locale.languageCode]![ - 'something_went_wrong_error_message']!; + return _localizedValues[locale.languageCode]!['something_went_wrong_error_message']!; } String get streamSDK { @@ -556,8 +533,7 @@ class AppLocalizationsDelegate extends LocalizationsDelegate { const AppLocalizationsDelegate(); @override - bool isSupported(Locale locale) => - AppLocalizations.languages().contains(locale.languageCode); + bool isSupported(Locale locale) => AppLocalizations.languages().contains(locale.languageCode); @override Future load(Locale locale) { diff --git a/sample_app/lib/utils/location_provider.dart b/sample_app/lib/utils/location_provider.dart index fbf7168028..67215d2efb 100644 --- a/sample_app/lib/utils/location_provider.dart +++ b/sample_app/lib/utils/location_provider.dart @@ -44,35 +44,36 @@ class LocationProvider { final settings = switch (CurrentPlatform.type) { PlatformType.android => AndroidSettings( - accuracy: accuracy, - distanceFilter: distanceFilter, - foregroundNotificationConfig: const ForegroundNotificationConfig( - setOngoing: true, - notificationText: notificationText, - notificationTitle: notificationTitle, - notificationIcon: AndroidResource(name: 'ic_notification'), - ), + accuracy: accuracy, + distanceFilter: distanceFilter, + foregroundNotificationConfig: const ForegroundNotificationConfig( + setOngoing: true, + notificationText: notificationText, + notificationTitle: notificationTitle, + notificationIcon: AndroidResource(name: 'ic_notification'), ), + ), PlatformType.ios || PlatformType.macOS => AppleSettings( - accuracy: accuracy, - activityType: activityType, - distanceFilter: distanceFilter, - showBackgroundLocationIndicator: true, - pauseLocationUpdatesAutomatically: true, - ), + accuracy: accuracy, + activityType: activityType, + distanceFilter: distanceFilter, + showBackgroundLocationIndicator: true, + pauseLocationUpdatesAutomatically: true, + ), _ => LocationSettings( - accuracy: accuracy, - distanceFilter: distanceFilter, - ) + accuracy: accuracy, + distanceFilter: distanceFilter, + ), }; _positionSubscription?.cancel(); // avoid duplicate subscriptions - _positionSubscription = Geolocator.getPositionStream( - locationSettings: settings, - ).listen( - _positionStreamController.safeAdd, - onError: _positionStreamController.safeAddError, - ); + _positionSubscription = + Geolocator.getPositionStream( + locationSettings: settings, + ).listen( + _positionStreamController.safeAdd, + onError: _positionStreamController.safeAddError, + ); } /// Stop live tracking diff --git a/sample_app/lib/utils/notifications_service.dart b/sample_app/lib/utils/notifications_service.dart index 9c34099c4f..6a8b2e809b 100644 --- a/sample_app/lib/utils/notifications_service.dart +++ b/sample_app/lib/utils/notifications_service.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart' - hide Message; +import 'package:flutter_local_notifications/flutter_local_notifications.dart' hide Message; import 'package:go_router/go_router.dart'; import 'package:sample_app/routes/routes.dart'; import 'package:sample_app/utils/localizations.dart'; diff --git a/sample_app/lib/utils/shared_location_service.dart b/sample_app/lib/utils/shared_location_service.dart index 1d7a7128c6..27e04868dd 100644 --- a/sample_app/lib/utils/shared_location_service.dart +++ b/sample_app/lib/utils/shared_location_service.dart @@ -9,8 +9,8 @@ class SharedLocationService { SharedLocationService({ required StreamChatClient client, LocationProvider? locationProvider, - }) : _client = client, - _locationProvider = locationProvider ?? LocationProvider(); + }) : _client = client, + _locationProvider = locationProvider ?? LocationProvider(); final StreamChatClient _client; final LocationProvider _locationProvider; @@ -23,12 +23,12 @@ class SharedLocationService { _activeLiveLocationsSubscription = _client.state.activeLiveLocationsStream .distinct((prev, curr) => prev.length == curr.length) .listen((locations) async { - // If there are no more active locations to update, stop tracking. - if (locations.isEmpty) return _stopTrackingLocation(); + // If there are no more active locations to update, stop tracking. + if (locations.isEmpty) return _stopTrackingLocation(); - // Otherwise, start tracking the user's location. - return _startTrackingLocation(); - }); + // Otherwise, start tracking the user's location. + return _startTrackingLocation(); + }); return _client.getActiveLiveLocations().ignore(); } diff --git a/sample_app/lib/widgets/channel_list.dart b/sample_app/lib/widgets/channel_list.dart index a87e51b012..319a4664c3 100644 --- a/sample_app/lib/widgets/channel_list.dart +++ b/sample_app/lib/widgets/channel_list.dart @@ -21,8 +21,7 @@ class ChannelList extends StatefulWidget { class _ChannelList extends State { final ScrollController _scrollController = ScrollController(); - late final StreamMessageSearchListController _messageSearchListController = - StreamMessageSearchListController( + late final StreamMessageSearchListController _messageSearchListController = StreamMessageSearchListController( client: StreamChat.of(context).client, filter: Filter.in_('members', [StreamChat.of(context).currentUser!.id]), limit: 5, @@ -33,8 +32,7 @@ class _ChannelList extends State { ], ); - late final TextEditingController _controller = TextEditingController() - ..addListener(_channelQueryListener); + late final TextEditingController _controller = TextEditingController()..addListener(_channelQueryListener); bool _isSearchActive = false; @@ -86,8 +84,7 @@ class _ChannelList extends State { }, child: NotificationListener( onNotification: (ScrollNotification scrollInfo) { - if (_scrollController.position.userScrollDirection == - ScrollDirection.reverse) { + if (_scrollController.position.userScrollDirection == ScrollDirection.reverse) { FocusScope.of(context).unfocus(); } return true; @@ -147,25 +144,19 @@ class _ChannelListDefault extends StatelessWidget { context, MaterialPageRoute( builder: (context) { - final isOneToOne = channel.memberCount == 2 && - channel.isDistinct; + final isOneToOne = channel.memberCount == 2 && channel.isDistinct; return StreamChannel( channel: channel, child: isOneToOne ? ChatInfoScreen( - messageTheme: - chatTheme.ownMessageTheme, + messageTheme: chatTheme.ownMessageTheme, user: channel.state!.members - .where((m) => - m.userId != - channel.client.state - .currentUser!.id) + .where((m) => m.userId != channel.client.state.currentUser!.id) .first .user, ) : GroupInfoScreen( - messageTheme: - chatTheme.ownMessageTheme, + messageTheme: chatTheme.ownMessageTheme, ), ); }, @@ -187,8 +178,7 @@ class _ChannelListDefault extends StatelessWidget { final res = await showConfirmationBottomSheet( context, title: 'Delete Conversation', - question: - 'Are you sure you want to delete this conversation?', + question: 'Are you sure you want to delete this conversation?', okText: 'Delete', cancelText: 'Cancel', icon: StreamSvgIcon( @@ -204,9 +194,7 @@ class _ChannelListDefault extends StatelessWidget { ], ), child: ColoredBox( - color: channel.isPinned - ? chatTheme.colorTheme.highlight - : Colors.transparent, + color: channel.isPinned ? chatTheme.colorTheme.highlight : Colors.transparent, child: defaultWidget, ), ); @@ -233,14 +221,9 @@ class _ChannelListDefault extends StatelessWidget { }, child: Text( 'Start a chat', - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentPrimary, - ), + style: StreamChatTheme.of(context).textTheme.bodyBold.copyWith( + color: StreamChatTheme.of(context).colorTheme.accentPrimary, + ), ), ), ), @@ -293,35 +276,36 @@ class _ChannelListSearch extends StatelessWidget { }, ); }, - itemBuilder: ( - context, - messageResponses, - index, - defaultWidget, - ) { - final messageResponse = messageResponses[index]; + itemBuilder: + ( + context, + messageResponses, + index, + defaultWidget, + ) { + final messageResponse = messageResponses[index]; - return defaultWidget.copyWith( - onTap: () async { - FocusScope.of(context).requestFocus(FocusNode()); - final client = StreamChat.of(context).client; - final router = GoRouter.of(context); - final message = messageResponse.message; - final channel = client.channel( - messageResponse.channel!.type, - id: messageResponse.channel!.id, - ); - if (channel.state == null) { - await channel.watch(); - } - router.pushNamed( - Routes.CHANNEL_PAGE.name, - pathParameters: Routes.CHANNEL_PAGE.params(channel), - queryParameters: Routes.CHANNEL_PAGE.queryParams(message), + return defaultWidget.copyWith( + onTap: () async { + FocusScope.of(context).requestFocus(FocusNode()); + final client = StreamChat.of(context).client; + final router = GoRouter.of(context); + final message = messageResponse.message; + final channel = client.channel( + messageResponse.channel!.type, + id: messageResponse.channel!.id, + ); + if (channel.state == null) { + await channel.watch(); + } + router.pushNamed( + Routes.CHANNEL_PAGE.name, + pathParameters: Routes.CHANNEL_PAGE.params(channel), + queryParameters: Routes.CHANNEL_PAGE.queryParams(message), + ); + }, ); }, - ); - }, ); } } diff --git a/sample_app/lib/widgets/chips_input_text_field.dart b/sample_app/lib/widgets/chips_input_text_field.dart index 45368e0939..8b29a15a52 100644 --- a/sample_app/lib/widgets/chips_input_text_field.dart +++ b/sample_app/lib/widgets/chips_input_text_field.dart @@ -77,14 +77,9 @@ class ChipInputTextFieldState extends State> { padding: const EdgeInsets.symmetric(vertical: 4), child: Text( '${AppLocalizations.of(context).to.toUpperCase()}:', - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.5)), + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.5), + ), ), ), const SizedBox(width: 12), @@ -114,14 +109,9 @@ class ChipInputTextFieldState extends State> { disabledBorder: InputBorder.none, contentPadding: const EdgeInsets.only(top: 4), hintText: widget.hint, - hintStyle: StreamChatTheme.of(context) - .textTheme - .body - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.5)), + hintStyle: StreamChatTheme.of(context).textTheme.body.copyWith( + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.5), + ), ), ), ], @@ -134,18 +124,12 @@ class ChipInputTextFieldState extends State> { icon: _chips.isEmpty ? StreamSvgIcon( icon: StreamSvgIcons.user, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), size: 24, ) : StreamSvgIcon( icon: StreamSvgIcons.userAdd, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), size: 24, ), onPressed: resumeItemAddition, diff --git a/sample_app/lib/widgets/location/location_attachment.dart b/sample_app/lib/widgets/location/location_attachment.dart index 405f97b7df..b43dc0e718 100644 --- a/sample_app/lib/widgets/location/location_attachment.dart +++ b/sample_app/lib/widgets/location/location_attachment.dart @@ -32,8 +32,12 @@ class LocationAttachmentBuilder extends StreamAttachmentWidgetBuilder { bool canHandle(Message message, _) => message.sharedLocation != null; @override - Widget build(BuildContext context, Message message, _) { - assert(debugAssertCanHandle(message, _), ''); + Widget build( + BuildContext context, + Message message, + Map> attachments, + ) { + assert(debugAssertCanHandle(message, attachments), ''); final user = message.user; final location = message.sharedLocation!; diff --git a/sample_app/lib/widgets/search_text_field.dart b/sample_app/lib/widgets/search_text_field.dart index ec3084e47e..87eca779c1 100644 --- a/sample_app/lib/widgets/search_text_field.dart +++ b/sample_app/lib/widgets/search_text_field.dart @@ -50,17 +50,14 @@ class SearchTextField extends StatelessWidget { ), child: StreamSvgIcon( icon: StreamSvgIcons.search, - color: - StreamChatTheme.of(context).colorTheme.textHighEmphasis, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, size: 24, ), ), hintText: hintText, hintStyle: StreamChatTheme.of(context).textTheme.body.copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.5)), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.5), + ), contentPadding: EdgeInsets.zero, border: OutlineInputBorder( borderSide: BorderSide.none, diff --git a/sample_app/lib/widgets/simple_map_view.dart b/sample_app/lib/widgets/simple_map_view.dart index 0faadc11f7..a7d8c6e0a7 100644 --- a/sample_app/lib/widgets/simple_map_view.dart +++ b/sample_app/lib/widgets/simple_map_view.dart @@ -4,11 +4,12 @@ import 'package:flutter_map_animations/flutter_map_animations.dart'; import 'package:latlong2/latlong.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -typedef MarkerBuilder = Widget Function( - BuildContext context, - Animation animation, - double markerSize, -); +typedef MarkerBuilder = + Widget Function( + BuildContext context, + Animation animation, + double markerSize, + ); class SimpleMapView extends StatefulWidget { const SimpleMapView({ @@ -39,8 +40,7 @@ class SimpleMapView extends StatefulWidget { State createState() => _SimpleMapViewState(); } -class _SimpleMapViewState extends State - with TickerProviderStateMixin { +class _SimpleMapViewState extends State with TickerProviderStateMixin { late final _mapController = AnimatedMapController(vsync: this); late final _initialCenter = widget.coordinates.toLatLng(); diff --git a/sample_app/lib/widgets/stream_version.dart b/sample_app/lib/widgets/stream_version.dart index dfafee4cd3..416c9adabf 100644 --- a/sample_app/lib/widgets/stream_version.dart +++ b/sample_app/lib/widgets/stream_version.dart @@ -23,8 +23,7 @@ class StreamVersion extends StatelessWidget { final pubspec = snapshot.data!; final yaml = loadYaml(pubspec); - final streamChatDep = - yaml['packages']['stream_chat_flutter']['version']; + final streamChatDep = yaml['packages']['stream_chat_flutter']['version']; return Text( '${AppLocalizations.of(context).streamSDK} v $streamChatDep', diff --git a/sample_app/pubspec.yaml b/sample_app/pubspec.yaml index c9e10e5623..f43137f903 100644 --- a/sample_app/pubspec.yaml +++ b/sample_app/pubspec.yaml @@ -16,8 +16,8 @@ version: 2.2.0 # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: avatar_glow: ^3.0.0 From d6c8cab4e208880ef0883a678822ba68bd2750de Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 4 Feb 2026 16:03:32 +0530 Subject: [PATCH 02/33] refactor(ui)!: redesign Avatar components (#2502) * feat(ui): add new avatar components * refactor(ui)!: Redesign Avatar components This commit introduces a complete redesign of the Avatar components to simplify the API, improve consistency, and align with the new design system. The following components have been removed and replaced: - `StreamUserAvatar` - `StreamGroupAvatar` - `StreamChannelAvatar` They are replaced by a new suite of components: - `StreamUserAvatar`: Displays a single user's avatar. - `StreamUserAvatarGroup`: Displays a grid of user avatars for group channels. - `StreamUserAvatarStack`: A new component for displaying a stack of overlapping user avatars. - `StreamChannelAvatar`: Rebuilt to internally use the new avatar components. Key changes: - **Simplified Sizing**: The `constraints` parameter has been replaced with a `size` enum (e.g., `StreamAvatarSize.lg`) for standardized sizing. - **Tap Handling**: The `onTap` callback has been removed. Avatars should now be wrapped with a `GestureDetector` or `InkWell` for handling interactions. - **Parameter Cleanup**: Parameters like `borderRadius`, `selected`, `selectionColor`, and `selectionThickness` have been removed to simplify the API. Customization is now handled through theming. - **Online Indicator**: The `showOnlineStatus` parameter has been renamed to `showOnlineIndicator`. Its default is now `true` for `StreamUserAvatar` and `false` for avatars inside groups or stacks. - **`StreamGroupAvatar` is now `StreamUserAvatarGroup`**: The component has been renamed and now accepts a list of `User` objects instead of `Member` objects. - **Migration Guide**: A detailed migration guide for the new avatar components has been added under `migrations/redesign/stream_avatar.md`. - **Theming**: Integrated the new `StreamTheme` extension from `stream_core_flutter` for better theme management. * feat: Use git version for stream_core_flutter This commit updates the `stream_core_flutter` dependency to use a version from a git repository. Specifically, it points to the `feat/avatar-group-and-badge-count` branch of the `GetStream/stream-core-flutter` repository. * chore: skip smudge on CI Temporarily skip LFS smudge process on CI workflows to work around an issue with a dependency. * docs: update user avatar group/stack doc comments * chore: Update Goldens * fix: set user avatar size in thread list tile * chore: Update Goldens * docs: add theming guide for redesigned components The migration guide for the redesigned UI components is updated to include a new "Theming" section. This section explains how to use `StreamTheme` to customize the appearance of the new components, providing code examples for both light and dark modes. It also adds a "Need Help?" section directing users to open a GitHub issue if they encounter problems. * chore: pin stream_core_flutter dependency to a specific commit * feat: Sort users to show current user first In the `StreamChannelAvatar` widget, the list of members is now sorted to prioritize the current user. This ensures that in a `StreamUserAvatarGroup`, the current user's avatar is shown first when available. * chore: prevent publishing of stream_core_flutter until available on pub.dev * chore: update stream_core_flutter dependency The `stream_core_flutter` git dependency reference has been updated to `c066cb481bd8a8523e5ea52f3433ffeaeab11332` in `packages/stream_chat_flutter/pubspec.yaml`. * chore: remove GIT_LFS_SKIP_SMUDGE from workflows Removes the `GIT_LFS_SKIP_SMUDGE` environment variable and associated TODO comments from GitHub Actions workflows (`stream_flutter_workflow`, `beta_version_analyze`, `update_goldens`, `distribute_internal`, and `release_publish`). This workaround is no longer required. --------- Co-authored-by: xsahil03x <25670178+xsahil03x@users.noreply.github.com> --- analysis_options.yaml | 4 +- melos.yaml | 6 + migrations/redesign/README.md | 50 ++++ migrations/redesign/stream_avatar.md | 225 ++++++++++++++++ .../lib/src/avatars/group_avatar.dart | 176 ------------ .../lib/src/avatars/user_avatar.dart | 193 -------------- .../stream_channel_info_bottom_sheet.dart | 18 +- .../lib/src/channel/channel_header.dart | 9 +- .../lib/src/channel/channel_list_header.dart | 36 +-- .../src/channel/stream_channel_avatar.dart | 252 ------------------ .../avatar/stream_channel_avatar.dart | 117 ++++++++ .../components/avatar/stream_user_avatar.dart | 168 ++++++++++++ .../avatar/stream_user_avatar_group.dart | 80 ++++++ .../avatar/stream_user_avatar_stack.dart | 113 ++++++++ .../lib/src/dialogs/channel_info_dialog.dart | 7 +- .../lib/src/gallery/gallery_footer.dart | 4 +- .../message_input/quoted_message_widget.dart | 7 +- .../lib/src/message_widget/bottom_row.dart | 8 +- .../message_widget/thread_participants.dart | 33 +-- .../message_widget/user_avatar_transform.dart | 19 +- .../interactor/poll_options_list_view.dart | 40 +-- .../lib/src/reactions/user_reactions.dart | 18 +- .../stream_channel_grid_tile.dart | 11 +- .../stream_channel_list_tile.dart | 6 +- .../stream_message_search_list_tile.dart | 10 +- .../stream_poll_vote_list_tile.dart | 4 +- .../stream_thread_list_tile.dart | 2 +- .../stream_user_grid_tile.dart | 15 +- .../stream_user_list_tile.dart | 10 +- .../lib/src/user/user_mention_tile.dart | 6 +- .../lib/stream_chat_flutter.dart | 11 +- packages/stream_chat_flutter/pubspec.yaml | 8 + .../test/flutter_test_config.dart | 5 +- .../src/avatars/goldens/ci/group_avatar_0.png | Bin 10575 -> 2383 bytes .../src/avatars/goldens/ci/user_avatar_0.png | Bin 3684 -> 2716 bytes .../src/avatars/goldens/ci/user_avatar_1.png | Bin 3652 -> 2707 bytes .../test/src/avatars/group_avatar_test.dart | 27 +- .../test/src/channel/channel_image_test.dart | 56 +--- .../ci/channel_header_bottom_widget.png | Bin 1547 -> 1906 bytes .../stream_message_reactions_modal_dark.png | Bin 17727 -> 10996 bytes .../stream_message_reactions_modal_light.png | Bin 18262 -> 11095 bytes ..._message_reactions_modal_reversed_dark.png | Bin 17682 -> 11095 bytes ...message_reactions_modal_reversed_light.png | Bin 18190 -> 11078 bytes .../ci/stream_poll_options_dialog_dark.png | Bin 12575 -> 9936 bytes .../ci/stream_poll_options_dialog_light.png | Bin 12892 -> 9630 bytes .../ci/stream_poll_results_dialog_dark.png | Bin 16353 -> 12111 bytes .../ci/stream_poll_results_dialog_light.png | Bin 16678 -> 11145 bytes ...poll_results_dialog_with_show_all_dark.png | Bin 13101 -> 10155 bytes ...oll_results_dialog_with_show_all_light.png | Bin 13233 -> 9473 bytes .../ci/stream_poll_interactor_closed_dark.png | Bin 8335 -> 5719 bytes .../stream_poll_interactor_closed_light.png | Bin 8130 -> 5568 bytes .../ci/stream_poll_interactor_dark.png | Bin 10289 -> 7757 bytes .../ci/stream_poll_interactor_light.png | Bin 9987 -> 7522 bytes .../ci/stream_thread_list_tile_dark.png | Bin 4258 -> 3314 bytes .../ci/stream_thread_list_tile_light.png | Bin 4290 -> 3150 bytes sample_app/lib/app.dart | 10 +- sample_app/lib/pages/channel_list_page.dart | 4 +- sample_app/lib/pages/chat_info_screen.dart | 10 +- sample_app/lib/pages/choose_user_page.dart | 4 +- .../lib/pages/group_chat_details_screen.dart | 5 +- sample_app/lib/pages/group_info_screen.dart | 11 +- sample_app/lib/pages/new_chat_screen.dart | 7 +- .../lib/pages/new_group_chat_screen.dart | 7 +- .../widgets/location/location_attachment.dart | 4 +- .../location/location_detail_dialog.dart | 4 +- .../location/location_picker_dialog.dart | 5 +- .../location/location_user_marker.dart | 37 ++- .../lib/widgets/message_info_sheet.dart | 5 +- sample_app/lib/widgets/simple_map_view.dart | 15 +- 69 files changed, 944 insertions(+), 938 deletions(-) create mode 100644 migrations/redesign/README.md create mode 100644 migrations/redesign/stream_avatar.md delete mode 100644 packages/stream_chat_flutter/lib/src/avatars/group_avatar.dart delete mode 100644 packages/stream_chat_flutter/lib/src/avatars/user_avatar.dart delete mode 100644 packages/stream_chat_flutter/lib/src/channel/stream_channel_avatar.dart create mode 100644 packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart create mode 100644 packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar.dart create mode 100644 packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar_group.dart create mode 100644 packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar_stack.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index b94dec6968..94dbf883a8 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -44,7 +44,9 @@ linter: - avoid_null_checks_in_equality_operators - avoid_positional_boolean_parameters - avoid_private_typedef_functions - - avoid_redundant_argument_values + # Does not always make sense to remove them; it also makes it hard + # to notice future breaking changes. + # - avoid_redundant_argument_values - avoid_return_types_on_setters - avoid_returning_null_for_void - avoid_shadowing_type_parameters diff --git a/melos.yaml b/melos.yaml index 97a056fb83..47e5de7a21 100644 --- a/melos.yaml +++ b/melos.yaml @@ -91,6 +91,12 @@ command: stream_chat_persistence: ^10.0.0-beta.12 streaming_shared_preferences: ^2.0.0 svg_icon_widget: ^0.0.1 + # TODO: Replace with hosted version before merging PR + stream_core_flutter: + git: + url: https://github.com/GetStream/stream-core-flutter.git + ref: c066cb481bd8a8523e5ea52f3433ffeaeab11332 + path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 url_launcher: ^6.3.0 diff --git a/migrations/redesign/README.md b/migrations/redesign/README.md new file mode 100644 index 0000000000..ee9a95e8ad --- /dev/null +++ b/migrations/redesign/README.md @@ -0,0 +1,50 @@ +# Stream Chat Flutter UI Redesign Migration Guide + +This folder contains migration guides for the redesigned UI components in Stream Chat Flutter SDK. + +## Overview + +The redesigned components aim to provide: +- Simplified and consistent APIs +- Better theme integration +- Improved developer experience +- Reduced boilerplate + +Each component migration guide contains specific details about the changes and how to migrate. + +## Theming + +The redesigned components use `StreamTheme` for theming. If no `StreamTheme` is provided, a default theme is automatically created based on `Theme.of(context).brightness` (light or dark mode). + +To customize the default theming, add `StreamTheme` as a theme extension to your `MaterialApp`: + +```dart +MaterialApp( + theme: ThemeData( + extensions: [ + StreamTheme( + brightness: Brightness.light, + colorScheme: StreamColorScheme.light().copyWith( + // Customize colors... + ), + avatarTheme: const StreamAvatarThemeData( + // Customize avatar defaults... + ), + ), + ], + ), + // ... +) +``` + +You can also use the convenience factories `StreamTheme.light()` or `StreamTheme.dark()` as a starting point. + +## Components + +| Component | Migration Guide | +|-----------|-----------------| +| Stream Avatar | [stream_avatar.md](stream_avatar.md) | + +## Need Help? + +If you encounter any issues during migration or have questions, please [open an issue](https://github.com/GetStream/stream-chat-flutter/issues) on GitHub. diff --git a/migrations/redesign/stream_avatar.md b/migrations/redesign/stream_avatar.md new file mode 100644 index 0000000000..7205691ad5 --- /dev/null +++ b/migrations/redesign/stream_avatar.md @@ -0,0 +1,225 @@ +# Stream Avatar Components Migration Guide + +This guide covers the migration for the redesigned avatar components in Stream Chat Flutter SDK. + +--- + +## Table of Contents + +- [Quick Reference](#quick-reference) +- [StreamUserAvatar](#streamuseravatar) +- [StreamChannelAvatar](#streamchannelavatar) +- [StreamGroupAvatar](#streamgroupavatar) +- [StreamUserAvatarStack](#streamuseravatarstack) +- [Size Reference](#size-reference) +- [Migration Checklist](#migration-checklist) + +--- + +## Quick Reference + +| Component | Key Changes | +|-----------|-------------| +| [**StreamUserAvatar**](#streamuseravatar) | `constraints` → `size` enum, `showOnlineStatus` → `showOnlineIndicator`, `onTap` removed | +| [**StreamChannelAvatar**](#streamchannelavatar) | `constraints` → `size` enum, `onTap` and builder callbacks removed | +| [**StreamGroupAvatar**](#streamgroupavatar) | Renamed to `StreamUserAvatarGroup`, `members` → `users` | +| [**StreamUserAvatarStack**](#streamuseravatarstack) | New component for overlapping avatars | + +--- + +## StreamUserAvatar + +### Breaking Changes: + +- `constraints` parameter replaced with `size` enum (`StreamAvatarSize`) +- `showOnlineStatus` renamed to `showOnlineIndicator` +- `onTap` callback removed — wrap with `GestureDetector` or `InkWell` instead +- `borderRadius` parameter removed +- `selected`, `selectionColor`, `selectionThickness` parameters removed +- `onlineIndicatorAlignment` and `onlineIndicatorConstraints` removed + +### Migration: + +**Before:** +```dart +StreamUserAvatar( + user: user, + constraints: BoxConstraints.tight(const Size(40, 40)), + borderRadius: BorderRadius.circular(20), + showOnlineStatus: false, + onTap: (user) => print('Tapped ${user.name}'), +) +``` + +**After:** +```dart +GestureDetector( + onTap: () => print('Tapped ${user.name}'), + child: StreamUserAvatar( + size: StreamAvatarSize.lg, + user: user, + showOnlineIndicator: false, + ), +) +``` + +> **Important:** +> - Use `GestureDetector` or `InkWell` to handle tap events +> - Use `StreamAvatarSize` enum values (`.xs`, `.sm`, `.md`, `.lg`, `.xl`) instead of `BoxConstraints` +> - See [Size Reference](#size-reference) for mapping old constraints to new enum values + +--- + +## StreamChannelAvatar + +### Breaking Changes: + +- `constraints` parameter replaced with `size` enum (`StreamAvatarGroupSize`) +- `onTap` callback removed — wrap with `GestureDetector` or `InkWell` instead +- `borderRadius` parameter removed +- `selected`, `selectionColor`, `selectionThickness` parameters removed +- `ownSpaceAvatarBuilder`, `oneToOneAvatarBuilder`, `groupAvatarBuilder` callbacks removed + +### Migration: + +**Before:** +```dart +StreamChannelAvatar( + channel: channel, + constraints: BoxConstraints.tight(const Size(40, 40)), + onTap: () => print('Tapped channel'), + selected: isSelected, +) +``` + +**After:** +```dart +GestureDetector( + onTap: () => print('Tapped channel'), + child: StreamChannelAvatar( + size: StreamAvatarGroupSize.lg, + channel: channel, + ), +) +``` + +> **Important:** +> - Use `StreamAvatarGroupSize` enum values (`.lg`, `.xl`) instead of `BoxConstraints` +> - Custom avatar builders are no longer supported + +--- + +## StreamGroupAvatar + +### Breaking Changes: + +- Renamed from `StreamGroupAvatar` to `StreamUserAvatarGroup` +- `members` parameter replaced with `users` (`Iterable` instead of `List`) +- `constraints` parameter replaced with `size` enum (`StreamAvatarGroupSize`) +- `channel` parameter removed +- `onTap` callback removed — wrap with `GestureDetector` or `InkWell` instead +- `borderRadius` parameter removed +- `selected`, `selectionColor`, `selectionThickness` parameters removed + +### Migration: + +**Before:** +```dart +StreamGroupAvatar( + channel: channel, + members: otherMembers, + constraints: BoxConstraints.tight(const Size(40, 40)), + onTap: () => print('Tapped group'), +) +``` + +**After:** +```dart +GestureDetector( + onTap: () => print('Tapped group'), + child: StreamUserAvatarGroup( + size: StreamAvatarGroupSize.lg, + users: otherMembers.map((m) => m.user!), + ), +) +``` + +> **Important:** +> - Extract `User` objects from `Member` when migrating: `members.map((m) => m.user!)` +> - The component no longer requires a `channel` reference + +--- + +## StreamUserAvatarStack + +### Breaking Changes: + +- **New component** for displaying overlapping user avatars (e.g., thread participants) +- Replaces custom `Stack` + `Positioned` implementations + +### Parameters: + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `users` | `Iterable` | required | Users to display | +| `size` | `StreamAvatarStackSize?` | `.sm` | Size of avatars | +| `max` | `int` | `5` | Max avatars before overflow badge | +| `overlap` | `double` | `0.33` | Overlap fraction (0.0 - 1.0) | + +### Usage: + +```dart +StreamUserAvatarStack( + max: 3, + size: StreamAvatarStackSize.xs, + users: threadParticipants, +) +``` + +> **Important:** +> - Use this component instead of manually building overlapping avatar stacks +> - The `overlap` parameter controls how much each avatar overlaps the previous one + +--- + +## Size Reference + +### StreamAvatarSize + +| Old Constraints | New Size | Diameter | +|-----------------|----------|----------| +| `BoxConstraints.tight(Size(20, 20))` | `.xs` | 20px | +| `BoxConstraints.tight(Size(24, 24))` | `.sm` | 24px | +| `BoxConstraints.tight(Size(32, 32))` | `.md` | 32px | +| `BoxConstraints.tight(Size(40, 40))` | `.lg` | 40px | +| `BoxConstraints.tight(Size(64, 64))` | `.xl` | 64px | + +### StreamAvatarGroupSize + +| Old Constraints | New Size | Diameter | +|-----------------|----------|----------| +| `BoxConstraints.tight(Size(40, 40))` | `.lg` | 40px | +| `BoxConstraints.tight(Size(64, 64))` | `.xl` | 64px | + +### StreamAvatarStackSize + +| Old Constraints | New Size | Diameter | +|-----------------|----------|----------| +| `BoxConstraints.tight(Size(20, 20))` | `.xs` | 20px | +| `BoxConstraints.tight(Size(24, 24))` | `.sm` | 24px | + +> **Note:** +> If your old constraints don't match exactly, choose the closest available size. + +--- + +## Migration Checklist + +- [ ] Replace `StreamUserAvatar` `constraints` with `size` enum (`StreamAvatarSize`) +- [ ] Rename `showOnlineStatus` to `showOnlineIndicator` +- [ ] Move `onTap` callbacks to parent `GestureDetector` or `InkWell` widgets +- [ ] Replace `StreamGroupAvatar` with `StreamUserAvatarGroup` +- [ ] Change `members` parameter to `users` (extract `User` from `Member`) +- [ ] Replace `StreamChannelAvatar` `constraints` with `size` enum (`StreamAvatarGroupSize`) +- [ ] Remove `selected`, `selectionColor`, `selectionThickness` parameters +- [ ] Use `StreamUserAvatarStack` for overlapping avatar displays diff --git a/packages/stream_chat_flutter/lib/src/avatars/group_avatar.dart b/packages/stream_chat_flutter/lib/src/avatars/group_avatar.dart deleted file mode 100644 index 8b164f8401..0000000000 --- a/packages/stream_chat_flutter/lib/src/avatars/group_avatar.dart +++ /dev/null @@ -1,176 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// WidgetBuilder for [StreamGroupAvatar]. -typedef StreamGroupAvatarBuilder = - Widget Function( - BuildContext context, - List members, - // ignore: avoid_positional_boolean_parameters - bool isSelected, - ); - -/// {@template streamGroupAvatar} -/// Widget for constructing a group of images -/// {@endtemplate} -class StreamGroupAvatar extends StatelessWidget { - /// {@macro streamGroupAvatar} - const StreamGroupAvatar({ - super.key, - this.channel, - required this.members, - this.constraints, - this.onTap, - this.borderRadius, - this.selected = false, - this.selectionColor, - this.selectionThickness = 4, - }); - - /// The channel of the avatar - final Channel? channel; - - /// The list of members in the group whose avatars should be displayed. - final List members; - - /// Constraints on the widget - final BoxConstraints? constraints; - - /// The action to perform when the widget is tapped - final VoidCallback? onTap; - - /// If `true`, this widget should be highlighted. - /// - /// Defaults to `false`. - final bool selected; - - /// [BorderRadius] to pass to the widget - final BorderRadius? borderRadius; - - /// The color to highlight the widget with if [selected] is `true` - final Color? selectionColor; - - /// The value to use for the border thickness and padding of the - /// selected image - final double selectionThickness; - - @override - Widget build(BuildContext context) { - final channel = this.channel ?? StreamChannel.of(context).channel; - - assert(channel.state != null, 'Channel ${channel.id} is not initialized'); - - final streamChatTheme = StreamChatTheme.of(context); - final colorTheme = streamChatTheme.colorTheme; - final previewTheme = streamChatTheme.channelPreviewTheme.avatarTheme; - - Widget avatar = GestureDetector( - onTap: onTap, - child: ClipRRect( - borderRadius: borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero, - child: Container( - constraints: constraints ?? previewTheme?.constraints, - decoration: BoxDecoration(color: colorTheme.accentPrimary), - child: Flex( - direction: Axis.vertical, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Flexible( - fit: FlexFit.tight, - child: Flex( - direction: Axis.horizontal, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: members - .take(2) - .map( - (member) => Flexible( - fit: FlexFit.tight, - child: FittedBox( - fit: BoxFit.cover, - clipBehavior: Clip.antiAlias, - child: Transform.scale( - scale: 1.2, - child: BetterStreamBuilder( - stream: channel.state!.membersStream.map( - (members) => members.firstWhere( - (it) => it.userId == member.userId, - orElse: () => member, - ), - ), - initialData: member, - builder: (context, member) => StreamUserAvatar( - showOnlineStatus: false, - user: member.user!, - borderRadius: BorderRadius.zero, - ), - ), - ), - ), - ), - ) - .toList(), - ), - ), - if (members.length > 2) - Flexible( - fit: FlexFit.tight, - child: Flex( - direction: Axis.horizontal, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: members - .skip(2) - .take(2) - .map( - (member) => Flexible( - fit: FlexFit.tight, - child: FittedBox( - fit: BoxFit.cover, - clipBehavior: Clip.antiAlias, - child: Transform.scale( - scale: 1.2, - child: BetterStreamBuilder( - stream: channel.state!.membersStream.map( - (members) => members.firstWhere( - (it) => it.userId == member.userId, - orElse: () => member, - ), - ), - initialData: member, - builder: (context, member) => StreamUserAvatar( - showOnlineStatus: false, - user: member.user!, - borderRadius: BorderRadius.zero, - ), - ), - ), - ), - ), - ) - .toList(), - ), - ), - ], - ), - ), - ), - ); - - if (selected) { - avatar = ClipRRect( - borderRadius: - BorderRadius.circular(selectionThickness) + - (borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero), - child: Container( - constraints: constraints ?? previewTheme?.constraints, - color: selectionColor ?? colorTheme.accentPrimary, - child: Padding( - padding: EdgeInsets.all(selectionThickness), - child: avatar, - ), - ), - ); - } - - return avatar; - } -} diff --git a/packages/stream_chat_flutter/lib/src/avatars/user_avatar.dart b/packages/stream_chat_flutter/lib/src/avatars/user_avatar.dart deleted file mode 100644 index f5bc3e4732..0000000000 --- a/packages/stream_chat_flutter/lib/src/avatars/user_avatar.dart +++ /dev/null @@ -1,193 +0,0 @@ -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// WidgetBuilder for [StreamUserAvatar]. -typedef StreamUserAvatarBuilder = - Widget Function( - BuildContext context, - User user, - // ignore: avoid_positional_boolean_parameters - bool isSelected, - ); - -/// {@template streamUserAvatar} -/// Displays a user's avatar. -/// {@endtemplate} -class StreamUserAvatar extends StatelessWidget { - /// {@macro streamUserAvatar} - const StreamUserAvatar({ - super.key, - required this.user, - this.constraints, - this.onlineIndicatorConstraints, - this.onTap, - this.onLongPress, - this.showOnlineStatus = true, - this.borderRadius, - this.onlineIndicatorAlignment = Alignment.topRight, - this.selected = false, - this.selectionColor, - this.selectionThickness = 4, - this.placeholder, - }); - - /// User whose avatar is to be displayed - final User user; - - /// Alignment of the online indicator - /// - /// Defaults to `Alignment.topRight` - final Alignment onlineIndicatorAlignment; - - /// Sizing constraints of the avatar - final BoxConstraints? constraints; - - /// [BorderRadius] of the image - final BorderRadius? borderRadius; - - /// Sizing constraints of the online indicator - final BoxConstraints? onlineIndicatorConstraints; - - /// {@macro onUserAvatarTap} - final OnUserAvatarPress? onTap; - - /// {@macro onUserAvatarTap} - final OnUserAvatarPress? onLongPress; - - /// Flag for showing online status - /// - /// Defaults to `true` - final bool showOnlineStatus; - - /// Flag for if avatar is selected - /// - /// Defaults to `false` - final bool selected; - - /// Color of selection - final Color? selectionColor; - - /// Selection thickness around the avatar - /// - /// Defaults to `4` - final double selectionThickness; - - /// {@macro placeholderUserImage} - final PlaceholderUserImage? placeholder; - - @override - Widget build(BuildContext context) { - final streamChatTheme = StreamChatTheme.of(context); - final colorTheme = streamChatTheme.colorTheme; - final avatarTheme = streamChatTheme.ownMessageTheme.avatarTheme; - final streamChatConfig = StreamChatConfiguration.of(context); - - final effectivePlaceholder = switch (placeholder) { - final placeholder? => placeholder, - _ => streamChatConfig.placeholderUserImage, - }; - - final effectiveBorderRadius = borderRadius ?? avatarTheme?.borderRadius; - - final backupGradientAvatar = ClipRRect( - borderRadius: effectiveBorderRadius ?? BorderRadius.zero, - child: streamChatConfig.defaultUserImage(context, user), - ); - - Widget avatar = FittedBox( - fit: BoxFit.cover, - child: Container( - constraints: constraints ?? avatarTheme?.constraints, - child: LayoutBuilder( - builder: (context, constraints) { - final imageUrl = user.image; - if (imageUrl == null || imageUrl.isEmpty) { - return backupGradientAvatar; - } - - // Calculate optimal thumbnail size for the avatar - final devicePixelRatio = MediaQuery.devicePixelRatioOf(context); - final thumbnailSize = constraints.biggest * devicePixelRatio; - - int? cacheWidth, cacheHeight; - if (thumbnailSize.isFinite && !thumbnailSize.isEmpty) { - cacheWidth = thumbnailSize.width.round(); - cacheHeight = thumbnailSize.height.round(); - } - - return CachedNetworkImage( - fit: BoxFit.cover, - filterQuality: FilterQuality.high, - imageUrl: imageUrl, - errorWidget: (_, __, ___) => backupGradientAvatar, - placeholder: switch (effectivePlaceholder) { - final holder? => (context, __) => holder(context, user), - _ => null, - }, - imageBuilder: (context, imageProvider) => DecoratedBox( - decoration: BoxDecoration( - borderRadius: effectiveBorderRadius, - image: DecorationImage( - fit: BoxFit.cover, - image: ResizeImage( - imageProvider, - width: cacheWidth, - height: cacheHeight, - ), - ), - ), - ), - ); - }, - ), - ), - ); - - if (selected) { - avatar = ClipRRect( - borderRadius: (effectiveBorderRadius ?? BorderRadius.zero) + BorderRadius.circular(selectionThickness), - child: Container( - constraints: constraints ?? avatarTheme?.constraints, - color: selectionColor ?? colorTheme.accentPrimary, - child: Padding( - padding: EdgeInsets.all(selectionThickness), - child: avatar, - ), - ), - ); - } - return GestureDetector( - onTap: onTap != null ? () => onTap!(user) : null, - onLongPress: onLongPress != null ? () => onLongPress!(user) : null, - child: Stack( - children: [ - avatar, - if (showOnlineStatus && user.online) - Positioned.fill( - child: Align( - alignment: onlineIndicatorAlignment, - child: Material( - type: MaterialType.circle, - color: colorTheme.barsBg, - child: Container( - margin: const EdgeInsets.all(2), - constraints: - onlineIndicatorConstraints ?? - const BoxConstraints.tightFor( - width: 8, - height: 8, - ), - child: Material( - shape: const CircleBorder(), - color: colorTheme.accentInfo, - ), - ), - ), - ), - ), - ], - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart b/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart index 72ef51b101..778d6b3be5 100644 --- a/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart @@ -94,17 +94,15 @@ class StreamChannelInfoBottomSheet extends StatelessWidget { mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamUserAvatar( - user: user, - constraints: const BoxConstraints.tightFor( - height: 64, - width: 64, + GestureDetector( + onTap: switch (onMemberTap) { + final onTap? => () => onTap(member), + _ => null, + }, + child: StreamUserAvatar( + size: .xl, + user: user, ), - borderRadius: BorderRadius.circular(32), - onlineIndicatorConstraints: BoxConstraints.tight( - const Size(12, 12), - ), - onTap: onMemberTap != null ? (_) => onMemberTap!(member) : null, ), const SizedBox(height: 6), Text( diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_header.dart b/packages/stream_chat_flutter/lib/src/channel/channel_header.dart index 679b102c78..7ada230edc 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_header.dart @@ -189,11 +189,12 @@ class StreamChannelHeader extends StatelessWidget implements PreferredSizeWidget Padding( padding: const EdgeInsets.only(right: 10), child: Center( - child: StreamChannelAvatar( - channel: channel, - borderRadius: channelHeaderTheme.avatarTheme?.borderRadius, - constraints: channelHeaderTheme.avatarTheme?.constraints, + child: GestureDetector( onTap: onImageTap, + child: StreamChannelAvatar( + size: .lg, + channel: channel, + ), ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart b/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart index ddbc9105ee..a375162297 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart @@ -137,24 +137,26 @@ class StreamChannelListHeader extends StatelessWidget implements PreferredSizeWi elevation: elevation, backgroundColor: backgroundColor ?? channelListHeaderThemeData.color, centerTitle: centerTitle, - leading: - leading ?? - Center( - child: user != null - ? StreamUserAvatar( - user: user, - showOnlineStatus: false, - onTap: - onUserAvatarTap ?? - (_) { - preNavigationCallback?.call(); - Scaffold.of(context).openDrawer(); - }, - borderRadius: channelListHeaderThemeData.avatarTheme?.borderRadius, - constraints: channelListHeaderThemeData.avatarTheme?.constraints, - ) - : const Empty(), + leading: switch ((leading, user)) { + (final leading?, _) => leading, + (_, final user?) => Center( + child: GestureDetector( + onTap: switch (onUserAvatarTap) { + final onTap? => () => onTap(user), + _ => () { + preNavigationCallback?.call(); + Scaffold.of(context).openDrawer(); + }, + }, + child: StreamUserAvatar( + size: .lg, + user: user, + showOnlineIndicator: false, + ), ), + ), + _ => const Empty(), + }, actions: actions ?? [ diff --git a/packages/stream_chat_flutter/lib/src/channel/stream_channel_avatar.dart b/packages/stream_chat_flutter/lib/src/channel/stream_channel_avatar.dart deleted file mode 100644 index 59ba01a8c2..0000000000 --- a/packages/stream_chat_flutter/lib/src/channel/stream_channel_avatar.dart +++ /dev/null @@ -1,252 +0,0 @@ -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_image.png) -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_image_paint.png) -/// -/// It shows the current [Channel] image. -/// -/// ```dart -/// class MyApp extends StatelessWidget { -/// final StreamChatClient client; -/// final Channel channel; -/// -/// MyApp(this.client, this.channel); -/// -/// @override -/// Widget build(BuildContext context) { -/// return MaterialApp( -/// debugShowCheckedModeBanner: false, -/// home: StreamChat( -/// client: client, -/// child: StreamChannel( -/// channel: channel, -/// child: Center( -/// child: StreamChannelAvatar( -/// channel: channel, -/// ), -/// ), -/// ), -/// ), -/// ); -/// } -/// } -/// ``` -/// -/// The widget uses a [StreamBuilder] to render the channel information -/// image as soon as it updates. -/// -/// By default the widget radius size is 40x40 pixels. -/// Set the property [constraints] to set a custom dimension. -/// -/// The widget renders the ui based on the first ancestor of type -/// [StreamChatTheme]. -/// Modify it to change the widget appearance. -class StreamChannelAvatar extends StatelessWidget { - /// Instantiate a new ChannelImage - StreamChannelAvatar({ - super.key, - required this.channel, - this.constraints, - this.onTap, - this.borderRadius, - this.selected = false, - this.selectionColor, - this.selectionThickness = 4, - this.ownSpaceAvatarBuilder, - this.oneToOneAvatarBuilder, - this.groupAvatarBuilder, - }) : assert( - channel.state != null, - 'Channel ${channel.id} is not initialized', - ); - - /// [BorderRadius] to display the widget - final BorderRadius? borderRadius; - - /// The channel to show the image of - final Channel channel; - - /// The diameter of the image - final BoxConstraints? constraints; - - /// The function called when the image is tapped - final VoidCallback? onTap; - - /// If image is selected - final bool selected; - - /// Selection color for image - final Color? selectionColor; - - /// Thickness of selection image - final double selectionThickness; - - /// Builder to create avatar for own space channel. - /// - /// Defaults to [StreamUserAvatar]. - final StreamUserAvatarBuilder? ownSpaceAvatarBuilder; - - /// Builder to create avatar for one to one channel. - /// - /// Defaults to [StreamUserAvatar]. - final StreamUserAvatarBuilder? oneToOneAvatarBuilder; - - /// Builder to create avatar for group channel. - /// - /// Defaults to [StreamGroupAvatar]. - final StreamGroupAvatarBuilder? groupAvatarBuilder; - - @override - Widget build(BuildContext context) { - final client = channel.client.state; - - final chatThemeData = StreamChatTheme.of(context); - final colorTheme = chatThemeData.colorTheme; - final previewTheme = chatThemeData.channelPreviewTheme.avatarTheme; - - final fallbackWidget = Center( - child: Text( - channel.name?.characters.firstOrNull ?? '', - style: TextStyle( - color: colorTheme.barsBg, - fontWeight: FontWeight.bold, - ), - ), - ); - - return BetterStreamBuilder( - stream: channel.imageStream, - initialData: channel.image, - builder: (context, channelImage) { - Widget child = ClipRRect( - borderRadius: borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero, - child: Container( - constraints: constraints ?? previewTheme?.constraints, - decoration: BoxDecoration(color: colorTheme.accentPrimary), - child: InkWell( - onTap: onTap, - child: LayoutBuilder( - builder: (context, constraints) { - if (channelImage.isEmpty) return fallbackWidget; - - // Calculate optimal thumbnail size for the avatar - final devicePixel = MediaQuery.devicePixelRatioOf(context); - final thumbnailSize = constraints.biggest * devicePixel; - - int? cacheWidth, cacheHeight; - if (thumbnailSize.isFinite && !thumbnailSize.isEmpty) { - cacheWidth = thumbnailSize.width.round(); - cacheHeight = thumbnailSize.height.round(); - } - - return CachedNetworkImage( - imageUrl: channelImage, - memCacheWidth: cacheWidth, - memCacheHeight: cacheHeight, - errorWidget: (_, __, ___) => fallbackWidget, - fit: BoxFit.cover, - ); - }, - ), - ), - ), - ); - - if (selected) { - child = ClipRRect( - key: const Key('selectedImage'), - borderRadius: - BorderRadius.circular(selectionThickness) + - (borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero), - child: Container( - constraints: constraints ?? previewTheme?.constraints, - color: selectionColor ?? colorTheme.accentPrimary, - child: Padding( - padding: EdgeInsets.all(selectionThickness), - child: child, - ), - ), - ); - } - return child; - }, - noDataBuilder: (context) { - final currentUser = client.currentUser!; - final otherMembers = channel.state!.members.where((it) => it.userId != currentUser.id).toList(growable: false); - - // our own space, no other members - if (otherMembers.isEmpty) { - return BetterStreamBuilder( - stream: client.currentUserStream.map((it) => it!), - initialData: currentUser, - builder: (context, user) { - final ownSpaceBuilder = ownSpaceAvatarBuilder; - if (ownSpaceBuilder != null) { - return ownSpaceBuilder(context, user, selected); - } - - return StreamUserAvatar( - borderRadius: borderRadius ?? previewTheme?.borderRadius, - user: user, - constraints: constraints ?? previewTheme?.constraints, - onTap: onTap != null ? (_) => onTap!() : null, - selected: selected, - selectionColor: selectionColor ?? colorTheme.accentPrimary, - selectionThickness: selectionThickness, - ); - }, - ); - } - - // 1-1 Conversation - if (otherMembers.length == 1) { - final member = otherMembers.first; - return BetterStreamBuilder( - stream: channel.state!.membersStream.map( - (members) => members.firstWhere( - (it) => it.userId == member.userId, - orElse: () => member, - ), - ), - initialData: member, - builder: (context, member) { - final oneToOneBuilder = oneToOneAvatarBuilder; - if (oneToOneBuilder != null) { - return oneToOneBuilder(context, member.user!, selected); - } - - return StreamUserAvatar( - borderRadius: borderRadius ?? previewTheme?.borderRadius, - user: member.user!, - constraints: constraints ?? previewTheme?.constraints, - onTap: onTap != null ? (_) => onTap!() : null, - selected: selected, - selectionColor: selectionColor ?? colorTheme.accentPrimary, - selectionThickness: selectionThickness, - ); - }, - ); - } - - final groupBuilder = groupAvatarBuilder; - if (groupBuilder != null) { - return groupBuilder(context, otherMembers, selected); - } - - // Group conversation - return StreamGroupAvatar( - channel: channel, - members: otherMembers, - borderRadius: borderRadius ?? previewTheme?.borderRadius, - constraints: constraints ?? previewTheme?.constraints, - onTap: onTap, - selected: selected, - selectionColor: selectionColor ?? colorTheme.accentPrimary, - selectionThickness: selectionThickness, - ); - }, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart b/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart new file mode 100644 index 0000000000..b1c4c592e7 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart @@ -0,0 +1,117 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar.dart'; +import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar_group.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// A circular avatar component for displaying a channel's image. +/// +/// [StreamChannelAvatar] displays a channel's image or an appropriate fallback +/// based on the channel type. It supports channel images, user avatars for +/// 1-1 conversations, and group avatars for multi-member channels. +/// +/// The avatar automatically handles: +/// - Reactive updates when channel image changes via [Channel.imageStream] +/// - Fallback to member avatars when no channel image is set +/// - Deterministic color assignment for member avatars +/// +/// {@tool snippet} +/// +/// Basic usage with a channel: +/// +/// ```dart +/// StreamChannelAvatar(channel: channel) +/// ``` +/// {@end-tool} +/// +/// {@tool snippet} +/// +/// With custom size: +/// +/// ```dart +/// StreamChannelAvatar( +/// channel: channel, +/// size: StreamAvatarGroupSize.xl, +/// ) +/// ``` +/// {@end-tool} +/// +/// ## Theming +/// +/// [StreamChannelAvatar] uses [StreamAvatarThemeData] for default styling. +/// Member avatars within the channel avatar use deterministic colors from +/// [StreamColorScheme.avatarPalette]. +/// +/// See also: +/// +/// * [StreamAvatarGroupSize], which defines the available size variants. +/// * [StreamAvatarThemeData], which provides theme-level customization. +/// * [StreamUserAvatar], which is used to display individual member avatars. +class StreamChannelAvatar extends StatelessWidget { + /// Creates a Stream channel avatar. + const StreamChannelAvatar({ + super.key, + this.size, + required this.channel, + }); + + /// The channel whose avatar is displayed. + final Channel channel; + + /// The size of the channel avatar. + /// + /// If null, defaults to [StreamAvatarGroupSize.lg]. + final StreamAvatarGroupSize? size; + + @override + Widget build(BuildContext context) { + assert(channel.state != null, 'Channel ${channel.id} is not initialized'); + + final effectiveSize = size ?? StreamAvatarGroupSize.lg; + + return BetterStreamBuilder( + stream: channel.imageStream, + initialData: channel.image, + builder: (context, channelImage) => StreamAvatar( + imageUrl: channelImage, + size: _avatarSizeForAvatarGroupSize(effectiveSize), + placeholder: (_) => const _StreamChannelAvatarPlaceholder(), + ), + noDataBuilder: (context) => BetterStreamBuilder( + stream: channel.state!.membersStream, + initialData: channel.state!.members, + builder: (context, members) { + final users = members.map((it) => it.user!); + final currentUserId = channel.client.state.currentUser?.id; + + return StreamUserAvatarGroup( + size: effectiveSize, + // Sort users by current user first. + users: users.sortedBy((it) => it.id == currentUserId ? 0 : 1), + ); + }, + ), + ); + } + + // Maps [StreamAvatarGroupSize] to corresponding [StreamAvatarSize]. + // + // Used when displaying a single channel image avatar. + StreamAvatarSize _avatarSizeForAvatarGroupSize( + StreamAvatarGroupSize size, + ) => switch (size) { + .lg => StreamAvatarSize.lg, + .xl => StreamAvatarSize.xl, + }; +} + +// Placeholder widget for [StreamChannelAvatar]. +// +// Displays a team icon as a fallback when the channel image fails to load. +class _StreamChannelAvatarPlaceholder extends StatelessWidget { + const _StreamChannelAvatarPlaceholder(); + + @override + Widget build(BuildContext context) => Icon(context.streamIcons.team); +} diff --git a/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar.dart b/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar.dart new file mode 100644 index 0000000000..6190e0e815 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar.dart @@ -0,0 +1,168 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/utils/extensions.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// A circular avatar component for displaying a user's profile. +/// +/// [StreamUserAvatar] displays a user's profile image or initials placeholder +/// when no image is available. It supports multiple sizes, deterministic +/// color assignment, and an optional online status indicator. +/// +/// The avatar automatically handles: +/// - Displaying the user's profile image when available +/// - Showing user initials as a placeholder when no image exists +/// - Deterministic color assignment based on the user's ID +/// - Online status indicator positioning +/// +/// {@tool snippet} +/// +/// Basic usage with a user: +/// +/// ```dart +/// StreamUserAvatar(user: currentUser) +/// ``` +/// {@end-tool} +/// +/// {@tool snippet} +/// +/// With online indicator and custom size: +/// +/// ```dart +/// StreamUserAvatar( +/// user: currentUser, +/// size: StreamAvatarSize.lg, +/// showOnlineIndicator: true, +/// ) +/// ``` +/// {@end-tool} +/// +/// {@tool snippet} +/// +/// With border for selection states: +/// +/// ```dart +/// StreamUserAvatar( +/// user: selectedUser, +/// showBorder: true, +/// ) +/// ``` +/// {@end-tool} +/// +/// ## Theming +/// +/// [StreamUserAvatar] uses [StreamAvatarThemeData] for default styling. Colors +/// are automatically assigned based on the user's ID hash, selecting from +/// [StreamColorScheme.avatarPalette] for consistent user-specific colors. +/// +/// See also: +/// +/// * [StreamAvatarSize], which defines the available size variants. +/// * [StreamAvatarThemeData], which provides theme-level customization. +/// * [StreamColorScheme.avatarPalette], which provides colors for user avatars. +class StreamUserAvatar extends StatelessWidget { + /// Creates a Stream user avatar. + const StreamUserAvatar({ + super.key, + this.size, + required this.user, + this.showBorder = true, + this.showOnlineIndicator = true, + }); + + /// The user whose avatar is displayed. + final User user; + + /// Whether to show a border around the avatar. + /// + /// Defaults to true. The border style is determined by + /// [StreamAvatarThemeData.border]. + final bool showBorder; + + /// Whether to show the online status indicator. + /// + /// Defaults to true. + final bool showOnlineIndicator; + + /// The size of the avatar. + /// + /// If null, uses [StreamAvatarThemeData.size], or falls back to + /// [StreamAvatarSize.lg]. + final StreamAvatarSize? size; + + @override + Widget build(BuildContext context) { + final avatarTheme = context.streamAvatarTheme; + final colorScheme = context.streamColorScheme; + final avatarPalette = colorScheme.avatarPalette; + + final userHash = user.id.hashCode; // Ensure deterministic colors. + final colorPair = avatarPalette[userHash % avatarPalette.length]; + + final effectiveSize = size ?? avatarTheme.size ?? .lg; + final effectiveBackgroundColor = avatarTheme.backgroundColor ?? colorPair.backgroundColor; + final effectiveForegroundColor = avatarTheme.foregroundColor ?? colorPair.foregroundColor; + + final userAvatar = StreamAvatar( + size: effectiveSize, + imageUrl: user.image, + showBorder: showBorder, + backgroundColor: effectiveBackgroundColor, + foregroundColor: effectiveForegroundColor, + placeholder: (_) => _StreamUserAvatarPlaceholder(user: user, size: effectiveSize), + ); + + if (showOnlineIndicator) { + final indicatorSize = _indicatorSizeForAvatarSize(effectiveSize); + + return StreamOnlineIndicator( + size: indicatorSize, + isOnline: user.online, + alignment: AlignmentDirectional.topEnd, + child: userAvatar, + ); + } + + return userAvatar; + } + + // Maps [StreamAvatarSize] to corresponding [StreamOnlineIndicatorSize]. + // + // Ensures the online indicator scales appropriately with the avatar size. + StreamOnlineIndicatorSize _indicatorSizeForAvatarSize( + StreamAvatarSize size, + ) => switch (size) { + .xs || .sm => StreamOnlineIndicatorSize.sm, + .md => StreamOnlineIndicatorSize.md, + .lg => StreamOnlineIndicatorSize.lg, + .xl => StreamOnlineIndicatorSize.xl, + }; +} + +// Placeholder widget for [StreamUserAvatar]. +// +// Displays user initials or a fallback person icon when no name is available. +// Shows full initials (up to 2 characters) for medium and larger sizes, +// and only the first initial for extra-small and small sizes. +class _StreamUserAvatarPlaceholder extends StatelessWidget { + const _StreamUserAvatarPlaceholder({ + required this.user, + required this.size, + }); + + final User user; + final StreamAvatarSize size; + + @override + Widget build(BuildContext context) { + final userInitials = user.name.initials; + if (userInitials != null && userInitials.isNotEmpty) { + return switch (size) { + .md || .lg || .xl => Text(userInitials), + .xs || .sm => Text(userInitials.characters.first), + }; + } + + return Icon(context.streamIcons.people); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar_group.dart b/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar_group.dart new file mode 100644 index 0000000000..5b557f4e6b --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar_group.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// A widget that displays multiple user avatars in a grid layout. +/// +/// [StreamUserAvatarGroup] arranges user avatars in a 2x2 grid pattern, +/// typically used for displaying group channel participants. It supports +/// two sizes and automatically handles overflow with a badge indicator. +/// +/// The group automatically handles: +/// - Grid layout for up to 4 user avatars +/// - Overflow indicator showing remaining count for additional users +/// - Deterministic color assignment for each user avatar +/// - Consistent sizing across all child avatars +/// +/// {@tool snippet} +/// +/// Basic usage with a list of users: +/// +/// ```dart +/// StreamUserAvatarGroup(users: groupMembers) +/// ``` +/// {@end-tool} +/// +/// {@tool snippet} +/// +/// With custom size: +/// +/// ```dart +/// StreamUserAvatarGroup( +/// users: groupMembers, +/// size: StreamAvatarGroupSize.xl, +/// ) +/// ``` +/// {@end-tool} +/// +/// ## Theming +/// +/// [StreamUserAvatarGroup] uses [StreamAvatarThemeData] for styling the child +/// avatars. Individual user avatars use deterministic colors from +/// [StreamColorScheme.avatarPalette]. +/// +/// See also: +/// +/// * [StreamAvatarGroupSize], which defines the available size variants. +/// * [StreamUserAvatar], which is used to display individual user avatars. +/// * [StreamAvatarGroup], the underlying group component. +class StreamUserAvatarGroup extends StatelessWidget { + /// Creates a Stream user avatar group. + /// + /// If [users] is empty, returns an empty [SizedBox]. + const StreamUserAvatarGroup({ + super.key, + required this.users, + this.size, + }); + + /// The list of users whose avatars are displayed. + final Iterable users; + + /// The size of the avatar group. + /// + /// If null, defaults to [StreamAvatarGroupSize.lg]. + final StreamAvatarGroupSize? size; + + @override + Widget build(BuildContext context) { + return StreamAvatarGroup( + size: size, + children: users.map( + (user) => StreamUserAvatar( + user: user, + showOnlineIndicator: false, + ), + ), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar_stack.dart b/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar_stack.dart new file mode 100644 index 0000000000..12b8ee48f9 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar_stack.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// A widget that displays a stack of user avatars with overlap. +/// +/// [StreamUserAvatarStack] displays multiple user avatars in a horizontal +/// stack, with each avatar partially overlapping the previous one. This is +/// useful for showing participants in a conversation, group members, or +/// any collection of users in a compact space. +/// +/// The stack automatically handles: +/// - Displaying user avatars with deterministic colors +/// - Overflow handling with a badge showing remaining count +/// - Customizable overlap and maximum visible avatars +/// +/// {@tool snippet} +/// +/// Basic usage with a list of users: +/// +/// ```dart +/// StreamUserAvatarStack(users: participants) +/// ``` +/// {@end-tool} +/// +/// {@tool snippet} +/// +/// With custom max and size: +/// +/// ```dart +/// StreamUserAvatarStack( +/// users: participants, +/// max: 3, +/// size: StreamAvatarStackSize.xs, +/// ) +/// ``` +/// {@end-tool} +/// +/// {@tool snippet} +/// +/// With custom overlap: +/// +/// ```dart +/// StreamUserAvatarStack( +/// users: participants, +/// overlap: 0.5, // 50% overlap +/// ) +/// ``` +/// {@end-tool} +/// +/// ## Theming +/// +/// [StreamUserAvatarStack] uses [StreamAvatarThemeData] for default styling. +/// Individual user avatars use deterministic colors from +/// [StreamColorScheme.avatarPalette]. +/// +/// See also: +/// +/// * [StreamAvatarStackSize], which defines the available size variants. +/// * [StreamUserAvatar], which is used to display individual user avatars. +/// * [StreamAvatarStack], the underlying stack component. +class StreamUserAvatarStack extends StatelessWidget { + /// Creates a Stream user avatar stack. + /// + /// If [users] is empty, returns an empty [SizedBox]. + /// The [max] must be at least 2. + const StreamUserAvatarStack({ + super.key, + required this.users, + this.size, + this.overlap = 0.33, + this.max = 5, + }) : assert(max >= 2, 'max must be at least 2'); + + /// The list of users whose avatars are displayed. + final Iterable users; + + /// The size of the avatar stack. + /// + /// If null, defaults to [StreamAvatarStackSize.sm]. + final StreamAvatarStackSize? size; + + /// How much each avatar overlaps the previous one, as a fraction of size. + /// + /// - `0.0`: No overlap (side by side) + /// - `0.33`: 33% overlap (default) + /// - `1.0`: Fully stacked + final double overlap; + + /// Maximum number of avatars to display before showing overflow badge. + /// + /// When [users] exceeds this value, displays [max] avatars followed + /// by a badge showing the overflow count (e.g., "+2"). + /// + /// Must be at least 2. Defaults to 5. + final int max; + + @override + Widget build(BuildContext context) { + return StreamAvatarStack( + max: max, + size: size, + overlap: overlap, + children: users.map( + (user) => StreamUserAvatar( + user: user, + showOnlineIndicator: false, + ), + ), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/dialogs/channel_info_dialog.dart b/packages/stream_chat_flutter/lib/src/dialogs/channel_info_dialog.dart index 3f1a061ebb..dc1991b74c 100644 --- a/packages/stream_chat_flutter/lib/src/dialogs/channel_info_dialog.dart +++ b/packages/stream_chat_flutter/lib/src/dialogs/channel_info_dialog.dart @@ -48,17 +48,12 @@ class ChannelInfoDialog extends StatelessWidget { Column( children: [ StreamUserAvatar( + size: .xl, user: members .firstWhere( (e) => e.user?.id != userAsMember.user?.id, ) .user!, - constraints: const BoxConstraints.tightFor( - height: 64, - width: 64, - ), - borderRadius: BorderRadius.circular(32), - onlineIndicatorConstraints: BoxConstraints.tight(const Size(12, 12)), ), const SizedBox(height: 6), Text( diff --git a/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart b/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart index 73fff05323..72e96f923d 100644 --- a/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart +++ b/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart @@ -261,9 +261,9 @@ class _StreamGalleryFooterState extends State { ], ), child: StreamUserAvatar( + size: .sm, user: message.user!, - constraints: BoxConstraints.tight(const Size(24, 24)), - showOnlineStatus: false, + showOnlineIndicator: false, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart b/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart index 20277f085b..fafa0cffed 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart @@ -68,12 +68,9 @@ class StreamQuotedMessageWidget extends StatelessWidget { const SizedBox(width: 8), if (message.user != null) StreamUserAvatar( + size: .sm, user: message.user!, - constraints: const BoxConstraints.tightFor( - height: 24, - width: 24, - ), - showOnlineStatus: false, + showOnlineIndicator: false, ), ]; return Padding( diff --git a/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart b/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart index 05a7b70b1c..42d8fe05d0 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart @@ -246,12 +246,8 @@ class BottomRow extends StatelessWidget { ), if (showInChannel || showThreadReplyIndicator) ...[ if (showThreadParticipants) - SizedBox.fromSize( - size: Size((threadParticipants!.length * 8.0) + 8, 16), - child: ThreadParticipants( - threadParticipants: threadParticipants, - streamChatTheme: streamChatTheme, - ), + ThreadParticipants( + threadParticipants: threadParticipants!, ), MouseRegion( cursor: SystemMouseCursors.click, diff --git a/packages/stream_chat_flutter/lib/src/message_widget/thread_participants.dart b/packages/stream_chat_flutter/lib/src/message_widget/thread_participants.dart index 5068e2931e..488224c399 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/thread_participants.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/thread_participants.dart @@ -10,40 +10,19 @@ class ThreadParticipants extends StatelessWidget { /// {@macro threadParticipants} const ThreadParticipants({ super.key, - required StreamChatThemeData streamChatTheme, required this.threadParticipants, - }) : _streamChatTheme = streamChatTheme; - - /// {@macro streamChatThemeData} - final StreamChatThemeData _streamChatTheme; + }); /// The users participating in the thread. final Iterable threadParticipants; @override Widget build(BuildContext context) { - var padding = 0.0; - return Stack( - children: threadParticipants.map((user) { - padding += 8.0; - return Positioned( - right: padding - 8, - bottom: 0, - top: 0, - child: Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: _streamChatTheme.colorTheme.barsBg, - ), - padding: const EdgeInsets.all(1), - child: StreamUserAvatar( - user: user, - constraints: BoxConstraints.tight(const Size.fromRadius(7)), - showOnlineStatus: false, - ), - ), - ); - }).toList(), + // TODO(redesign): Old design used 14px diameter avatars, but .xs is 20px. + return StreamUserAvatarStack( + max: 3, + size: .xs, + users: threadParticipants, ); } } diff --git a/packages/stream_chat_flutter/lib/src/message_widget/user_avatar_transform.dart b/packages/stream_chat_flutter/lib/src/message_widget/user_avatar_transform.dart index a215a442fb..7893572f9a 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/user_avatar_transform.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/user_avatar_transform.dart @@ -39,15 +39,20 @@ class UserAvatarTransform extends StatelessWidget { 0, translateUserAvatar ? (messageTheme.avatarTheme?.constraints.maxHeight ?? 40) / 2 : 0, ), - child: - userAvatarBuilder?.call(context, message.user!) ?? - StreamUserAvatar( + child: switch (userAvatarBuilder) { + final builder? => builder(context, message.user!), + _ => GestureDetector( + onTap: switch (onUserAvatarTap) { + final onTap? => () => onTap(message.user!), + _ => null, + }, + child: StreamUserAvatar( + size: .md, user: message.user!, - onTap: onUserAvatarTap, - constraints: messageTheme.avatarTheme!.constraints, - borderRadius: messageTheme.avatarTheme!.borderRadius, - showOnlineStatus: false, + showOnlineIndicator: false, ), + ), + }, ); } } diff --git a/packages/stream_chat_flutter/lib/src/poll/interactor/poll_options_list_view.dart b/packages/stream_chat_flutter/lib/src/poll/interactor/poll_options_list_view.dart index 6bb6ccd03b..2f8510d914 100644 --- a/packages/stream_chat_flutter/lib/src/poll/interactor/poll_options_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/poll/interactor/poll_options_list_view.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/avatars/user_avatar.dart'; +import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar_stack.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/theme/poll_interactor_theme.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// {@template pollOptionsListView} @@ -247,42 +246,7 @@ class OptionVoters extends StatelessWidget { Widget build(BuildContext context) { if (voters.isEmpty) return const Empty(); - final theme = StreamChatTheme.of(context); - - final diameter = radius * 2; - final width = diameter + (voters.length * diameter * overlap); - - var overlapPadding = 0.0; - - return SizedBox.fromSize( - size: Size(width, diameter), - child: Stack( - children: [ - ...voters.map( - (user) { - overlapPadding += diameter * overlap; - return Positioned( - right: overlapPadding - (diameter * overlap), - bottom: 0, - top: 0, - child: Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: theme.colorTheme.barsBg, - ), - padding: const EdgeInsets.all(1), - child: StreamUserAvatar( - user: user, - constraints: BoxConstraints.tight(Size.fromRadius(radius)), - showOnlineStatus: false, - ), - ), - ); - }, - ), - ], - ), - ); + return StreamUserAvatarStack(size: .xs, users: voters, overlap: overlap); } } diff --git a/packages/stream_chat_flutter/lib/src/reactions/user_reactions.dart b/packages/stream_chat_flutter/lib/src/reactions/user_reactions.dart index 68065e8afc..7d3244508d 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/user_reactions.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/user_reactions.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/avatars/user_avatar.dart'; +import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/misc/reaction_icon.dart'; import 'package:stream_chat_flutter/src/reactions/indicator/reaction_indicator_icon_list.dart'; @@ -115,12 +115,16 @@ class _UserReactionItem extends StatelessWidget { Stack( clipBehavior: Clip.none, children: [ - StreamUserAvatar( - onTap: onTap, - user: reactionUser, - showOnlineStatus: false, - borderRadius: BorderRadius.circular(32), - constraints: const BoxConstraints.tightFor(height: 64, width: 64), + GestureDetector( + onTap: switch (onTap) { + final onTap? => () => onTap(reactionUser), + _ => null, + }, + child: StreamUserAvatar( + size: .xl, + user: reactionUser, + showOnlineIndicator: false, + ), ), PositionedDirectional( bottom: 8, diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart index f5fe3a82c5..fae5ad9bca 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart @@ -59,16 +59,7 @@ class StreamChannelGridTile extends StatelessWidget { Widget build(BuildContext context) { final channelPreviewTheme = StreamChannelPreviewTheme.of(context); - final child = - this.child ?? - StreamChannelAvatar( - channel: channel, - borderRadius: BorderRadius.circular(32), - constraints: const BoxConstraints.tightFor( - height: 64, - width: 64, - ), - ); + final child = this.child ?? StreamChannelAvatar(size: .xl, channel: channel); final footer = this.footer ?? diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart index 6c12bd0421..07dd751271 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart @@ -151,11 +151,7 @@ class StreamChannelListTile extends StatelessWidget { final streamChatTheme = StreamChatTheme.of(context); final streamChat = StreamChat.of(context); - final leading = - this.leading ?? - StreamChannelAvatar( - channel: channel, - ); + final leading = this.leading ?? StreamChannelAvatar(channel: channel); final title = this.title ?? diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_tile.dart index ea31da2c12..3a9be001a3 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_tile.dart @@ -111,15 +111,7 @@ class StreamMessageSearchListTile extends StatelessWidget { final user = message.user!; final channelPreviewTheme = StreamChannelPreviewTheme.of(context); - final leading = - this.leading ?? - StreamUserAvatar( - user: user, - constraints: const BoxConstraints.tightFor( - height: 40, - width: 40, - ), - ); + final leading = this.leading ?? StreamUserAvatar(size: .lg, user: user); final title = this.title ?? diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_tile.dart index 6dd52422f3..1e9cad5cc8 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_tile.dart @@ -93,9 +93,9 @@ class StreamPollVoteListTile extends StatelessWidget { children: [ if (pollVote.user case final user?) ...[ StreamUserAvatar( + size: .xs, user: user, - constraints: BoxConstraints.tight(const Size.fromRadius(10)), - showOnlineStatus: false, + showOnlineIndicator: false, ), Expanded( child: Padding( diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart index 2b089445f0..1db1e2a69b 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart @@ -223,7 +223,7 @@ class ThreadLatestReply extends StatelessWidget { return Row( spacing: 8, children: [ - if (latestReply.user case final user?) StreamUserAvatar(user: user), + if (latestReply.user case final user?) StreamUserAvatar(size: .md, user: user), Expanded( child: Column( mainAxisSize: MainAxisSize.min, diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_tile.dart index ce1e3a1afd..0a3a331365 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_tile.dart @@ -56,20 +56,7 @@ class StreamUserGridTile extends StatelessWidget { @override Widget build(BuildContext context) { - final child = - this.child ?? - StreamUserAvatar( - user: user, - borderRadius: BorderRadius.circular(32), - constraints: const BoxConstraints.tightFor( - height: 64, - width: 64, - ), - onlineIndicatorConstraints: const BoxConstraints.tightFor( - height: 12, - width: 12, - ), - ); + final child = this.child ?? StreamUserAvatar(size: .xl, user: user); final footer = this.footer ?? diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_tile.dart index cf094ae354..93d2eae436 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_tile.dart @@ -124,15 +124,7 @@ class StreamUserListTile extends StatelessWidget { Widget build(BuildContext context) { final chatThemeData = StreamChatTheme.of(context); - final leading = - this.leading ?? - StreamUserAvatar( - user: user, - constraints: const BoxConstraints.tightFor( - height: 40, - width: 40, - ), - ); + final leading = this.leading ?? StreamUserAvatar(size: .lg, user: user); final title = this.title ?? diff --git a/packages/stream_chat_flutter/lib/src/user/user_mention_tile.dart b/packages/stream_chat_flutter/lib/src/user/user_mention_tile.dart index f7f9ffa42d..6f024c23f1 100644 --- a/packages/stream_chat_flutter/lib/src/user/user_mention_tile.dart +++ b/packages/stream_chat_flutter/lib/src/user/user_mention_tile.dart @@ -43,11 +43,7 @@ class StreamUserMentionTile extends StatelessWidget { const SizedBox( width: 16, ), - leading ?? - StreamUserAvatar( - user: user, - constraints: BoxConstraints.tight(const Size(40, 40)), - ), + leading ?? StreamUserAvatar(size: .lg, user: user), const SizedBox(width: 8), Expanded( child: Align( diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 7020cc5dfa..95bd028974 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -2,6 +2,8 @@ export 'package:jiffy/jiffy.dart'; export 'package:photo_manager/photo_manager.dart' show ThumbnailSize, ThumbnailFormat; export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +export 'package:stream_core_flutter/stream_core_flutter.dart' show StreamAvatarSize, StreamTheme; + export 'src/ai_assistant/ai_typing_indicator_view.dart'; export 'src/ai_assistant/stream_typewriter_builder.dart'; export 'src/ai_assistant/streaming_message_view.dart'; @@ -23,8 +25,6 @@ export 'src/attachment/voice_recording_attachment_playlist.dart'; export 'src/attachment_actions_modal/attachment_actions_modal.dart'; export 'src/autocomplete/stream_autocomplete.dart'; export 'src/avatars/gradient_avatar.dart'; -export 'src/avatars/group_avatar.dart'; -export 'src/avatars/user_avatar.dart'; export 'src/bottom_sheets/attachment_modal_sheet.dart'; export 'src/bottom_sheets/edit_message_sheet.dart'; export 'src/bottom_sheets/error_alert_sheet.dart'; @@ -33,10 +33,15 @@ export 'src/channel/channel_header.dart'; export 'src/channel/channel_info.dart'; export 'src/channel/channel_list_header.dart'; export 'src/channel/channel_name.dart'; -export 'src/channel/stream_channel_avatar.dart'; export 'src/channel/stream_channel_name.dart'; export 'src/channel/stream_draft_message_preview_text.dart'; export 'src/channel/stream_message_preview_text.dart'; +// region SDK Design Refresh Components +export 'src/components/avatar/stream_channel_avatar.dart'; +export 'src/components/avatar/stream_user_avatar.dart'; +export 'src/components/avatar/stream_user_avatar_group.dart'; +export 'src/components/avatar/stream_user_avatar_stack.dart'; +// endregion export 'src/fullscreen_media/full_screen_media.dart'; export 'src/fullscreen_media/full_screen_media_builder.dart'; export 'src/gallery/gallery_footer.dart'; diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 19306c19f1..15180dff30 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -5,6 +5,9 @@ version: 10.0.0-beta.12 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues +# TODO: Remove this once stream_core_flutter is published to pub.dev +publish_to: none + # Note: The environment configuration and dependency versions are managed by Melos. # # Do not edit them manually. @@ -56,6 +59,11 @@ dependencies: share_plus: ">=11.0.0 <13.0.0" shimmer: ^3.0.0 stream_chat_flutter_core: ^10.0.0-beta.12 + stream_core_flutter: + git: + url: https://github.com/GetStream/stream-core-flutter.git + ref: c066cb481bd8a8523e5ea52f3433ffeaeab11332 + path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 thumblr: ^0.0.4 diff --git a/packages/stream_chat_flutter/test/flutter_test_config.dart b/packages/stream_chat_flutter/test/flutter_test_config.dart index 13bd9f624c..5bc88f9814 100644 --- a/packages/stream_chat_flutter/test/flutter_test_config.dart +++ b/packages/stream_chat_flutter/test/flutter_test_config.dart @@ -8,9 +8,8 @@ Future testExecutable(FutureOr Function() testMain) async { return AlchemistConfig.runWithConfig( config: AlchemistConfig( - platformGoldensConfig: PlatformGoldensConfig( - enabled: !isRunningInCi, - ), + ciGoldensConfig: CiGoldensConfig(enabled: isRunningInCi), + platformGoldensConfig: PlatformGoldensConfig(enabled: !isRunningInCi), ), run: testMain, ); diff --git a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/group_avatar_0.png b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/group_avatar_0.png index 7a3fe146164373c7c098ccfa74406c3e9c78a37f..ede411c97b72852c57f135cf70263bf62836d665 100644 GIT binary patch literal 2383 zcmeHJ?NgIi77x2XBPv)>Km&=Q3%W90SIUcpCRLz-E})n<10*ESs634sCk8MH&<2dw zxY5?SU?9@6Q;@u)1jEnm3#3%Jo0@#W%0Y}>JN%^hd$NC3v8P?Un|+%_+pug5F2XQ zjloPt;#_2uIG?EzlwvR#oI@!$=~?Q+wqf1W@+=r&gjzgJp?8={Uz$=F_e8&Nqu!m3V+L7zs%8KRaGtVfxsDOAl-DO z>~I=C)bo*&+M>b^zsxE@BAh1Dnnq)0q0ZCIuBki>Mm7e}gT9Ey$2}}~Polz>d&J)N zy%Mpw%Ec$4#t51Xn^P>d&+079?ntatZFZb(cv_TGa5a9f@pqdYNHaUAm#-?*(O@34Ks*e2DQW zgClRXj0)vnC6d29uB1}ib*&|JfR6uSM@oos$E!L=(b>UzbD1i+%@?2&CBw=2o*x6p zcwr!16d{WU^zxr{A>_12QFn?)1}?>1p{sOY4NE%Jr0R1tm_hBftH*xgv3iL|+%63% zvGg2`9g(>YA9pw#>D;0)Ek!g5KiTT4nKH!M zhuu2i-JknEe}2=_;JOZ5X^-&*cm_x45DI1$r*>shZ$iy~5@V;@c%L`IAHodazZ>w5uhl052((_B*RdXED<5WpcSsU09e%_5A5GLFL zUv31LP4ugrB`C~rTAr5kJtFk)1VB}AukmqD^*=?aiSb`{m#h#nW0)ev4RtF-Qs3Vc5hrVnoWGQypYwg z>B>-Pzb{|CxRO9>Xdhc_v1=Ef(fR{J)q!D{u)oCArN{CzU{axUCR;h4oI8=}x|onR zLf(s6Hf%@ao16$Og)Tzw8LfJAV9&4L=Ko-bux%7pROOgDE5Mlu0?A6xNs}Kd;{FRn C?FKFY literal 10575 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 diff --git a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_0.png b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_0.png index a511a95448767d51a4f7e72312e74b5ccc0947fe..04fe73a4a7a3a1eb0a442accf6a681c4da806146 100644 GIT binary patch literal 2716 zcmeHJ>r)bl7N>4?+kDh)JL~#Lt0lKaEguwD@m*@9Vq0x$KC-Ju%pg8cQ9+oisoS-D zfMzD>b}^YY5KB!Ai8WIe;*n5lB9i&Kt^x@HJ_0U%xPQdX?3r`UZ_YWtIdjhWbZ(u8 zMcM6f+5-Rp?4W1QTmS&Jd493oyR4bw(wYJ*Y(rm&3I}wGU6!oD4sO=$zk;qtU>u~&Oj=)lFs0wjP;#bkYyX&e=PA)^A5Fgp@*8LLE3S6B zaKiU+h|Uk2LWVI$Z-6NJec$*Um>*eic2ry9I!NVfB?_zdB&m8JjQmNSjS(tqZ;p}0 zwAfb7+E!9~7VpZEaPF2Tth;e1P#IKq(fH{B$fRU`h%vw_GT*%FLTgUTwRBKjkla>| z$ZcMj7h=QN5Q++d$BvIgk#LxSIwk9q97@d2GQnS%m6is#z0XKMi7{TMU&MQG*@kW? zF}{=A7+oC~Qt5CyqHLHG8|5Fc&O~x`CEsqM6H^n;?i0KYu#C5lFyq$Ttk)`9MXA;Boomy}4&vfn~#0Ej8#O)%5^yV7^|B=`6qr^jC~85|kMwI*cnmlSc~cVhxQB zHm+!r@$DXujVX>ucPJjQwBOpQsi`L901lmFs+WYtmJO>qs7Q{ELuUp}Hon_J-7X{o z39Bp2(z!wgkWe@iPXb~|GwID0CCQ?>Q-4ni5&n~jmDsFsEiW}eZ_ExMmhczrj3%A5 zzj|X~xJla!=8~ugwEPsBQD`=NjMp}zLr4m~w*(^ze)FVAn{aLQUkl#Yq!6)IAWet7 z8su6I5_ET%N1*1pmrc6Khs=e&62c*g{<0QFI7!Gc+Z3y$@j71H=SCp^F*Zb0u`$m( zwYPWuXHTbosOn0 zXC3OimMi--lC-3M-t9VTZ!+U5x3VQAwy>`T4?}&OkQ70heu3@ihWdX_>S8BFQC`R% zC{PdmorG5AEg7cLcfTm(Ug6@JV!I60=eIiiBkF5UB2YB?X^a#eM}pBPkzK5%HOd3w z$T1vbx237O0SINwUbiFE>90vAn?VRnU{*tVSwx;+f@p3{vGxWe%CaD;EXp0#Ux;yj zL8Tt_j^>Wcis9I@2x10)Cb@EGmCj=g<$%PW%YLZIi?J@gCJt;F%mFR?sJ*|tElqmv zz3lUkBVRhu*yKX^u~{mg9@U>27<>+k;+Ni>I-D>iO)3c(y4WLdvgIvNim#8Q=lpy3 zw|(Tqtz?5?S2GN5LCoX_i0+uKiRq=NG3uA9+YMv)fVp`^eLz@3wEB!<(;{(l`ieF`**iMuhKKoGv^r}V9{_S$YROiX2UU;f-VN5NIWIgL| z>5aof>8$d!+$!!T@C@mK2OLY*u+!|GN9B!-cL%)jW?mWbsH75r~RdQ9t;sf zAw1C$@T7C^nO_QS(R-d@3{Shd$Q8ZOV7LF;WAa8R*c-kRc&{x; zh+uP4@I7~*(mGX^`g*ZSwnV`WD$fTK({iu6SH(Zo%QO#_%hxlDeHddyjIs_HqR?pX z>r&IbDQuD^78&YA0#bAriy#WKFP#2;daT92`)xFLJhpFZHJI;QTkHE>4LY4rH{V;G z7-)Ro&RL&`T;Ly0z~N-qP)M`)5T~sjQ6tqzXkcY5TXBx9fYH#OpP_saQc&#u>-T4F zkO$Km3TZ9VS~O3YmR*drb*oKVJ?~Cs*cz4M^&CG@MgxEJsAz88=cEb-ru1+0$T|5A ze}j;ZGlS*c=^-@(pz?+u3j^;V<~pp1@-Bqn-1}4_(I^tRE%-8UZ`g8J<@5 EKXUE+=>Px# literal 3684 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 B(To59 diff --git a/packages/stream_chat_flutter/test/src/avatars/group_avatar_test.dart b/packages/stream_chat_flutter/test/src/avatars/group_avatar_test.dart index 304de393d2..9614b24e92 100644 --- a/packages/stream_chat_flutter/test/src/avatars/group_avatar_test.dart +++ b/packages/stream_chat_flutter/test/src/avatars/group_avatar_test.dart @@ -16,12 +16,8 @@ void main() { late MockChannel channel; late MockChannelState channelState; - late final member = Member( - user: User(id: 'alice', name: 'Alice'), - ); - late final member2 = Member( - user: User(id: 'bob', name: 'Bob'), - ); + late final user1 = User(id: 'alice', name: 'Alice'); + late final user2 = User(id: 'bob', name: 'Bob'); setUpAll(() { client = MockClient(); @@ -30,7 +26,10 @@ void main() { when(() => channel.state!).thenReturn(channelState); when(() => channelState.membersStream).thenAnswer( - (_) => Stream>.value([member, member2]), + (_) => Stream>.value([ + Member(user: user1), + Member(user: user2), + ]), ); }); @@ -50,11 +49,8 @@ void main() { channel: channel, child: Scaffold( body: Center( - child: StreamGroupAvatar( - members: [ - member, - member2, - ], + child: StreamUserAvatarGroup( + users: [user1, user2], ), ), ), @@ -86,11 +82,8 @@ void main() { child: SizedBox( width: 100, height: 100, - child: StreamGroupAvatar( - members: [ - member, - member2, - ], + child: StreamUserAvatarGroup( + users: [user1, user2], ), ), ), diff --git a/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart b/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart index 685c75f589..44921dc7c8 100644 --- a/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart +++ b/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart @@ -183,53 +183,17 @@ void main() { // wait for the initial state to be rendered. await tester.pumpAndSettle(); - final image = tester.widget(find.byType(StreamGroupAvatar)); - final otherMembers = members.where((it) => it.userId != currentUser.id); - expect( - image.members.map((it) => it.user?.id), - otherMembers.map((it) => it.user?.id), - ); - }, - ); - - testWidgets( - 'using select: true should show a selection border', - (tester) async { - final client = MockClient(); - final clientState = MockClientState(); - final channel = MockChannel(); - final channelState = MockChannelState(); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => channel.state).thenReturn(channelState); - when(() => channel.client).thenReturn(client); - when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); - when(() => channel.name).thenReturn('test'); - when(() => channel.imageStream).thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); - when(() => channel.image).thenReturn('https://bit.ly/321RmWb'); - - await tester.pumpWidget( - MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamChannelAvatar( - channel: channel, - selected: true, - ), - ), - ), - ), - ), - ); + // The new StreamChannelAvatar uses StreamUserAvatarGroup internally + // for multi-member channels + final avatarGroup = find.byType(StreamUserAvatarGroup); + expect(avatarGroup, findsOneWidget); - // wait for the initial state to be rendered. - await tester.pumpAndSettle(); - - expect(find.byKey(const Key('selectedImage')), findsOneWidget); + // Verify user avatars are shown for all members + expect(find.byType(StreamUserAvatar), findsNWidgets(members.length)); }, ); + + // Note: The 'selected' parameter has been removed in the redesigned + // StreamChannelAvatar component. Selection states should now be handled + // at the parent widget level if needed. } diff --git a/packages/stream_chat_flutter/test/src/channel/goldens/ci/channel_header_bottom_widget.png b/packages/stream_chat_flutter/test/src/channel/goldens/ci/channel_header_bottom_widget.png index a5be81e46f16dd4f5e386a4225c1775f91c56821..bdee9d01d8530b43c936aed6558dbeffb0d513c2 100644 GIT binary patch delta 1863 zcmZ`(X;2c17VhdbOGy%!au04<8hS;_(%i$PRH!6H68BL-&0PsiuDQjHS=9X!R8swH--uw0D&b;&E%zQIv`OZ0WJ}O{M;f^&~R`UYF22G@W zo$3zvbl2jou1wzGtY1$E^h0%&W>N0iT~!S|C}oO3@1q<7mp+5;pJwoA5K0+qAAL>m9D-J59R7g_Zlm0q zt&)Juj8y>dOpKQ7+ePw0Di&Hcqb#q+b&~##YmOl%+e|)@t>gBTv@n&V0WtoUgxgo-875N-(=!*W6|d)x zw7>q_Mk{H_J7TGJb%G^kg3zk16nZGw_x6(?xB8S|(`Q`X7zXlq5uE&Pa9VAy#GOm!68oTmxGt_DJ zUi1M?bhh?ab^#+sx6@`W`155qL9z-}hF^BObsJbv3eRVqNkiJ6P=@3?vc*hO@?dnE z?C?<5f0IM%P|U9z^iCo~HD@3=Hl3s^XeH29bw~0txOqp(6zWc*)b9Y-Gvh03WZoQa9a zMw_?zEPrcY=m2rG=`Qs0EP6?3PXDljGt#Vql3uozr@9t>c+fdl&Lt!y6#B9DRqx7O z-&K9}y>?o`N-RhatmSN3Q9GriRQuGf;tHhu zu{uN|6Y@htoL^8-Mt8SMnq+rReC6Tq@iThsnP9Wmes z-)EYPs&xh0Pcr9~iqa3D{TO}xKrGGU;i@8X1bkmJk&G!`F?A{f#ebWe=BCLnFE2;T z@Uo6H9WrIR8AoacHAE-aM3`wk7)V)Z1tyaHd0Sg%*REZ=ckvi^dNZM0KT3U!Wz=^& zZAlb}d#`l8+5t-NeY>|eIiHr=oVaQHV+AE%vrybDDvP1j%^aj4f^c$svIl%k;gg%Z z&5132i0ftZhb2=46cP~yUnxMTKZKn(NvtivS>obSk#gPl} z2Ho?DZ*1c}E5)(JYwX*<@-_s+6SDN#UYDJdz0xB-0O6POmhrJ+pL3oQE}n==$AB>{ zdF5V}UG#KpEqCk9j-&SMEElRqTS)RBw!--^NU{;XjW<&G*W54mY1q% zg5u$TWA1qyw#~5rbi;zr!-@`XOZ!V?x}-0TBl+ICnNs)|tA+S&+fp(}_P{i|%-pHs z-E#32AIiQxRkE@}Ccx(oM_us}p*(6fSw*ZLX^%A1tiMKAt)b76uK*&ahRP+e9j@lm z+xK;A?PYu6qpN4*x^x3PQVryn`qX@8ZV-8YX`~FqO76z>r6!snS3T45p5W?MP;O4_ zC*okC7WOU9e573ns2XYEoB}B*Wn&NwqeXP)oHH%eVoLsiG6qs(^nzi7P)(O|`X6Wg n5ByJP|4hg|6m!Y}p~@xyq9G*up36c(_eku59THJx>vQXG1&fwv delta 1501 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 diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png index 30ee9c682c16613f17a0a5f0dfa762064b1d6443..90c778792ccad16b7c01a7212ada2755f6ae6903 100644 GIT binary patch literal 11095 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 YTW2eHh78Hw2LT`TC)z4ik6*m~FM`dk*Z=?k literal 18262 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%0AuU? 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ΝOxFgP})A_1JVe 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 OO-o&0t@O$BxBm;2AOSi6 diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_light.png index 54cedd7d291c038ddc1e4d5667e5f893a0926813..37eae4546de9e5f63d409ee00aef57feea908e0e 100644 GIT binary patch literal 11078 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 literal 18190 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 diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_dark.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_dark.png index 01841c4f529b79555a41f6b0f7f0c41a6c9cbc78..fe7bb92296690e57c00778ad3b66a19db52d9064 100644 GIT binary patch literal 9936 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 literal 12892 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

BYV#m<3RUlGr=7L@ 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Ȫ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 M)wxr8+v@562cZDXvH$=8 diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_light.png index 54fefe2d99e37e958c016f45f8ad0ce7723d8a43..1b3ce9148ec2765836eb5540c4422f7203125ebe 100644 GIT binary patch literal 11145 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 literal 16678 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%0ACbggUnnYUX?O+T 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 diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_dark.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_dark.png index 3905f507780c2b04fc38045beadaf096c74f82cf..5f5a766292f38e99913ebba5cede0bd44bd1f6c0 100644 GIT binary patch literal 10155 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 PJK$RjTk}UJeXji{pfAn< literal 13101 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 ZvD2P!Z)|_$4nAXo&LC_p%T8aq`Co7hZ94z} literal 13233 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

{;8NAz-NmzBc1Hd95S9ONRXnd41 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 H>goRhI255M diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_dark.png index 6f2fedb2393f9d79ff4e3140b02803f0b28d910b..600d24b9567b9622948f0f4b7f1c3dc9cda3f975 100644 GIT binary patch literal 5719 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 q2(bAe3IKp4A<(xd2dlA&cN2#rO-C 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 diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_dark.png index 9194488c7227633919a4470e2358f6365a513a6d..5da7f724abd1bf30e1dc3b2b298a63360c94d291 100644 GIT binary patch literal 7757 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 literal 10289 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 diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_light.png index 1aaa2d083df4c0c6a3aff4c2dc109d1bd25e1bbe..4ec326e034610f21012519ae0cf5a9a3a4ddadf0 100644 GIT binary patch literal 7522 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 literal 9987 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 bsMPVzG+3A@8(hX206z+Hs?Uis#=idp#qRQH diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png index 4284f19d59c373b4f45c340fb3b1a9a9cf1a1fc8..fcf6f252cbce8c920e24ef88202564de8893235a 100644 GIT binary patch literal 3314 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 literal 4290 zcmc&%X&_YZ_n$Vzue71DCQD`giuop4N*G2VjD0IKvKFSXi&B}UIp=ep^CVc9K@aeY@`FI21181> zmLSk>J3vn7;{oow#eS;+k6l5QP<;@kLwp)|*&C#9V$BC!5quu;AkZOS6N5|EA(^uy zk!W$o^_GP(b@kft+NzZ(UXh63S$irI1QL=Cr_5RmKIOr78yH_Jcy1F%j3@4o?Ijj) zj>Ho;F+_t)e31RCxfFf)AW;Y&yZfqZw?@n}g+S@!V*L#lf+{;R>roB3$gxmP%gpS^ zuAB3o{sE}@HH=w)ZB|y+*T#mhpBV^rMIYeAOQVnGnO{~8N_0Pgrc8Z$|2}andgaUG zxv!0`C>{1whuWGx#_@hyu#^lYK-eXh(Z+?N@(jbb)EOZRSNRxH3mKkem5sHJ1Vf^1$e}?(^b{k z=@5hPW9%ZF8bCud6B#x8>(KzcHl> zQ*AsMxp4SaO9lKKA_FKyCFkq@%wKXU>T9)X)=iIT+6@A=&sX#l5P0XcD17L@d_@Xe zHhx*hM_*gKY^T{=)vdQ-N^jpD93|^A%Y9|&s_I9$$+?=0FPv`J{*=@8JW8C)>gQt} zEbkgsF7g_Uo@8gCpF0}5q4~%5Uyo#mIB|Nj;OAsY0AQDfY7A?(Ml0qgW7hIwl($El ze*O|$A^WNHb+Ga}hwI5#LxrUQ=8KC%IX|Ct_dFu(XTuAu9-1!cjm z!eVr$wNZly+23!LE~w(H(1Mlihf!p02j|&SjMk@Q%-{|D=C47Cu#hRO4-zQ$sU}$z z2xNh;&N=9+DU%HXu(^5zj^h(dO=b$TxLUaoVA`!14{`_N4FjXNuM58E=hPY^PzsT9 zMI_nl++IATAXF3;-sbiP0g%JOT~V}|=IEN*u<)?qycV(Uj0xVO4F@W6r;8-r%6S~w z|B1dHFjkS2qOU_8P zB-}-Amc{IjcBwnv578PJBDp`ep>-CvhhX>5_LX`sIL*e((Ps@Hj!Fs{Dc+C#tfK1- z*gXR*(PmDPk+8Bc%%ZhVJ5F{^ zjru%WJ_u|Fon=4WB4qP!E)NQ^G4lSkJ7vfg!{&jMLz6sn}TsH1>|H zPwd9roEea(NOD6|l;-*yfFA`h#25&^jfu@@h3zdAw;v~veyr7X*0LK#XAz$1N>h4efY}nb@0J zua=^5v*#@1u!2=eVH%mNcs^-U3gy(Eo@lBn_WVSj?o+S+PPuT%v0HF?M@x5Jvi z@E=)W{)rd(cY!XB7i#gcmV+Ji#`^NnR^1QUr>&#@rIII&a|z}C(=z}$iwx4ARvIFq zxiBIAvX2VVwqUHnns2JXF*Tz-F8TqV%~(_sr>NnZ4iG>JnK~ap_!&u>gD89Rxt_&|b_X9ACpu+cNL%!e#gi&efz7a^iy zvC*XQWx@FAfDOYT9+Zf~c$=othJTV#-RHNP$#8^{4SR2;KiHv1#6pp~_bmgtTtewU zm%i_=BgW9B0=nX=eLXiws~FPi7=B#IUZ9Qsc8VV1wps==ItgPeCH?l5byqw(QG3O@ zH9p_ov0!G4na<=^X6CzBP|1<0%LN%Njq8M!$a-(LSwEKb@Er!Y6^SOtnuf`-fP&Yp zPN0R^emsuUJWPPCqLQr%>vcaUB((E@OG^2LzSqVb^kH;yby7p6?2*AVw4x-kTpj

@$=NnaHBwi zPJ)?dW{$kTKIDaK$eP29h{m%q&acJY8SK*6>#>7Q<|Fd2eJOc3c`Jq-IB$vITM~UL z1XM0Q-Ou6mUQ!0^d{6+m&SKg`pA7J7@jIGa04E>tZdsDkY)O>$v zqNA;HpipUw_W84E>p4$pllM)NQxs5gDMREV*Q&&AZE6Rf3_h7DXs_aCVGc3IQ#SI> zB)eIiul@vO!sOhkT8%k`T)_1_+8y*wm|;6M5w<4h>1$%|XnD!TaFuCc5R@d}B$lt; z%@D<+=@w@X@5P4deE!uF6zyqS^L67u z!u?Xo`e6UEvK06s)8;q(8L<0>d80H04F|s8Kk+>7ytA4)&7?NXWMpjYc$+FPW1SWK zu+kYj&Ec^X9OxvWWSGndzc*SZ)!U83j z)RSLT#z0brPjoA=ysIs1Ckqut>B$RsH%8c1pCC8EP@qY&h)##Z%9)&#yTDGC&?9x? z!wl1~Vvs?xS4v)CA$pDsWv{jp-EDJ{=+euRiw&uj?4tAAg;h?711 zl8(Ww^J(cElocD~oKE>3i-Cyj<&K_q3kZDQRa zxj8Oo`N4OHC&mt_@OG%Szg%QoYQf+)zqQxQGn?7xO}U$|L!USq73Q|~7(W($Q9@0X zD)1%4^=O8$fmWoTeQ9|l`@FFe;Na_0BH-Xq_*(Ex z-w-?Q73YTM!HNwV?)wxTGRxYv4)b4ooQ!QBC;jUNVy8pZ! zV~)Mv^fC=W0L_-gslH=&OPpNgFb}tUN$^0SwOVmPgv%{GJKtW>GvBE&DuuFmgZVq> zdW0+h*$BT}i7@MTj=?dY&+N6);RX6W(0n^^+%l4S@8Byo(9rb=7?I5ai;V(RM~r(K z0zDvqhjN=g=7Im;3387o|D}uMHp}-ObI&uNo!0Sx@k7}bZ?4_dAl;3tB7h+v6GJlt JioRRy{{exFzs3Ln diff --git a/sample_app/lib/app.dart b/sample_app/lib/app.dart index 7732b93fb4..e40339351f 100644 --- a/sample_app/lib/app.dart +++ b/sample_app/lib/app.dart @@ -480,8 +480,14 @@ class _StreamChatSampleAppState extends State defaultValue: 0, ), builder: (context, snapshot) => MaterialApp.router( - theme: ThemeData.light(), - darkTheme: ThemeData.dark(), + theme: ThemeData( + brightness: .light, + extensions: [StreamTheme.light()], + ), + darkTheme: ThemeData( + brightness: .dark, + extensions: [StreamTheme.dark()], + ), themeMode: const { -1: ThemeMode.dark, 0: ThemeMode.system, diff --git a/sample_app/lib/pages/channel_list_page.dart b/sample_app/lib/pages/channel_list_page.dart index 5ffc5f9a07..fb9db8ec7c 100644 --- a/sample_app/lib/pages/channel_list_page.dart +++ b/sample_app/lib/pages/channel_list_page.dart @@ -194,9 +194,9 @@ class LeftDrawer extends StatelessWidget { child: Row( children: [ StreamUserAvatar( + size: .lg, user: user, - showOnlineStatus: false, - constraints: BoxConstraints.tight(const Size.fromRadius(20)), + showOnlineIndicator: false, ), Padding( padding: const EdgeInsets.only(left: 16), diff --git a/sample_app/lib/pages/chat_info_screen.dart b/sample_app/lib/pages/chat_info_screen.dart index e9dfbd3dc8..1aab7b6430 100644 --- a/sample_app/lib/pages/chat_info_screen.dart +++ b/sample_app/lib/pages/chat_info_screen.dart @@ -68,13 +68,9 @@ class _ChatInfoScreenState extends State { Padding( padding: const EdgeInsets.all(16), child: StreamUserAvatar( + size: .xl, user: widget.user!, - constraints: const BoxConstraints.tightFor( - width: 72, - height: 72, - ), - borderRadius: BorderRadius.circular(36), - showOnlineStatus: false, + showOnlineIndicator: false, ), ), Text( @@ -509,8 +505,8 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { Padding( padding: const EdgeInsets.all(8), child: StreamChannelAvatar( + size: .lg, channel: channel, - constraints: const BoxConstraints(maxWidth: 40, maxHeight: 40), ), ), Expanded( diff --git a/sample_app/lib/pages/choose_user_page.dart b/sample_app/lib/pages/choose_user_page.dart index 37300f6b91..fc73289c67 100644 --- a/sample_app/lib/pages/choose_user_page.dart +++ b/sample_app/lib/pages/choose_user_page.dart @@ -122,10 +122,8 @@ class ChooseUserPage extends StatelessWidget { router.replaceNamed(Routes.CHANNEL_LIST_PAGE.name); }, leading: StreamUserAvatar( + size: .lg, user: user, - constraints: BoxConstraints.tight( - const Size.fromRadius(20), - ), ), title: Text( user.name, diff --git a/sample_app/lib/pages/group_chat_details_screen.dart b/sample_app/lib/pages/group_chat_details_screen.dart index 666a49bd54..c2de052627 100644 --- a/sample_app/lib/pages/group_chat_details_screen.dart +++ b/sample_app/lib/pages/group_chat_details_screen.dart @@ -206,11 +206,8 @@ class _GroupChatDetailsScreenState extends State { return ListTile( key: ObjectKey(user), leading: StreamUserAvatar( + size: .lg, user: user, - constraints: const BoxConstraints.tightFor( - width: 40, - height: 40, - ), ), title: Text( user.name, diff --git a/sample_app/lib/pages/group_info_screen.dart b/sample_app/lib/pages/group_info_screen.dart index f64e1481d9..fbcb5e4387 100644 --- a/sample_app/lib/pages/group_info_screen.dart +++ b/sample_app/lib/pages/group_info_screen.dart @@ -254,11 +254,8 @@ class _GroupInfoScreenState extends State { vertical: 12, ), child: StreamUserAvatar( + size: .lg, user: member.user!, - constraints: const BoxConstraints.tightFor( - height: 40, - width: 40, - ), ), ), Expanded( @@ -730,12 +727,8 @@ class _GroupInfoScreenState extends State { child: Padding( padding: const EdgeInsets.all(16), child: StreamUserAvatar( + size: .xl, user: user, - constraints: const BoxConstraints.tightFor( - height: 64, - width: 64, - ), - borderRadius: BorderRadius.circular(32), ), ), ), diff --git a/sample_app/lib/pages/new_chat_screen.dart b/sample_app/lib/pages/new_chat_screen.dart index e084465ecf..6ce60e5038 100644 --- a/sample_app/lib/pages/new_chat_screen.dart +++ b/sample_app/lib/pages/new_chat_screen.dart @@ -218,12 +218,9 @@ class _NewChatScreenState extends State { shape: BoxShape.circle, ), child: StreamUserAvatar( - showOnlineStatus: false, + size: .sm, user: user, - constraints: const BoxConstraints.tightFor( - height: 24, - width: 24, - ), + showOnlineIndicator: false, ), ), const StreamSvgIcon(icon: StreamSvgIcons.close), diff --git a/sample_app/lib/pages/new_group_chat_screen.dart b/sample_app/lib/pages/new_group_chat_screen.dart index ffbc4cdd1a..9336b37ecd 100644 --- a/sample_app/lib/pages/new_group_chat_screen.dart +++ b/sample_app/lib/pages/new_group_chat_screen.dart @@ -143,13 +143,8 @@ class _NewGroupChatScreenState extends State { Stack( children: [ StreamUserAvatar( - onlineIndicatorAlignment: const Alignment(0.9, 0.9), + size: .xl, user: user, - borderRadius: BorderRadius.circular(32), - constraints: const BoxConstraints.tightFor( - height: 64, - width: 64, - ), ), Positioned( top: -4, diff --git a/sample_app/lib/widgets/location/location_attachment.dart b/sample_app/lib/widgets/location/location_attachment.dart index b43dc0e718..5a7038ddd5 100644 --- a/sample_app/lib/widgets/location/location_attachment.dart +++ b/sample_app/lib/widgets/location/location_attachment.dart @@ -103,12 +103,12 @@ class LocationAttachment extends StatelessWidget { onTap: onLocationTap, child: IgnorePointer( child: SimpleMapView( - markerSize: 40, + markerSize: MarkerSize.lg, showLocateMeButton: false, coordinates: sharedLocation.coordinates, markerBuilder: (_, __, size) => LocationUserMarker( user: user, - markerSize: size, + size: size, sharedLocation: sharedLocation, ), ), diff --git a/sample_app/lib/widgets/location/location_detail_dialog.dart b/sample_app/lib/widgets/location/location_detail_dialog.dart index c5082bf365..b6e39b48b9 100644 --- a/sample_app/lib/widgets/location/location_detail_dialog.dart +++ b/sample_app/lib/widgets/location/location_detail_dialog.dart @@ -72,11 +72,11 @@ class LocationDetailDialog extends StatelessWidget { children: [ SimpleMapView( cameraZoom: 16, - markerSize: 48, + markerSize: MarkerSize.xl, coordinates: sharedLocation.coordinates, markerBuilder: (_, __, size) => LocationUserMarker( user: message.user, - markerSize: size, + size: size, sharedLocation: sharedLocation, ), ), diff --git a/sample_app/lib/widgets/location/location_picker_dialog.dart b/sample_app/lib/widgets/location/location_picker_dialog.dart index 1abaaf9e1c..2c8eaafb39 100644 --- a/sample_app/lib/widgets/location/location_picker_dialog.dart +++ b/sample_app/lib/widgets/location/location_picker_dialog.dart @@ -2,6 +2,7 @@ import 'package:avatar_glow/avatar_glow.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:sample_app/utils/location_provider.dart'; +import 'package:sample_app/widgets/location/location_user_marker.dart'; import 'package:sample_app/widgets/simple_map_view.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -94,7 +95,7 @@ class _LocationPickerDialogState extends State { return SimpleMapView( cameraZoom: 18, - markerSize: 24, + markerSize: MarkerSize.sm, coordinates: coordinates, markerBuilder: (context, _, size) => AvatarGlow( glowColor: colorTheme.accentPrimary, @@ -107,7 +108,7 @@ class _LocationPickerDialogState extends State { ), ), child: CircleAvatar( - radius: size / 2, + radius: size.value / 2, backgroundColor: colorTheme.accentPrimary, ), ), diff --git a/sample_app/lib/widgets/location/location_user_marker.dart b/sample_app/lib/widgets/location/location_user_marker.dart index 102e16876c..3c8994ab98 100644 --- a/sample_app/lib/widgets/location/location_user_marker.dart +++ b/sample_app/lib/widgets/location/location_user_marker.dart @@ -2,16 +2,30 @@ import 'package:avatar_glow/avatar_glow.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +enum MarkerSize { + xs(20), + sm(24), + md(32), + lg(40), + xl(64) + ; + + const MarkerSize(this.value); + + final double value; +} + class LocationUserMarker extends StatelessWidget { const LocationUserMarker({ super.key, this.user, - this.markerSize = 40, + this.size = MarkerSize.lg, required this.sharedLocation, }); final User? user; - final double markerSize; + final MarkerSize size; + final Location sharedLocation; @override @@ -29,12 +43,9 @@ class LocationUserMarker extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(borderWidth), child: StreamUserAvatar( + size: _avatarSizeForMarkerSize(size), user: user, - constraints: BoxConstraints.tightFor( - width: markerSize, - height: markerSize, - ), - showOnlineStatus: false, + showOnlineIndicator: false, ), ), ); @@ -48,9 +59,19 @@ class LocationUserMarker extends StatelessWidget { } return Icon( - size: markerSize, + size: size.value, Icons.person_pin, color: colorTheme.accentPrimary, ); } + + StreamAvatarSize _avatarSizeForMarkerSize( + MarkerSize size, + ) => switch (size) { + .xs => StreamAvatarSize.xs, + .sm => StreamAvatarSize.sm, + .md => StreamAvatarSize.md, + .lg => StreamAvatarSize.lg, + .xl => StreamAvatarSize.xl, + }; } diff --git a/sample_app/lib/widgets/message_info_sheet.dart b/sample_app/lib/widgets/message_info_sheet.dart index 6f0a8cec57..fc9874e1e1 100644 --- a/sample_app/lib/widgets/message_info_sheet.dart +++ b/sample_app/lib/widgets/message_info_sheet.dart @@ -251,11 +251,8 @@ class _UserReadTile extends StatelessWidget { children: [ // User avatar StreamUserAvatar( + size: .lg, user: read.user, - constraints: const BoxConstraints.tightFor( - height: 40, - width: 40, - ), ), const SizedBox(width: 12), diff --git a/sample_app/lib/widgets/simple_map_view.dart b/sample_app/lib/widgets/simple_map_view.dart index a7d8c6e0a7..ab75abcff6 100644 --- a/sample_app/lib/widgets/simple_map_view.dart +++ b/sample_app/lib/widgets/simple_map_view.dart @@ -2,20 +2,21 @@ import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map_animations/flutter_map_animations.dart'; import 'package:latlong2/latlong.dart'; +import 'package:sample_app/widgets/location/location_user_marker.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; typedef MarkerBuilder = Widget Function( BuildContext context, Animation animation, - double markerSize, + MarkerSize markerSize, ); class SimpleMapView extends StatefulWidget { const SimpleMapView({ super.key, this.cameraZoom = 15, - this.markerSize = 30, + this.markerSize = MarkerSize.lg, required this.coordinates, this.showLocateMeButton = true, this.markerBuilder = _defaultMarkerBuilder, @@ -23,17 +24,17 @@ class SimpleMapView extends StatefulWidget { final double cameraZoom; - final double markerSize; + final MarkerSize markerSize; final LocationCoordinates coordinates; final bool showLocateMeButton; final MarkerBuilder markerBuilder; - static Widget _defaultMarkerBuilder(BuildContext context, _, double size) { + static Widget _defaultMarkerBuilder(BuildContext context, _, MarkerSize size) { final theme = StreamChatTheme.of(context); final iconColor = theme.colorTheme.accentPrimary; - return Icon(size: size, Icons.person_pin, color: iconColor); + return Icon(size: size.value, Icons.person_pin, color: iconColor); } @override @@ -87,8 +88,8 @@ class _SimpleMapViewState extends State with TickerProviderStateM AnimatedMarkerLayer( markers: [ AnimatedMarker( - height: widget.markerSize, - width: widget.markerSize, + height: widget.markerSize.value, + width: widget.markerSize.value, point: widget.coordinates.toLatLng(), builder: (context, animation) => widget.markerBuilder( context, From 4f37a1da5fcb34735814bef400745d6a94f8b871 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Fri, 6 Feb 2026 12:30:02 +0100 Subject: [PATCH 03/33] update project files for latest flutter version (#2507) --- .gitignore | 1 + sample_app/ios/Flutter/AppFrameworkInfo.plist | 2 +- sample_app/ios/Runner.xcodeproj/project.pbxproj | 4 ++-- .../Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme | 2 ++ 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 981d572c14..06a0c25816 100644 --- a/.gitignore +++ b/.gitignore @@ -66,6 +66,7 @@ GeneratedPluginRegistrant.* **/ios/**/xcuserdata **/ios/.generated/ **/ios/Flutter/App.framework +**/ios/Flutter/ephemeral **/ios/Flutter/Flutter.framework **/ios/Flutter/Flutter.podspec **/ios/Flutter/Generated.xcconfig diff --git a/sample_app/ios/Flutter/AppFrameworkInfo.plist b/sample_app/ios/Flutter/AppFrameworkInfo.plist index 8c6e56146e..d57061dd6b 100644 --- a/sample_app/ios/Flutter/AppFrameworkInfo.plist +++ b/sample_app/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 12.0 + 13.0 diff --git a/sample_app/ios/Runner.xcodeproj/project.pbxproj b/sample_app/ios/Runner.xcodeproj/project.pbxproj index 5543119cb8..3debb7913b 100644 --- a/sample_app/ios/Runner.xcodeproj/project.pbxproj +++ b/sample_app/ios/Runner.xcodeproj/project.pbxproj @@ -296,7 +296,7 @@ "${BUILT_PRODUCTS_DIR}/package_info_plus/package_info_plus.framework", "${BUILT_PRODUCTS_DIR}/path_provider_foundation/path_provider_foundation.framework", "${BUILT_PRODUCTS_DIR}/photo_manager/photo_manager.framework", - "${BUILT_PRODUCTS_DIR}/record_ios/record_ios.framework", + "${BUILT_PRODUCTS_DIR}/record_darwin/record_darwin.framework", "${BUILT_PRODUCTS_DIR}/sentry_flutter/sentry_flutter.framework", "${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework", "${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework", @@ -340,7 +340,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_foundation.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/photo_manager.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/record_ios.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/record_darwin.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sentry_flutter.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework", diff --git a/sample_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/sample_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c53e2b314e..9c12df59c6 100644 --- a/sample_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/sample_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> Date: Fri, 6 Feb 2026 12:52:22 +0100 Subject: [PATCH 04/33] add internal testflight builds (#2508) --- .github/workflows/distribute_internal.yml | 44 +++++++++++++++++++++++ sample_app/ios/fastlane/Fastfile | 23 ++++++++++++ 2 files changed, 67 insertions(+) diff --git a/.github/workflows/distribute_internal.yml b/.github/workflows/distribute_internal.yml index 19969d27cc..f1ad493bee 100644 --- a/.github/workflows/distribute_internal.yml +++ b/.github/workflows/distribute_internal.yml @@ -144,3 +144,47 @@ jobs: MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} APPSTORE_API_KEY: ${{ secrets.APPSTORE_API_KEY }} run: bundle exec fastlane distribute_to_firebase + + # TODO: Remove once feat/design-refresh is merged to master + ios_testflight: + needs: determine_platforms + if: ${{ needs.determine_platforms.outputs.run_ios == 'true' }} + runs-on: macos-15 # Requires xcode 15 or later + timeout-minutes: 30 + steps: + - name: Connect Bot + uses: webfactory/ssh-agent@v0.9.1 + with: + ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }} + + - name: "Git Checkout" + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: "Install Flutter" + uses: subosito/flutter-action@v2 + with: + flutter-version: ${{ env.FLUTTER_VERSION }} + channel: stable + cache: true + cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} + + - name: "Install Tools" + run: flutter pub global activate melos + + - name: "Bootstrap Workspace" + run: melos bootstrap + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + working-directory: sample_app/ios + + - name: Distribute to TestFlight Internal + working-directory: sample_app/ios + env: + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + APPSTORE_API_KEY: ${{ secrets.APPSTORE_API_KEY }} + run: bundle exec fastlane distribute_to_testflight_internal diff --git a/sample_app/ios/fastlane/Fastfile b/sample_app/ios/fastlane/Fastfile index 20f466bc78..2cdb0391c6 100644 --- a/sample_app/ios/fastlane/Fastfile +++ b/sample_app/ios/fastlane/Fastfile @@ -116,6 +116,29 @@ platform :ios do ) end + # Usage: bundle exec fastlane ios distribute_to_testflight_internal + lane :distribute_to_testflight_internal do + match_me + + current_build_number = latest_testflight_build_number( + api_key: appstore_api_key, + app_identifier: app_identifier + ) + + build_number = (current_build_number || 0).to_i + 1 + build_ipa(export_method: "app-store", build_number: build_number) + + upload_to_testflight( + api_key: appstore_api_key, + distribute_external: false, + notify_external_testers: false, + ipa: "#{root_path}/build/ios/ipa/ChatSample.ipa", + groups: ['Internal Testers'], + changelog: 'Lots of amazing new features to test out!', + skip_waiting_for_build_processing: true, + ) + end + private_lane :appstore_api_key do @appstore_api_key ||= app_store_connect_api_key( key_id: 'MT3PRT8TB7', From 850ff729fc2fd9d1d8eafbe03c8292e4885ad82f Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Tue, 10 Feb 2026 12:31:59 +0100 Subject: [PATCH 05/33] feat(ui): message composer design update (#2505) * Adding new message composer * basic implementation message composer * Added focus node * Add placeholder * update core dependency * formatting * chore: Update Goldens --------- Co-authored-by: renefloor <15101411+renefloor@users.noreply.github.com> --- melos.yaml | 2 +- .../message_composer/message_composer.dart | 1 + .../message_composer_factory.dart | 28 ++++ .../message_composer_input_header.dart | 28 ++++ .../message_composer_input_leading.dart | 27 ++++ .../message_composer_input_trailing.dart | 21 +++ .../message_composer_leading.dart | 39 ++++++ .../message_composer_trailing.dart | 14 ++ .../stream_chat_message_composer.dart | 130 ++++++++++++++++++ .../message_input/stream_message_input.dart | 17 +-- .../lib/stream_chat_flutter.dart | 4 +- packages/stream_chat_flutter/pubspec.yaml | 2 +- .../goldens/ci/edit_message_sheet_0.png | Bin 5653 -> 5520 bytes 13 files changed, 298 insertions(+), 15 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/src/components/message_composer/message_composer.dart create mode 100644 packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_factory.dart create mode 100644 packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart create mode 100644 packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart create mode 100644 packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart create mode 100644 packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart create mode 100644 packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart create mode 100644 packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart diff --git a/melos.yaml b/melos.yaml index 47e5de7a21..9d31b072df 100644 --- a/melos.yaml +++ b/melos.yaml @@ -95,7 +95,7 @@ command: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: c066cb481bd8a8523e5ea52f3433ffeaeab11332 + ref: f0af49b3cb1426efed625d0a24e58874363e9538 path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer.dart new file mode 100644 index 0000000000..687278691a --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer.dart @@ -0,0 +1 @@ +export 'stream_chat_message_composer.dart'; diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_factory.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_factory.dart new file mode 100644 index 0000000000..9cd9a238ca --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_factory.dart @@ -0,0 +1,28 @@ +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/stream_chat_message_composer.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' show StreamComponentBuilder; + +class StreamMessageComposerFactory { + StreamMessageComposerFactory({ + this.messageComposer, + this.leading, + this.trailing, + this.input, + this.inputLeading, + this.inputHeader, + this.inputTrailing, + }); + + final StreamComponentBuilder? messageComposer; + final StreamComponentBuilder? leading; + final StreamComponentBuilder? trailing; + final StreamComponentBuilder? input; + final StreamComponentBuilder? inputLeading; + final StreamComponentBuilder? inputHeader; + final StreamComponentBuilder? inputTrailing; + + // placeholder for real implementation + static StreamMessageComposerFactory? maybeOf(BuildContext context) { + return StreamMessageComposerFactory(); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart new file mode 100644 index 0000000000..8a7e043b03 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_factory.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +class StreamMessageComposerInputHeader extends StatelessWidget { + const StreamMessageComposerInputHeader({super.key, required this.props}); + + final MessageComposerComponentProps props; + + @override + Widget build(BuildContext context) { + return StreamMessageComposerFactory.maybeOf(context)?.inputHeader?.call(context, props) ?? + DefaultStreamMessageComposerInputHeader(props: props); + } +} + +class DefaultStreamMessageComposerInputHeader extends StatelessWidget { + const DefaultStreamMessageComposerInputHeader({super.key, required this.props}); + + final MessageComposerComponentProps props; + + @override + Widget build(BuildContext context) { + // TODO: implement header for attachments and quoted message + return const SizedBox.shrink(); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart new file mode 100644 index 0000000000..ef257df5b3 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart @@ -0,0 +1,27 @@ +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_factory.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +class StreamMessageComposerInputLeading extends StatelessWidget { + const StreamMessageComposerInputLeading({super.key, required this.props}); + + final MessageComposerComponentProps props; + + @override + Widget build(BuildContext context) { + return StreamMessageComposerFactory.maybeOf(context)?.inputLeading?.call(context, props) ?? + DefaultStreamMessageComposerInputLeading(props: props); + } +} + +class DefaultStreamMessageComposerInputLeading extends StatelessWidget { + const DefaultStreamMessageComposerInputLeading({super.key, required this.props}); + + final MessageComposerComponentProps props; + + @override + Widget build(BuildContext context) { + // Doesn't show anything by default + return const SizedBox.shrink(); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart new file mode 100644 index 0000000000..cf965352c7 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_factory.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/stream_chat_message_composer.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; + +class StreamMessageComposerInputTrailing extends StatelessWidget { + const StreamMessageComposerInputTrailing({super.key, required this.props}); + + final MessageComposerComponentProps props; + + @override + Widget build(BuildContext context) { + return StreamMessageComposerFactory.maybeOf(context)?.inputTrailing?.call(context, props) ?? + core.StreamMessageComposerInputTrailing( + controller: props.controller, + onSendPressed: props.onSendPressed, + onMicrophonePressed: props.onMicrophonePressed, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart new file mode 100644 index 0000000000..ce441479bc --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_factory.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/stream_chat_message_composer.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +class StreamMessageComposerLeading extends StatelessWidget { + const StreamMessageComposerLeading({super.key, required this.props}); + final MessageComposerComponentProps props; + + @override + Widget build(BuildContext context) { + return StreamMessageComposerFactory.maybeOf(context)?.leading?.call(context, props) ?? + DefaultStreamMessageComposerLeading(props: props); + } +} + +class DefaultStreamMessageComposerLeading extends StatelessWidget { + const DefaultStreamMessageComposerLeading({super.key, required this.props}); + + final MessageComposerComponentProps props; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + StreamButton.icon( + icon: context.streamIcons.plusLarge, + style: StreamButtonStyle.secondary, + type: StreamButtonType.outline, + size: StreamButtonSize.large, + onTap: () { + // TODO: Implement attachment picker + }, + ), + SizedBox(width: context.streamSpacing.xs), + ], + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart new file mode 100644 index 0000000000..af02ea317d --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart @@ -0,0 +1,14 @@ +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_factory.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +class StreamMessageComposerTrailing extends StatelessWidget { + const StreamMessageComposerTrailing({super.key, required this.props}); + + final MessageComposerComponentProps props; + + @override + Widget build(BuildContext context) { + return StreamMessageComposerFactory.maybeOf(context)?.trailing?.call(context, props) ?? const SizedBox.shrink(); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart new file mode 100644 index 0000000000..f87bace493 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart @@ -0,0 +1,130 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_factory.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_input_header.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_input_leading.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_input_trailing.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_leading.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_trailing.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; + +class StreamChatMessageComposer extends StatefulWidget { + StreamChatMessageComposer({ + super.key, + this.controller, + required VoidCallback onSendPressed, + VoidCallback? onMicrophonePressed, + FocusNode? focusNode, + String placeholder = '', + }) : props = MessageComposerProps( + isFloating: false, + message: null, + onSendPressed: onSendPressed, + onMicrophonePressed: onMicrophonePressed, + focusNode: focusNode, + placeholder: placeholder, + ); + + final StreamMessageInputController? controller; + final MessageComposerProps props; + + @override + State createState() => _StreamChatMessageComposerState(); +} + +class _StreamChatMessageComposerState extends State { + late MessageTextFieldController _controller; + + @override + void initState() { + super.initState(); + _initController(); + } + + @override + void didUpdateWidget(StreamChatMessageComposer oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller != oldWidget.controller) { + _disposeController(oldWidget); + _initController(); + } + } + + @override + void dispose() { + _disposeController(widget); + super.dispose(); + } + + void _initController() { + _controller = widget.controller?.textFieldController ?? MessageTextFieldController(); + } + + void _disposeController(StreamChatMessageComposer widget) { + if (widget.controller == null) { + _controller.dispose(); + } + } + + @override + Widget build(BuildContext context) { + final componentProps = MessageComposerComponentProps( + controller: _controller, + isFloating: widget.props.isFloating, + message: widget.props.message, + onSendPressed: widget.props.onSendPressed, + onMicrophonePressed: widget.props.onMicrophonePressed, + ); + + return StreamMessageComposerFactory.maybeOf(context)?.messageComposer?.call(context, widget.props) ?? + core.StreamBaseMessageComposer( + placeholder: widget.props.placeholder, + controller: _controller, + isFloating: widget.props.isFloating, + focusNode: widget.props.focusNode, + composerLeading: StreamMessageComposerLeading(props: componentProps), + composerTrailing: StreamMessageComposerTrailing(props: componentProps), + inputHeader: StreamMessageComposerInputHeader(props: componentProps), + inputTrailing: StreamMessageComposerInputTrailing(props: componentProps), + inputLeading: StreamMessageComposerInputLeading(props: componentProps), + ); + } +} + +/// Properties to build the main message composer component +class MessageComposerProps { + const MessageComposerProps({ + this.isFloating = false, + this.message, + this.placeholder = '', + required this.onSendPressed, + this.onMicrophonePressed, + this.focusNode, + }); + + final bool isFloating; + final Message? message; + final String placeholder; + final VoidCallback onSendPressed; + final VoidCallback? onMicrophonePressed; + final FocusNode? focusNode; +} + +/// Properties to build any of the sub-components. +/// These properties are all the same, so features such as 'add attachment', +/// can be added to any of the sub-components. +class MessageComposerComponentProps { + const MessageComposerComponentProps({ + required this.controller, + this.isFloating = false, + this.message, + required this.onSendPressed, + this.onMicrophonePressed, + }); + + final MessageTextFieldController controller; + final bool isFloating; + final Message? message; + final VoidCallback onSendPressed; + final VoidCallback? onMicrophonePressed; +} diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart index d109eff1b8..946f1c0ec5 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart @@ -760,18 +760,11 @@ class StreamMessageInputState extends State with Restoration ) { return StreamMessageValueListenableBuilder( valueListenable: controller, - builder: (context, value, _) => Padding( - padding: widget.padding, - child: Column( - spacing: 8, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _buildTopMessageArea(context), - _buildTextField(context), - _buildDmCheckbox(context), - ].nonNulls.toList(), - ), + builder: (context, value, _) => StreamChatMessageComposer( + controller: controller, + placeholder: _getHint(context) ?? '', + focusNode: focusNode, + onSendPressed: sendMessage, ), ); } diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 95bd028974..0f6dba1a6f 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -2,7 +2,8 @@ export 'package:jiffy/jiffy.dart'; export 'package:photo_manager/photo_manager.dart' show ThumbnailSize, ThumbnailFormat; export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -export 'package:stream_core_flutter/stream_core_flutter.dart' show StreamAvatarSize, StreamTheme; +export 'package:stream_core_flutter/stream_core_flutter.dart' + show StreamAvatarSize, StreamTheme, StreamComponentFactory; export 'src/ai_assistant/ai_typing_indicator_view.dart'; export 'src/ai_assistant/stream_typewriter_builder.dart'; @@ -41,6 +42,7 @@ export 'src/components/avatar/stream_channel_avatar.dart'; export 'src/components/avatar/stream_user_avatar.dart'; export 'src/components/avatar/stream_user_avatar_group.dart'; export 'src/components/avatar/stream_user_avatar_stack.dart'; +export 'src/components/message_composer/message_composer.dart'; // endregion export 'src/fullscreen_media/full_screen_media.dart'; export 'src/fullscreen_media/full_screen_media_builder.dart'; diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 15180dff30..8512892e71 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: c066cb481bd8a8523e5ea52f3433ffeaeab11332 + ref: f0af49b3cb1426efed625d0a24e58874363e9538 path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 diff --git a/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/edit_message_sheet_0.png b/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/edit_message_sheet_0.png index 4e07e17bec853e6d540a6b0c965e157e7ec6130f..79f0440011ad9882af6c03b6c72693b309046486 100644 GIT binary patch literal 5520 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

G(+WzO27F%DakAg@eK1t+``ZOf3-2KIbAju zs;_wuXO_ZFnwwqDCf}SmXvkQ_T;Vc`~!KgQ}H#qNhh;Y!sW*9 ztJc$h_5M&+8u9Val_`Wlyz=Vw&zbxS({2W(>i5|~A~!>AA)=3NxU|G%M+`38r^VD? z-*KMsE4z{@6vu`|5S&gw-wQT;qV%R)Yv%7ynbAu%j%@$U-O5UJYMlQG>mmXo`%fvv z2d{8m!*yGg2Sd=yF}p)6*6gsr9SirYmGINiT9;r)Z?z1vj{TnK%H&;`Lv?spEFZv4 zH^UA;+l;U9!^foN>u7fcCO#iHY2i%{+wfYRF%0j<@6J|)6P_T8-X8fIJ7?!oR_6Pq zvig?P_9sNUb%;=f0}DHnbO?JP+)VhpP-|Y_I){xlSr=+!&#Y0))~wdlxtzvx0ZsZY z+>ZN}SNm7e#5)$$qU>f5fja$a%hv<&NP9 z#)dGO=-WfP*XXD7mZL&wY;Lvs>!n-hLuQ;oPPJdvzTS*!Z2vxK%CybG@aS-sK*YY> zxIX$~r(^Geac5rTjbSDCajBob)y$qL1fQ&~P5s>PCrgs}UA&}783aOyZ@5hFQu59A zACSKXhpe38*2?*OK^lVInf~t$6h$Kni)~h0CkFd_I$rrsQZA*fY-#Y=`pAqdZIp5{ zpvRA)q0Vv5OWUlTRw({Jg#IlnGXWhot(bU(pN(Hr5Bh>l6~1F1?VR*+4%{ zKUqgaV1{Mt8+41LKUPzg$o?Eb&aRY(9c|-T?^*P!! z{?#6Hwo)VQ6i2mP1~e$ToD% zm@F(OePS6r2&%!9DS0N&EGCVB!ysN6Ppn(@U?SWc@ovkR4Z&MEB9Ucj++PAW*@oVO zcmON=CS57HV4e0G%#656(38vmtlSa>N6~U0PzJ2ce!z+uS3<^y%@3 z0rQ-~;rxVs&c()5PI}MWN!AVY^4~Oj9eg{c>n-!W!w6CtY+>=jG4iih(R+Rc1B+w% z1GYopaZ=4O)Gb9Xe`iVH+Y}k;1a$ECMYjD9T3ySkR6eu~ z|H=b8zVSR(zg79o3$;SkxuweiX`C|&&O%sA+a)w@yRBRDDho=V3H8an({3EE2 z`UkdQ2I#94z9f;|rEMz(QD!1U1c*SWaGFk8;*Cocyc1F|$Y4P8%E0%6k=^x^HaNgM z^Uo?~qN!_jDoB% Date: Thu, 19 Feb 2026 11:25:40 +0100 Subject: [PATCH 06/33] chore(ui): update goldens (#2515) * chore: Update Goldens * fix analysis warning --------- Co-authored-by: renefloor <15101411+renefloor@users.noreply.github.com> --- analysis_options.yaml | 2 -- ...m_voice_recording_attachment_idle_dark.png | Bin 5971 -> 5971 bytes ..._voice_recording_attachment_idle_light.png | Bin 6733 -> 6733 bytes ...oice_recording_attachment_playing_dark.png | Bin 5462 -> 5462 bytes ...ice_recording_attachment_playing_light.png | Bin 6374 -> 6374 bytes ...ice_recording_attachment_playlist_dark.png | Bin 14035 -> 14035 bytes ...ce_recording_attachment_playlist_light.png | Bin 14530 -> 14530 bytes .../avatars/goldens/ci/gradient_avatar_0.png | Bin 11931 -> 11931 bytes .../avatars/goldens/ci/gradient_avatar_1.png | Bin 12019 -> 12019 bytes .../avatars/goldens/ci/gradient_avatar_2.png | Bin 11931 -> 11931 bytes .../avatars/goldens/ci/gradient_avatar_3.png | Bin 11953 -> 11953 bytes .../goldens/ci/gradient_avatar_issue_2369.png | Bin 117282 -> 117282 bytes .../src/avatars/goldens/ci/group_avatar_0.png | Bin 2383 -> 2383 bytes .../src/avatars/goldens/ci/user_avatar_0.png | Bin 2716 -> 2716 bytes .../src/avatars/goldens/ci/user_avatar_1.png | Bin 2707 -> 2707 bytes .../goldens/ci/attachment_modal_sheet_0.png | Bin 1237 -> 1237 bytes .../goldens/ci/edit_message_sheet_0.png | Bin 5520 -> 5520 bytes .../goldens/ci/error_alert_sheet_0.png | Bin 1735 -> 1735 bytes .../ci/channel_header_bottom_widget.png | Bin 1906 -> 1906 bytes .../goldens/ci/confirmation_dialog_0.png | Bin 4144 -> 4144 bytes .../goldens/ci/delete_message_dialog_0.png | Bin 4144 -> 4144 bytes .../dialogs/goldens/ci/message_dialog_0.png | Bin 4139 -> 4139 bytes .../dialogs/goldens/ci/message_dialog_1.png | Bin 4220 -> 4220 bytes .../dialogs/goldens/ci/message_dialog_2.png | Bin 4139 -> 4139 bytes .../gallery/goldens/ci/gallery_footer_0.png | Bin 2220 -> 2220 bytes .../gallery/goldens/ci/gallery_header_0.png | Bin 1716 -> 1716 bytes .../icons/goldens/ci/stream_svg_icon_dark.png | Bin 117978 -> 117978 bytes .../goldens/ci/stream_svg_icon_light.png | Bin 118840 -> 118840 bytes .../goldens/ci/sending_indicator_0.png | Bin 374 -> 374 bytes .../goldens/ci/sending_indicator_1.png | Bin 373 -> 373 bytes .../goldens/ci/sending_indicator_2.png | Bin 309 -> 309 bytes .../goldens/ci/sending_indicator_3.png | Bin 508 -> 508 bytes .../ci/upload_progress_indicator_0.png | Bin 1828 -> 1828 bytes .../ci/upload_progress_indicator_1.png | Bin 1818 -> 1818 bytes .../ci/upload_progress_indicator_2.png | Bin 1783 -> 1783 bytes ...essage_action_item_custom_styling_dark.png | Bin 392 -> 392 bytes ...ssage_action_item_custom_styling_light.png | Bin 391 -> 391 bytes ...stream_message_action_item_delete_dark.png | Bin 388 -> 388 bytes ...tream_message_action_item_delete_light.png | Bin 355 -> 355 bytes .../stream_message_action_item_reply_dark.png | Bin 398 -> 398 bytes ...stream_message_action_item_reply_light.png | Bin 358 -> 358 bytes ...stream_audio_recorder_button_idle_dark.png | Bin 1651 -> 1651 bytes ...tream_audio_recorder_button_idle_light.png | Bin 1679 -> 1679 bytes ...io_recorder_button_recording_hold_dark.png | Bin 4885 -> 4885 bytes ...o_recorder_button_recording_hold_light.png | Bin 4838 -> 4838 bytes ..._recorder_button_recording_locked_dark.png | Bin 6711 -> 6711 bytes ...recorder_button_recording_locked_light.png | Bin 6595 -> 6595 bytes ...recorder_button_recording_stopped_dark.png | Bin 5896 -> 5896 bytes ...ecorder_button_recording_stopped_light.png | Bin 5814 -> 5814 bytes .../goldens/ci/attachment_button_0.png | Bin 1354 -> 1354 bytes .../goldens/ci/clear_input_item_0.png | Bin 815 -> 815 bytes .../goldens/ci/command_button_0.png | Bin 534 -> 534 bytes .../goldens/ci/countdown_button_0.png | Bin 809 -> 809 bytes .../moderated_message_actions_modal_dark.png | Bin 2500 -> 2500 bytes .../moderated_message_actions_modal_light.png | Bin 2534 -> 2534 bytes .../ci/stream_message_actions_modal_dark.png | Bin 4796 -> 4796 bytes .../ci/stream_message_actions_modal_light.png | Bin 5087 -> 5087 bytes ...am_message_actions_modal_reversed_dark.png | Bin 4880 -> 4880 bytes ...m_message_actions_modal_reversed_light.png | Bin 5074 -> 5074 bytes ...ons_modal_reversed_with_reactions_dark.png | Bin 8213 -> 8213 bytes ...ns_modal_reversed_with_reactions_light.png | Bin 8796 -> 8796 bytes ...sage_actions_modal_with_reactions_dark.png | Bin 8112 -> 8112 bytes ...age_actions_modal_with_reactions_light.png | Bin 8735 -> 8735 bytes .../stream_message_reactions_modal_dark.png | Bin 10996 -> 10996 bytes .../stream_message_reactions_modal_light.png | Bin 11095 -> 11095 bytes ..._message_reactions_modal_reversed_dark.png | Bin 11095 -> 11095 bytes ...message_reactions_modal_reversed_light.png | Bin 11078 -> 11078 bytes .../goldens/ci/deleted_message_custom.png | Bin 945 -> 945 bytes .../goldens/ci/deleted_message_dark.png | Bin 749 -> 749 bytes .../goldens/ci/deleted_message_light.png | Bin 742 -> 742 bytes .../goldens/ci/message_text.png | Bin 872 -> 872 bytes .../src/misc/goldens/ci/reaction_bubble_2.png | Bin 3206 -> 3206 bytes .../goldens/ci/reaction_bubble_3_dark.png | Bin 2626 -> 2626 bytes .../goldens/ci/reaction_bubble_3_light.png | Bin 2400 -> 2400 bytes .../goldens/ci/reaction_bubble_like_dark.png | Bin 2051 -> 2051 bytes .../goldens/ci/reaction_bubble_like_light.png | Bin 1800 -> 1800 bytes ...ream_audio_waveform_slider_custom_dark.png | Bin 3489 -> 3489 bytes ...eam_audio_waveform_slider_custom_light.png | Bin 3439 -> 3439 bytes .../ci/stream_audio_waveform_slider_dark.png | Bin 3168 -> 3168 bytes ...tream_audio_waveform_slider_empty_dark.png | Bin 988 -> 988 bytes ...ream_audio_waveform_slider_empty_light.png | Bin 976 -> 976 bytes ...am_audio_waveform_slider_inverted_dark.png | Bin 3237 -> 3237 bytes ...m_audio_waveform_slider_inverted_light.png | Bin 3272 -> 3272 bytes ...m_audio_waveform_slider_less_data_dark.png | Bin 2999 -> 2999 bytes ..._audio_waveform_slider_less_data_light.png | Bin 3055 -> 3055 bytes .../ci/stream_audio_waveform_slider_light.png | Bin 3187 -> 3187 bytes ...am_audio_waveform_slider_progress_dark.png | Bin 3476 -> 3476 bytes ...m_audio_waveform_slider_progress_light.png | Bin 3236 -> 3236 bytes .../misc/goldens/ci/stream_timestamp_dark.png | Bin 902 -> 902 bytes .../goldens/ci/stream_timestamp_light.png | Bin 979 -> 979 bytes .../misc/goldens/ci/system_message_dark.png | Bin 651 -> 651 bytes .../misc/goldens/ci/system_message_light.png | Bin 647 -> 647 bytes .../ci/poll_delete_option_dialog_dark.png | Bin 2436 -> 2436 bytes .../ci/poll_delete_option_dialog_light.png | Bin 2490 -> 2490 bytes ...poll_option_reorderable_list_view_dark.png | Bin 6852 -> 6852 bytes ...ption_reorderable_list_view_error_dark.png | Bin 6932 -> 6932 bytes ...tion_reorderable_list_view_error_light.png | Bin 7018 -> 7018 bytes ...oll_option_reorderable_list_view_light.png | Bin 6901 -> 6901 bytes .../ci/poll_question_text_field_dark.png | Bin 1765 -> 1765 bytes .../poll_question_text_field_error_dark.png | Bin 1861 -> 1861 bytes .../poll_question_text_field_error_light.png | Bin 1933 -> 1933 bytes .../ci/poll_question_text_field_light.png | Bin 1803 -> 1803 bytes .../ci/stream_poll_creator_dialog_dark.png | Bin 18739 -> 18739 bytes .../ci/stream_poll_creator_dialog_light.png | Bin 18266 -> 18266 bytes ...m_poll_creator_full_screen_dialog_dark.png | Bin 14138 -> 14138 bytes ..._poll_creator_full_screen_dialog_light.png | Bin 13997 -> 13997 bytes ...poll_option_reorderable_list_view_dark.png | Bin 6852 -> 6852 bytes ...oll_option_reorderable_list_view_error.png | Bin 7018 -> 7018 bytes ...oll_option_reorderable_list_view_light.png | Bin 6901 -> 6901 bytes .../ci/poll_question_text_field_dark.png | Bin 1765 -> 1765 bytes .../ci/poll_question_text_field_error.png | Bin 1933 -> 1933 bytes .../ci/poll_question_text_field_light.png | Bin 1803 -> 1803 bytes .../ci/stream_poll_creator_dialog_dark.png | Bin 18739 -> 18739 bytes .../ci/stream_poll_creator_dialog_light.png | Bin 18266 -> 18266 bytes ...m_poll_creator_full_screen_dialog_dark.png | Bin 14138 -> 14138 bytes ..._poll_creator_full_screen_dialog_light.png | Bin 13997 -> 13997 bytes .../ci/stream_poll_options_dialog_dark.png | Bin 9936 -> 9936 bytes .../ci/stream_poll_options_dialog_light.png | Bin 9630 -> 9630 bytes .../ci/stream_poll_results_dialog_dark.png | Bin 12111 -> 12111 bytes .../ci/stream_poll_results_dialog_light.png | Bin 11145 -> 11145 bytes ...poll_results_dialog_with_show_all_dark.png | Bin 10155 -> 10155 bytes ...oll_results_dialog_with_show_all_light.png | Bin 9473 -> 9473 bytes .../ci/poll_add_comment_dialog_dark.png | Bin 3450 -> 3450 bytes .../ci/poll_add_comment_dialog_light.png | Bin 3469 -> 3469 bytes ...comment_dialog_with_initial_value_dark.png | Bin 3979 -> 3979 bytes ...omment_dialog_with_initial_value_light.png | Bin 4014 -> 4014 bytes .../goldens/ci/poll_end_vote_dialog_dark.png | Bin 2231 -> 2231 bytes .../goldens/ci/poll_end_vote_dialog_light.png | Bin 2272 -> 2272 bytes .../goldens/ci/poll_header_dark.png | Bin 915 -> 915 bytes .../goldens/ci/poll_header_light.png | Bin 977 -> 977 bytes .../ci/poll_header_long_question_dark.png | Bin 1077 -> 1077 bytes .../ci/poll_header_long_question_light.png | Bin 1027 -> 1027 bytes ...l_header_subtitle_voting_mode_all_dark.png | Bin 915 -> 915 bytes ..._header_subtitle_voting_mode_all_light.png | Bin 977 -> 977 bytes ...der_subtitle_voting_mode_disabled_dark.png | Bin 915 -> 915 bytes ...er_subtitle_voting_mode_disabled_light.png | Bin 977 -> 977 bytes ...ader_subtitle_voting_mode_limited_dark.png | Bin 915 -> 915 bytes ...der_subtitle_voting_mode_limited_light.png | Bin 977 -> 977 bytes ...eader_subtitle_voting_mode_unique_dark.png | Bin 915 -> 915 bytes ...ader_subtitle_voting_mode_unique_light.png | Bin 977 -> 977 bytes .../ci/poll_suggest_option_dialog_dark.png | Bin 3457 -> 3457 bytes .../ci/poll_suggest_option_dialog_light.png | Bin 3366 -> 3366 bytes ...option_dialog_with_initial_option_dark.png | Bin 4011 -> 4011 bytes ...ption_dialog_with_initial_option_light.png | Bin 3969 -> 3969 bytes .../ci/stream_poll_interactor_closed_dark.png | Bin 5719 -> 5719 bytes .../stream_poll_interactor_closed_light.png | Bin 5568 -> 5568 bytes .../ci/stream_poll_interactor_dark.png | Bin 7757 -> 7757 bytes .../ci/stream_poll_interactor_light.png | Bin 7522 -> 7522 bytes .../ci/reaction_indicator_icon_list_dark.png | Bin 1644 -> 1644 bytes .../ci/reaction_indicator_icon_list_light.png | Bin 1827 -> 1827 bytes ...tion_indicator_icon_list_selected_dark.png | Bin 1783 -> 1783 bytes ...ion_indicator_icon_list_selected_light.png | Bin 1899 -> 1899 bytes .../ci/stream_reaction_indicator_dark.png | Bin 1458 -> 1458 bytes .../ci/stream_reaction_indicator_light.png | Bin 1605 -> 1605 bytes .../ci/stream_reaction_indicator_own_dark.png | Bin 1259 -> 1259 bytes .../stream_reaction_indicator_own_light.png | Bin 1358 -> 1358 bytes .../ci/reaction_icon_button_selected_dark.png | Bin 649 -> 649 bytes .../reaction_icon_button_selected_light.png | Bin 613 -> 613 bytes .../reaction_icon_button_unselected_dark.png | Bin 564 -> 564 bytes .../reaction_icon_button_unselected_light.png | Bin 621 -> 621 bytes .../ci/reaction_picker_icon_list_dark.png | Bin 2324 -> 2324 bytes .../ci/reaction_picker_icon_list_light.png | Bin 2580 -> 2580 bytes ...eaction_picker_icon_list_selected_dark.png | Bin 2495 -> 2495 bytes ...action_picker_icon_list_selected_light.png | Bin 2645 -> 2645 bytes .../ci/stream_reaction_picker_dark.png | Bin 3635 -> 3635 bytes .../ci/stream_reaction_picker_light.png | Bin 3817 -> 3817 bytes .../stream_reaction_picker_selected_dark.png | Bin 3701 -> 3701 bytes .../stream_reaction_picker_selected_light.png | Bin 3861 -> 3861 bytes .../ci/stream_draft_list_tile_dark.png | Bin 1252 -> 1252 bytes .../ci/stream_draft_list_tile_light.png | Bin 1226 -> 1226 bytes .../ci/stream_thread_list_tile_dark.png | Bin 3314 -> 3314 bytes .../ci/stream_thread_list_tile_light.png | Bin 3150 -> 3150 bytes .../ci/stream_unread_threads_banner_dark.png | Bin 2270 -> 2270 bytes .../ci/stream_unread_threads_banner_light.png | Bin 2277 -> 2277 bytes .../test/message_list_core_test.dart | 12 ++++++------ 175 files changed, 6 insertions(+), 8 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 94dbf883a8..6e7d2449c4 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -23,7 +23,6 @@ linter: - control_flow_in_finally - empty_statements - hash_and_equals - - invariant_booleans - literal_only_boolean_expressions - no_adjacent_strings_in_list - no_duplicate_case_values @@ -86,7 +85,6 @@ linter: - prefer_const_declarations - prefer_const_literals_to_create_immutables - prefer_contains - - prefer_equal_for_default_values - prefer_final_fields - prefer_final_in_for_each - prefer_final_locals diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_dark.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_dark.png index 5b8b18196320c86ed2614c9778cea5cff69bab71..abfbdbe3e9e08ec3db4dbca85000fcf60ff776cb 100644 GIT binary patch delta 37 rcmcbtcUfGZx^prw85kH?ik&<|IDnvrBc)@bov1hf;HL_r diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_light.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_light.png index 3388c25411238965b4e396f4c742d7d675e5892c..eb4b302736131ddba8737c7b8691b8d213563816 100644 GIT binary patch delta 37 rcmX?Wa@J&miWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn9lsO+$v_I9 delta 37 rcmX?Wa@J&miWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b9lsO+;F1cT diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_dark.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_dark.png index f1b39c342268eba6fd333d3414fc896d086775fb..d7164ba4d26a727ceea34ef3b3497149b295e0e6 100644 GIT binary patch delta 37 rcmcbnbxmu6iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnorEX=$r%cv delta 37 rcmcbnbxmu6iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@borEX=;A;w@ diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_light.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_light.png index 142b228787226d06e2a1ba84eb1fb656281576b4..12dd89837624dc9c91823cdf5fa130fc82d43918 100644 GIT binary patch delta 37 rcmaE6_{?yEiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-3kc+(54Gw delta 37 rcmaE6_{?yEiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-3kc+=lBa^ diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_dark.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_dark.png index 3825488b27bde51df919f95473a56025aa540c0b..9847953a7e41efc0a712d9f3ca1a6b476078c1dc 100644 GIT binary patch delta 37 rcmcbddpUQ4iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-BdFG+pY`0 delta 37 rcmcbddpUQ4iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-BdFG^8gFK diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_light.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_light.png index c964b5988707466546bbf5c7029ace1cb195c8a5..f5b00306dac388c2b7e2d2beb44bcfe3f2d25f1f 100644 GIT binary patch delta 37 rcmX?9c&KoKiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnU5f<(*WL@3 delta 37 rcmX?9c&KoKiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bU5f<(?=TCN diff --git a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_0.png b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_0.png index 7069fe981d363de005c12d000c7fcc4124b713f5..6e685f7848cc364e7a0b73c8ced955266c7a16ce 100644 GIT binary patch delta 37 rcmbOoJ3DrQiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnU927e#}*3j delta 37 rcmbOoJ3DrQiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bU927e-e?N% diff --git a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_1.png b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_1.png index 5838bbf505ee510f87bd3e856d7bca78eeb6f122..8779ad28aefedc1fad247a12ce52e6b8a4d532ab 100644 GIT binary patch delta 37 rcmewy`#E-kiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-BvvS;D!tL delta 37 rcmewy`#E-kiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-BvvS_t*>f diff --git a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_2.png b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_2.png index 7069fe981d363de005c12d000c7fcc4124b713f5..6e685f7848cc364e7a0b73c8ced955266c7a16ce 100644 GIT binary patch delta 37 rcmbOoJ3DrQiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnU927e#}*3j delta 37 rcmbOoJ3DrQiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bU927e-e?N% diff --git a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_3.png b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_3.png index c71145083114ecfc7e835b53a5d2b2e6533aecfe..88abc34ded27e59e7b1072192ba9b0a3afdfd81e 100644 GIT binary patch delta 37 rcmdlOyD@fxiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnU9lbj&2kGd delta 37 rcmdlOyD@fxiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bU9lbj0ssSJ4PO8N delta 42 xcmZ29g?-T!_6aIdjKx9jP7LeL$-HD>U|=bB@(kesf*OvLjz+t!c8pI>0stDd4PO8N diff --git a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/group_avatar_0.png b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/group_avatar_0.png index ede411c97b72852c57f135cf70263bf62836d665..367f47bfb5512fe6d48d05d05c95e87dc83203db 100644 GIT binary patch delta 37 rcmX>vbY5tJiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnoggOwz;_Bz delta 37 rcmX>vbY5tJiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@boggOw*U1V{ diff --git a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_0.png b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_0.png index 04fe73a4a7a3a1eb0a442accf6a681c4da806146..ee6d1dff5214644a526f83c3039ffa843e559924 100644 GIT binary patch delta 37 rcmbOuI!APZiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT^ttxvy2Kp delta 37 rcmbOuI!APZiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bT^ttx%H9e- diff --git a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_1.png b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_1.png index 89d6dca4fc43c9fa3b89dd9f63655ff59bfad852..87e44f277cb895cc9dad40a127a2eb084cbd13a7 100644 GIT binary patch delta 37 rcmbO%I$3mriWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT__g-u<{Bc delta 37 rcmbO%I$3mriWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bT__g-$V3Vw diff --git a/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/attachment_modal_sheet_0.png b/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/attachment_modal_sheet_0.png index d37466f32f88a11c606a3dc8966848b2c8a632f9..d61f6d8fcc40f3101990b892547bb1375ec8f36a 100644 GIT binary patch delta 37 rcmcc0d6jd5iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-EGZx^prw85kH?ik&<|IDnvrBc)@b-EGZx^prw85kH?ik&<|IDnvrBc)@bU63dM&2GZx^prw85kH?ik&<|IDnvrBc)@bT_+m=)fft^ diff --git a/packages/stream_chat_flutter/test/src/channel/goldens/ci/channel_header_bottom_widget.png b/packages/stream_chat_flutter/test/src/channel/goldens/ci/channel_header_bottom_widget.png index bdee9d01d8530b43c936aed6558dbeffb0d513c2..0e7a46ffdd6380eb4e73c5ac8f57872b9fff5f77 100644 GIT binary patch delta 37 rcmeyw_la+UiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnoe4Vt$$JW~ delta 37 rcmeyw_la+UiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@boe4Vt;LQrJ diff --git a/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/confirmation_dialog_0.png b/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/confirmation_dialog_0.png index 4c69a1e814acdbce9c7aa07a39691ad3653a9e55..c426684cc65bff3651582636c67fb4fab909320e 100644 GIT binary patch delta 37 rcmdm>ut8yhiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-6wtky;}ut8yhiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-6wtk)U68@ diff --git a/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/delete_message_dialog_0.png b/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/delete_message_dialog_0.png index 53706b4f8ce0f74a6c76535001de851183cb5347..99f0dc354b78cd0bd755bd1ef97b439519ae3805 100644 GIT binary patch delta 37 rcmdm>ut8yhiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-6wtky;}ut8yhiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-6wtk)U68@ diff --git a/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_0.png b/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_0.png index 0e3074511d24603bf3c5190c3cdef89e60d7723c..ef6313474e4aa8253e1382fa67675afdf4cc8368 100644 GIT binary patch delta 37 rcmZ3juv%e)iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-CKSDyXgxC delta 37 rcmZ3juv%e)iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-CKSD(>n_W diff --git a/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_1.png b/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_1.png index 0e6d1770b80266223b6749dedd3b21396f561257..2dd9ed245dec9dd332f30b79d95354cf330f7e16 100644 GIT binary patch delta 37 rcmeyP@JC^SiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnot*#x(Nqfh delta 37 rcmeyP@JC^SiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bot*#x=%xz# diff --git a/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_2.png b/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_2.png index 0e3074511d24603bf3c5190c3cdef89e60d7723c..ef6313474e4aa8253e1382fa67675afdf4cc8368 100644 GIT binary patch delta 37 rcmZ3juv%e)iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-CKSDyXgxC delta 37 rcmZ3juv%e)iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-CKSD(>n_W diff --git a/packages/stream_chat_flutter/test/src/gallery/goldens/ci/gallery_footer_0.png b/packages/stream_chat_flutter/test/src/gallery/goldens/ci/gallery_footer_0.png index 7217acb047df2c0352bd4399479b8d19ffb8e098..2db682a07a7de4d2314dc01f415ce94656d68715 100644 GIT binary patch delta 37 rcmZ1@xJGb-iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT^GZx^prw85kH?ik&<|IDnvrBc)@bT^GZx^prw85kH?ik&<|IDnvrBc)@bT^Sny&zcHy diff --git a/packages/stream_chat_flutter/test/src/icons/goldens/ci/stream_svg_icon_dark.png b/packages/stream_chat_flutter/test/src/icons/goldens/ci/stream_svg_icon_dark.png index e498b63bf8f89bc590653c3aeac89b41dc910b86..a18102896018a17bc64920c90de26e26aa5e4624 100644 GIT binary patch delta 42 xcmcaLll|6A_6aIdEX7WqAsj$Z!;#X#z`(#*9OUlAuU|=bB@(kesf*OvLjz+t!c8qh*001p(4U_-? diff --git a/packages/stream_chat_flutter/test/src/icons/goldens/ci/stream_svg_icon_light.png b/packages/stream_chat_flutter/test/src/icons/goldens/ci/stream_svg_icon_light.png index 42eefb8b114dfcdee3c1019c07bec35d16b01305..8098dacd46baaf8fb8526b2b82c37ae2139b0744 100644 GIT binary patch delta 42 xcmdn7fPKdT_6aIdEX7WqAsj$Z!;#X#z`(#*9OUlAuY delta 42 xcmdn7fPKdT_6aIdjKx9jP7LeL$-HD>U|=bB@(kesf*OvLjz+t!c8tHy0{|tF4Zi>Y diff --git a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_0.png b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_0.png index 838561febdb82f6e1b6b41683c469919e493c52a..7b56538eb8203a26651926e45675fc0d7dda9809 100644 GIT binary patch delta 37 rcmeyy^o?nPiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnodqKR$5jfQ delta 37 rcmeyy^o?nPiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bodqKR-lqzk diff --git a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_1.png b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_1.png index 9185f126567283fd0ab1c561ea0d93af70ffbb0c..de7cd95a6d5db02b058d65a5405322996de60611 100644 GIT binary patch delta 37 rcmey$^p$CXiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnojD@_#|sLY delta 37 rcmey$^p$CXiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bojD@_-dzfs diff --git a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_2.png b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_2.png index 06e22f1b1a4ce12415f6f55a8e73d0633f836230..907fb75960dfb5f1fe068bb84887a769cbfa3777 100644 GIT binary patch delta 36 qcmdnWw3TUsiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWm-FE=1Q3|;L delta 23 fcmdnWw3TUs3KwH>kh>GZx^prwCn^e0bo>GUTrLQR diff --git a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_3.png b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_3.png index 31704acd50e36ef69175a9aef6713748de2e63a5..ce0e27e0d9f847ae7ecc5b1f2f207bf58d713a16 100644 GIT binary patch delta 37 rcmeyv{D*mhiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-9APD%2x|9 delta 37 rcmeyv{D*mhiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-9APD;i(HT diff --git a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_0.png b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_0.png index 4724897fe5647f75e5fb048d197c2780ca4a5f72..834961f86adbe516d59d5d8da44e75335c6118a7 100644 GIT binary patch delta 37 rcmZ3&w}fwkiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-7_`-wC)PT delta 37 rcmZ3&w}fwkiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-7_`-%s>jn diff --git a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_1.png b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_1.png index 3ee1a563914cf45b2d7c513c3ba5f0b099050c82..419582b6f3eb3bc1ef73d54ab44ea5e7901ed559 100644 GIT binary patch delta 37 rcmbQmH;ZqAiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-5oXnvI+{O delta 37 rcmbQmH;ZqAiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-5oXn$y^Gi diff --git a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_2.png b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_2.png index 8d3d9a3c786f0e5479a2b149771409b9b36be093..e53612c8e751729702236e270cc9e70aef446296 100644 GIT binary patch delta 37 rcmey)`<-`!iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-A*GZx^prw85kH?ik&<|IDnvrBc)@b-A*6GZx^prw85kH?ik&<|IDnvrBc)@bohl;$*(nNS diff --git a/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_reply_dark.png b/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_reply_dark.png index eb2407025e718d7b848b434f04fa3a5b86da1c9f..be0eb556f464928da8d3ccc04864ef42dcd41d87 100644 GIT binary patch delta 37 rcmeBU?qi;yBE?edGZx^prw85kH?ik&<|IDnvrBc)@bodzQS+6M}4 diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_idle_dark.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_idle_dark.png index 7f8b19262c0ba7dad297b3328c03de8d08ef9c71..8d1f6af7c5ed2b30172e0cf518732f22171baec4 100644 GIT binary patch delta 37 rcmey&^OGZx^prw85kH?ik&<|IDnvrBc)@bohcgt;ED>Z diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_idle_light.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_idle_light.png index 619207c4bf1557f5a01a46a0d78654493f2b1c6b..8b4288a46b2fc614e072a8397fe7eb7c019b212d 100644 GIT binary patch delta 37 rcmeC@?dP4KBE?edGZx^prw85kH?ik&<|IDnvrBc)@b-E|=V&V~xp diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_light.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_light.png index 707603a471eb97a6750ebe4a180636ba0a12c3b5..379343902f00503c3bb6cdfdc06d528dcd6606cc 100644 GIT binary patch delta 37 rcmaE+`b>3#iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-3lQ9%~%UW delta 37 rcmaE+`b>3#iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-3lQ9GZx^prw85kH?ik&<|IDnvrBc)@b-A_pX+%*ea diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_light.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_light.png index 48f4d73ae38fb616bb84d7e55044a819811e867a..68d318212d593cdee92b9c374517bfc7bbd957a0 100644 GIT binary patch delta 37 rcmX?XeAsw`iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnU8^Jj#}x|x delta 37 rcmX?XeAsw`iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bU8^Jj-e(H_ diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_dark.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_dark.png index fe3753a23bffa4698d0d6fb95bc02774de299ae8..c669f3b8de87f3976a021c23910c0dee149573a8 100644 GIT binary patch delta 37 rcmeCs>(HB^BE?ed(HB^BE?u7GZx^prw85kH?ik&<|IDnvrBc)@bU4<9`*!~L0 diff --git a/packages/stream_chat_flutter/test/src/message_input/goldens/ci/attachment_button_0.png b/packages/stream_chat_flutter/test/src/message_input/goldens/ci/attachment_button_0.png index b7f33c75392946f9d1b342a54da778a7a306487d..0f0cafd3de1d69b7c03857b4f9d94e0b228d84b4 100644 GIT binary patch delta 37 rcmX@bb&6|(iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn9SGZx^prw85kH?ik&<|IDnvrBc)@b9Swde}b delta 37 rcmZ3_ww`T*iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-A5(>%{mIv diff --git a/packages/stream_chat_flutter/test/src/message_input/goldens/ci/command_button_0.png b/packages/stream_chat_flutter/test/src/message_input/goldens/ci/command_button_0.png index 588d58060220f19af46297aa82093da97f5d18cc..755d3e5dabf601716fb1efed1b54e943f5b19928 100644 GIT binary patch delta 37 rcmbQnGL2<|iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-3>+nt|1D6 delta 37 rcmbQnGL2<|iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-3>+n#d8XQ diff --git a/packages/stream_chat_flutter/test/src/message_input/goldens/ci/countdown_button_0.png b/packages/stream_chat_flutter/test/src/message_input/goldens/ci/countdown_button_0.png index 06b386f836733b3af444ded200d9ef62245e2e6d..8f5524d2818f1c4e019d854165ae007e7add1b4e 100644 GIT binary patch delta 37 rcmZ3GZx^prw85kH?ik&<|IDnvrBc)@b-D@TQ%YF*K diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/moderated_message_actions_modal_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/moderated_message_actions_modal_dark.png index 0c67a41ec5b08d1c66334138f55559658ac281c8..5ed84feb64877f62643b4737d0bbf68a8f4438dd 100644 GIT binary patch delta 37 rcmX>id_;JHiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT^lC=zK;sA delta 37 rcmX>id_;JHiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bT^lC=)!_=U diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/moderated_message_actions_modal_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/moderated_message_actions_modal_light.png index 97ecba385dacde4a713fb73e00d3192a5987c5df..be72c13d3f2e7201585926eea2fc317a49c12d69 100644 GIT binary patch delta 37 rcmaDR{7iU)iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-3m?s$YTo^ delta 37 rcmaDR{7iU)iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-3m?s-?a-D diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_dark.png index fa7735596546629737153d1c43f9a05f1a144ffd..8bf73f3a8251dce87474a3f6b1c8c84dfd7c754e 100644 GIT binary patch delta 37 rcmdm^x<_?_iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnU7ZjB!88iS delta 37 rcmdm^x<_?_iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bU7ZjB*oF$m diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_light.png index 84c8a06ad9a1015af42029cec84168c2369e5c18..58a918211c66d5a633a75367bb88b5b7ad0d244d 100644 GIT binary patch delta 37 rcmcbweqVipiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-9lji%ijw$ delta 37 rcmcbweqVipiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-9lji<1q^~ diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_dark.png index 9c7b02944faa602fecd13df4ee6329ffb5e22199..37bb042033b935748c7daa8d56e9fcc99bdb2a8e 100644 GIT binary patch delta 37 rcmbQBHbHHIiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-6bIawZaO- delta 37 rcmbQBHbHHIiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-6bIa%@hj6 diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_light.png index 0e7a90ddbd4f15f18a2f861fe1fe2e41c0bc0cb4..101f86692c5c9bf2eb712d7eebec49efdb75afc2 100644 GIT binary patch delta 37 rcmcbleo1|TiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-4tN}$Q=s} delta 37 rcmcbleo1|TiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-4tN}-){>I diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_dark.png index 70f016acae93cacd099f0e643944ba5a6e4f811c..464a49572d82ec1ea24b4f182c586fa37b6c1a50 100644 GIT binary patch delta 37 rcmbR0Fx6p#iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-F0~YzFi9x delta 37 rcmbR0Fx6p#iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-F0~Y)vpT_ diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_light.png index 092fc3b0dfc596c61095df7ba5b2f10dbf3c832c..fc1c9c1a1d958253f6ce07e55f8d28c7408a5727 100644 GIT binary patch delta 37 rcmccPa>r$YiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnotzQ?(f$hb delta 37 rcmccPa>r$YiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@botzQ?=}-#v diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_with_reactions_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_with_reactions_dark.png index 68182b20f4c93cdb1c187db50a0c8342c908854c..d9ed843efc723d330be491531aebc5cbbe6143cd 100644 GIT binary patch delta 37 rcmdmBzrlWjiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnU6DKh#N`U( delta 37 rcmdmBzrlWjiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bU6DKh+&2p2 diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_with_reactions_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_with_reactions_light.png index 6392c936be58425fe86715617b0031f18f7b6298..db59b1021d661153c149a87b34747d6bd565bed6 100644 GIT binary patch delta 37 rcmbR5GT&u_iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-9tqH!dnYG delta 37 rcmbR5GT&u_iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-9tqH*{usa diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png index b41bde7824895336ccdfb73e7cbf917ac698ee22..b5e37925d1aad939b2d88c18a43272c7b5e0ab23 100644 GIT binary patch delta 37 rcmewo`XzLNiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-8L-%-kb~O delta 37 rcmewo`XzLNiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-8L-%_3jJi diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png index 90c778792ccad16b7c01a7212ada2755f6ae6903..8b9e2194653b898568913772561a342dcbbd20bb 100644 GIT binary patch delta 37 rcmcZ}c0Fu@iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnouoDZ)qx8V delta 37 rcmcZ}c0Fu@iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bouoDZ?9&Sp diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_dark.png index 0726eb579f3c0cfdebdf79f14d5c1adf9116dd3a..d73843ad3664dbdb11846d05d08e63d35d742064 100644 GIT binary patch delta 37 rcmcZ}c0Fu@iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnouoDZ)qx8V delta 37 rcmcZ}c0Fu@iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bouoDZ?9&Sp diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_light.png index 37eae4546de9e5f63d409ee00aef57feea908e0e..dce33ea2221cc3452d6bee96e5680050392eec48 100644 GIT binary patch delta 37 rcmX>Wb}VdyiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn9fvjm(3c9| delta 37 rcmX>Wb}VdyiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b9fvjm=jjUH diff --git a/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_custom.png b/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_custom.png index 17676e62babc53ab4e79f12b7eee8a489af3ef38..3fcf5ac84b8d27d0b28d0e54db74b93eeb4f6888 100644 GIT binary patch delta 37 rcmdnUzL9-`iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT`@BNwZjTp delta 37 rcmdnUzL9-`iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bT`@BN%@qn- diff --git a/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_dark.png b/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_dark.png index 8c45821b842d263ed3e017960d186e213b45a086..e1af3e4ee3f3044adae8e260ee8a5708a44fec61 100644 GIT binary patch delta 37 rcmaFM`j&NqiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-FhYf#*PaK delta 37 rcmaFM`j&NqiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-FhYf-QWue diff --git a/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_light.png b/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_light.png index d61bcdba89c07f7860d098eb6f7b6c4383d69493..7d70319b7c70ea27634f43ca0f34ceee6153b9cf 100644 GIT binary patch delta 37 rcmaFH`iymgiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-3lfE#E1&? delta 37 rcmaFH`iymgiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-3lfE+u92B diff --git a/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/message_text.png b/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/message_text.png index 656da4e1ccd7f01a7bba14e0f0025aa9294c5693..819380daba8abdce8691e5d57f5ed58cadf20ce9 100644 GIT binary patch delta 37 rcmaFC_JVDKiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnofb0y#A6D5 delta 37 rcmaFC_JVDKiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bofb0y+qDXP diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_2.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_2.png index e78fe9bfe4bf6e4dc147d226131cbb8fb066505c..b982f08bc45749524dd45abe62b6095d7e6f0ef4 100644 GIT binary patch delta 37 rcmZpZY?GXzBE?edka!6!?iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn9Sau#y+R5u delta 37 rcmX>ka!6!?iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b9Sau#)RYP? diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_3_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_3_light.png index d4de76348620c05c99c0e43da5adcd306b69ab32..4bef4095f2de6fdb794ecd45b11351bff0c3907d 100644 GIT binary patch delta 37 rcmaDL^gw8WiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnof0Pi#bFAA delta 37 rcmaDL^gw8WiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bof0Pi+_MUU diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_like_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_like_dark.png index 08681c327fba9a11d8a5b6a8a6d36f9ae8a74fd9..f3cfab3aa4756d89650042b68674673a9c09cf5a 100644 GIT binary patch delta 37 rcmZn`Xcm~DBE?ed)@N9BE?ed)@N9BE?u7GZx^prw85kH?ik&<|IDnvrBc)@bT{15K&G!mi diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_custom_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_custom_light.png index 6cade430e45b896b7b604d44a0a4b57091745176..2a80d9629c367b85dfc40f3c28d74ecc0947f478 100644 GIT binary patch delta 37 rcmaDa^GZx^prw85kH?ik&<|IDnvrBc)@bogps(<2?$* diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_dark.png index a5cfa0663a4db0e269985e61d59cdd9adf240caa..bb548e48e189e1a8c5eed706f5fbd3f01bd12d2e 100644 GIT binary patch delta 37 rcmaDL@jzmNiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnoe~cK#|R3N delta 37 rcmaDL@jzmNiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@boe~cK-dYNh diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_dark.png index b1cfc6c8715f7b1ffce7a2e8c9addf9139549024..ae3ce8928f009093096b56d9c393600b077a7494 100644 GIT binary patch delta 37 rcmcb^eusU6iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-8^Oh!Z8Zl delta 37 rcmcb^eusU6iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-8^Oh*@Ft( diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_light.png index c13687d05ca7bd5310a1536aa3c545a48e301296..8397663d6ddb2f30eeebfeeb795a56b914eeb822 100644 GIT binary patch delta 37 rcmcb>et~_0iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-6UoJzPSpw delta 37 rcmcb>et~_0iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-6UoJ)(Z-^ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_inverted_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_inverted_dark.png index 5ed2a156ac69b311ad368f70b44fe14ac940adfb..273cc75f7adb0f677080d048804805ce44749ab4 100644 GIT binary patch delta 37 rcmZ1~xm0q3iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT{;f{w?GPG delta 37 rcmZ1~xm0q3iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bT{;f{&XNja diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_inverted_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_inverted_light.png index 685f779862a9faab874c35e94e5a5376fbc77a5c..eaf00e0de6ceb5981d3719292360e087855eb72e 100644 GIT binary patch delta 37 rcmX>hc|vl6iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT^A1k!Cnf? delta 37 rcmX>hc|vl6iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bT^A1k*su!B diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_dark.png index da0889c86b1b4a217deefca7a87e6560e39db6f9..b145cfc8fc8497a31c0b79c2b404d34722057354 100644 GIT binary patch delta 37 rcmdlkzFmBRiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT_raFyXOj& delta 37 rcmdlkzFmBRiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bT_raF(>V&1 diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_light.png index efb656ce3b3aa6c15bc2eb020c6a994af9797615..dac6d91b585f5ef0d2f625026f00ab8084fb3890 100644 GIT binary patch delta 37 rcmaDa{$6~7iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-9~Nz%ohth delta 37 rcmaDa{$6~7iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-9~Nz<7o># diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_light.png index 38ee4cfbd0ce9810aa7db4f6054affa4ddcd379f..fd1176a5914e165b367f1045627acccab84f94b7 100644 GIT binary patch delta 37 rcmew?@mXSmiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnohc6h%!Uff delta 37 rcmew?@mXSmiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bohc6hGZx^prw85kH?ik&<|IDnvrBc)@bT^KI_$~6i# diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_progress_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_progress_light.png index 750abddc9d44cff3f6b32765b54718602ab8f517..1c58b0d0be6caa5a3eb257e6382b25ac42b4f07c 100644 GIT binary patch delta 37 rcmZ1?xkPe;iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT^bJnw)P5O delta 37 rcmZ1?xkPe;iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bT^bJn&PWPi diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_timestamp_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_timestamp_dark.png index 90adba2dc02bf74b8f2159b5472a1f30cd74f9d8..4581216051b4268622ce9d06f53f51d5ab5f462c 100644 GIT binary patch delta 37 rcmZo;Z)2aJBE?ed diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_timestamp_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_timestamp_light.png index 208b7b9270be7a48a9bae64ae4b382b2220bb759..0fe2ab210ecaf1d256401ee0df0b53358b0ea446 100644 GIT binary patch delta 37 rcmcc2ewlrOiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-Be}(zn2QY delta 37 rcmcc2ewlrOiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-Be}(*69ks diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/system_message_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/system_message_dark.png index 5f48f32b097fe6b078aa70cb3d78a39095db476f..3c10fdf7e311ff379cbbdbf6e25fa5b3b41b9a1f 100644 GIT binary patch delta 37 rcmeBX?Pi^zBE?edZV{fKBE?edZV{fKBE?u7W diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_delete_option_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_delete_option_dialog_light.png index 78fbfc5a0f2eb0d60502a362801c2455cb7fb6e3..72d412692d4f89681898c6874e5fea6011307227 100644 GIT binary patch delta 37 rcmdlbyi0h3iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT@5DyyQ>P5 delta 37 rcmdlbyi0h3iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bT@5Dy()|jP diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_dark.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_dark.png index e6376fec02eb37a3669848554a16aaf09dd72b5c..fcab3abfa60c8820f665f465e0c2b3eb29593df0 100644 GIT binary patch delta 37 rcmX?Ndc<^siWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnU7HjD$LtFR delta 37 rcmX?Ndc<^siWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bU7HjD-#!Zl diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_error_dark.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_error_dark.png index f10d8de29133cf2383c38bcb9ad8a4d6d4a4fc11..67d917ca8ce1fb9671084171bf7e54c639c53601 100644 GIT binary patch delta 37 rcmbPYHpOg$iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-8CryyHX1H delta 37 rcmbPYHpOg$iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-8Cry(xeLb diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_error_light.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_error_light.png index 69bcfc0c418ac7e192f658faedf21e2f832a5216..b3c801869c3257ac3f565f4005322b1afe5e9042 100644 GIT binary patch delta 37 rcmaE5_R4I6iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnosKjB(k}}8 delta 37 rcmaE5_R4I6iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bosKjB>46IS diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_light.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_light.png index c19384d84af914e9c866d768a5ab383501019eb9..4b3accffd16d704fefc54d8356c0a75f72ee9969 100644 GIT binary patch delta 37 rcmexr`qgxTiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-F7Je))oty delta 37 rcmexr`qgxTiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-F7Je?Pv>` diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_dark.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_dark.png index 57ff5d753270421aaaf70e56914df3c6335a977f..fdffc5c05a2fe6543a234247f66e9444904fe549 100644 GIT binary patch delta 37 rcmaFL`;>QriWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-EuYn#&Qb< delta 37 rcmaFL`;>QriWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-EuYn-NXw8 diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_error_dark.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_error_dark.png index 2099c1e4e3dc655dbee5fc1bc185cf0b29bb35a2..325781cf82143f3b42b20e54418931faa360281d 100644 GIT binary patch delta 37 rcmX@gca(2}iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn9XmS!ym<;J delta 37 rcmX@gca(2}iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b9XmS!)5{7d diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_error_light.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_error_light.png index 64a2f93e583aa14d0894073dbfba9dcff1fd82fb..64ac751f023ffc33f50b50939a5c42cb343550d5 100644 GIT binary patch delta 37 rcmeC>@8zGMBE?ed@8zGMBE?u7*kxFBE?ed*kxFBE?u7U|=bB@(kesf*OvLj*WI-JplO#3_Sn< diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_dialog_light.png index ae58aabff3c377353d47b345b16bff5e85873913..cf9d67caee11ec55804228c2ee54303446e630d9 100644 GIT binary patch delta 39 tcmccB$9Sucae|5zORGZx^prw85kH?ik&<|IDnvrBc)@b-5)am?FGZx^prw85kH?ik&<|IDnvrBc)@bUA`Fr=mZN* diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_dark.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_dark.png index e6376fec02eb37a3669848554a16aaf09dd72b5c..fcab3abfa60c8820f665f465e0c2b3eb29593df0 100644 GIT binary patch delta 37 rcmX?Ndc<^siWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnU7HjD$LtFR delta 37 rcmX?Ndc<^siWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bU7HjD-#!Zl diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_error.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_error.png index 69bcfc0c418ac7e192f658faedf21e2f832a5216..b3c801869c3257ac3f565f4005322b1afe5e9042 100644 GIT binary patch delta 37 rcmaE5_R4I6iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnosKjB(k}}8 delta 37 rcmaE5_R4I6iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bosKjB>46IS diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_light.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_light.png index c19384d84af914e9c866d768a5ab383501019eb9..4b3accffd16d704fefc54d8356c0a75f72ee9969 100644 GIT binary patch delta 37 rcmexr`qgxTiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-F7Je))oty delta 37 rcmexr`qgxTiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-F7Je?Pv>` diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_question_text_field_dark.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_question_text_field_dark.png index 57ff5d753270421aaaf70e56914df3c6335a977f..fdffc5c05a2fe6543a234247f66e9444904fe549 100644 GIT binary patch delta 37 rcmaFL`;>QriWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-EuYn#&Qb< delta 37 rcmaFL`;>QriWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-EuYn-NXw8 diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_question_text_field_error.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_question_text_field_error.png index 64a2f93e583aa14d0894073dbfba9dcff1fd82fb..64ac751f023ffc33f50b50939a5c42cb343550d5 100644 GIT binary patch delta 37 rcmeC>@8zGMBE?ed@8zGMBE?u7*kxFBE?ed*kxFBE?u7U|=bB@(kesf*OvLj*WI-JplO#3_Sn< diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_dialog_light.png index ae58aabff3c377353d47b345b16bff5e85873913..cf9d67caee11ec55804228c2ee54303446e630d9 100644 GIT binary patch delta 39 tcmccB$9Sucae|5zORGZx^prw85kH?ik&<|IDnvrBc)@b-5)am?FGZx^prw85kH?ik&<|IDnvrBc)@bUA`Fr=mZN* diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_dark.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_dark.png index fe7bb92296690e57c00778ad3b66a19db52d9064..b0c221516224ab0be605bfebfeb922e71a090b04 100644 GIT binary patch delta 37 rcmccMd%<^tiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-6S;t(f|u) delta 37 rcmccMd%<^tiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-6S;t=~4@3 diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_light.png index 38c2754090b4c46139ee77e7e1e63fccd0641afe..0dae88575068d587532394d9700db57896964164 100644 GIT binary patch delta 37 rcmbQ|JGZx^prw85kH?ik&<|IDnvrBc)@bU4kkA+EEJ2 diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_dark.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_dark.png index 987180d0fd865486ef10b317cd22c973e9b386f0..c207861cf4ca14fa2b72deca922b9ed1931340e8 100644 GIT binary patch delta 37 rcmX>fcRp@{iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnouEDd)ny9~ delta 37 rcmX>fcRp@{iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bouEDd?6(UJ diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_light.png index 1b3ce9148ec2765836eb5540c4422f7203125ebe..04d612abfe65539bf4b64c84fecdf072bea5dcf4 100644 GIT binary patch delta 37 rcmeAS?+l-yBE?edGZx^prw85kH?ik&<|IDnvrBc)@bU9LI+-z^IK diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_light.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_light.png index 2bf4181e7cd29eb154e0b4b450f19ac708b54d1e..d404cda7268e6ea3e76c433db7a4eae91f7ffa49 100644 GIT binary patch delta 37 rcmZqlYV?|*BE?edGZx^prw85kH?ik&<|IDnvrBc)@boeeJl=4%S& diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_light.png index e7512a60ca78e01544b7c789cd04a0749128d0d9..5785a75e815a2d4ea63fb3ec7e6ca0e777b84996 100644 GIT binary patch delta 37 rcmeB`?vY diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_with_initial_value_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_with_initial_value_dark.png index 003933a7b2ed5ee65c3f7cf305f7d8b1191b9257..177e3ab4896550ac051413ba11555887da323823 100644 GIT binary patch delta 37 rcmeB{@0OpSBE?ed(D;yMYRg delta 37 rcmZ1{zfOLFiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bT>(D;($fl! diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_end_vote_dialog_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_end_vote_dialog_dark.png index 33575c2c74683d7e6add04986ec83f3333e71c0b..9268dd838b790ca5a6bd2b93ac4f1a59fdd083a7 100644 GIT binary patch delta 37 rcmdlkxLt69iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT_pzqxGZx^prw85kH?ik&<|IDnvrBc)@bT_pzq(UJ;< diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_end_vote_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_end_vote_dialog_light.png index 890bc6c0519a6ff3696fa43e5387a1d784ad0a88..8cd7b7a789d576940922bd1561dc3fd7c3f2a372 100644 GIT binary patch delta 37 rcmaDL_&{)iiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-69SE#u^I% delta 37 rcmaDL_&{)iiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-69SE-E0d0 diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_dark.png index f3c13cc1da277c19b38a8434481c8725e20f07e2..9abd07ad6bc8130b9b83ca2dff9be908001b2394 100644 GIT binary patch delta 37 rcmbQtKAC-jiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT_`gEtrrRa delta 37 rcmbQtKAC-jiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bT_`gE#Aylu diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_light.png index dccb508da19b200ef1df2c654410da083ddf15ec..65d45efa34da82fba3534b87f31b6a2d28b90cf3 100644 GIT binary patch delta 37 rcmcb}evy5GiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-DG9}zXJ-o delta 37 rcmcb}evy5GiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-DG9})>R6+ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_long_question_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_long_question_dark.png index 2feca6a6e6a59b5be062b671e4cc8a36eb63650d..78901b4d61a6617b6d1e5d7d3e468642b636bd1b 100644 GIT binary patch delta 37 rcmdnWv6W+jiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-FIdHxG@Uo delta 37 rcmdnWv6W+jiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-FIdH&w~o+ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_long_question_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_long_question_light.png index 9c384b4be42351b3e624cccc2d2bc2118bb12c4d..33261d08bf3d5fa25598a398380a90bb694fc321 100644 GIT binary patch delta 37 rcmZqXXy%xpBE?edj diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_all_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_all_dark.png index 7c4097df915ed3f84fb1bf6ba68c681923be9562..4b608ee0183437ff04df54cc4ba757694400ab14 100644 GIT binary patch delta 37 rcmbQtKAC-jiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT_`gEtrrRa delta 37 rcmbQtKAC-jiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bT_`gE#Aylu diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_all_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_all_light.png index c702d77baee57b24d905997fe0634a82c63469e4..c89e40cc732b440dd09284f86b7ef4a1f64df699 100644 GIT binary patch delta 37 rcmcb}evy5GiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-DG9}zXJ-o delta 37 rcmcb}evy5GiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-DG9})>R6+ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_disabled_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_disabled_dark.png index a91b1aab73199e76ceb9dbc31bd983d62741b067..b803e57eecf09c2caf13423e2c8533544a30a255 100644 GIT binary patch delta 37 rcmbQtKAC-jiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT_`gEtrrRa delta 37 rcmbQtKAC-jiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bT_`gE#Aylu diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_disabled_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_disabled_light.png index be52cf300be2e430766fc167efcce41ee2272652..bf03493cf8f46b6ff5b96750dd78b73c270ec983 100644 GIT binary patch delta 37 rcmcb}evy5GiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-DG9}zXJ-o delta 37 rcmcb}evy5GiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-DG9})>R6+ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_limited_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_limited_dark.png index 58c7540d6aaaa8a7f6a63a5e0f2af801a3e76597..f0c4fb26fb80a2f73a94da3686f915ca37359d49 100644 GIT binary patch delta 37 rcmbQtKAC-jiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT_`gEtrrRa delta 37 rcmbQtKAC-jiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bT_`gE#Aylu diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_limited_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_limited_light.png index 83920a60db6d30ac8cdf10e3352acadc9cdd4fa8..9f630e79040b99e54aaa8922cb81a15373c93b25 100644 GIT binary patch delta 37 rcmcb}evy5GiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-DG9}zXJ-o delta 37 rcmcb}evy5GiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-DG9})>R6+ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_unique_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_unique_dark.png index f3c13cc1da277c19b38a8434481c8725e20f07e2..9abd07ad6bc8130b9b83ca2dff9be908001b2394 100644 GIT binary patch delta 37 rcmbQtKAC-jiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT_`gEtrrRa delta 37 rcmbQtKAC-jiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bT_`gE#Aylu diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_unique_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_unique_light.png index dccb508da19b200ef1df2c654410da083ddf15ec..65d45efa34da82fba3534b87f31b6a2d28b90cf3 100644 GIT binary patch delta 37 rcmcb}evy5GiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-DG9}zXJ-o delta 37 rcmcb}evy5GiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-DG9})>R6+ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_dark.png index e88c31e00dc01b452af86436ecbd139f34609758..3839ac31d239c30443d02269c5a101fef846071f 100644 GIT binary patch delta 37 rcmZpaZj_#&BE?edGZx^prw85kH?ik&<|IDnvrBc)@b-3uN7&>{-x diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_with_initial_option_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_with_initial_option_dark.png index 89e22005a34034c4864c6953f4da581b1c2f449a..dc69423fe0883a96e51b55eed403357dd5088b77 100644 GIT binary patch delta 37 rcmZ22zgm8RiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT`oTWx}yq& delta 37 rcmZ22zgm8RiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bT`oTW(e(<1 diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_with_initial_option_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_with_initial_option_light.png index 2689a949c41091e8ea15aaa2b63acbd47ea4769d..0cd030dd55ad06627c9a9178409f4af511606ecd 100644 GIT binary patch delta 37 rcmZpaZ)BE?ed)BE?u7GZx^prw85kH?ik&<|IDnvrBc)@boun85;X(?j diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_light.png index cb8d2eaeaa68ce1ef185cc4dec2f22ff0b06bfc3..aeeb9b195f19d518538747fc7b2e91e2e3e9c4b5 100644 GIT binary patch delta 37 rcmX@0eL#DHiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnU6UvP!~+W9 delta 37 rcmX@0eL#DHiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bU6UvP+f@qT diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_dark.png index 5da7f724abd1bf30e1dc3b2b298a63360c94d291..a1b19ad5ac708d073e2b53181c78b5a273648e8c 100644 GIT binary patch delta 37 rcmX?WbJk{piWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn9lsm^%XA8} delta 37 rcmX?WbJk{piWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b9lsm^;>HTI diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_light.png index 4ec326e034610f21012519ae0cf5a9a3a4ddadf0..9b1ebdc7521c7b1bb8d622caeb1ba9a5f0d85e0d 100644 GIT binary patch delta 37 rcmaE4^~h?1iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnor){~(D@4O delta 37 rcmaE4^~h?1iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bor){~=t~Oi diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_dark.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_dark.png index 8e904035c0b14d6018a4133fc6baf8b23d9197b4..819784cbb0bc9febfc47b4991227c781baa195dc 100644 GIT binary patch delta 37 rcmaFE^M+@FiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnogNzi$1)0- delta 37 rcmaFE^M+@FiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bogNzi-h>L6 diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_light.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_light.png index 2f80e3268bdc4686d0c30de55c77eef281963d8d..9810a5df805b6a0fb6e495fdba8212ac41dfbf93 100644 GIT binary patch delta 37 rcmZ3?x0r8&iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-BUIIw4@5b delta 37 rcmZ3?x0r8&iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-BUII%k~Pv diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_selected_dark.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_selected_dark.png index 227c531012dba2923ae0a049ae6c948a27e3c57d..e9bae86872ed3f4d4ae61ca4a1f99fa210596a0d 100644 GIT binary patch delta 37 rcmey)`<-`!iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-A*GZx^prw85kH?ik&<|IDnvrBc)@b-A*GZx^prw85kH?ik&<|IDnvrBc)@boh~~7-p2}> diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_dark.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_dark.png index 0c0fab9fb0295f1c64f0c346d76e68f98c90f4ef..f6f83ee599b22b6c05b7271000cc771d3059ff7a 100644 GIT binary patch delta 37 rcmdnQy@`8*iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT?s1ywGZx^prw85kH?ik&<|IDnvrBc)@bT?s1y&Up%F diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_light.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_light.png index c9d9896647c9ae04934a21a73a655608ee2715ba..c360f09bc00652f24dba30d775114c17eb1b0e6f 100644 GIT binary patch delta 37 rcmX@gbChR-iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn9XlHUyX*=h delta 37 rcmX@gbChR-iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b9XlHU(>@9# diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_own_dark.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_own_dark.png index b20b3d4b8864a423054cd35e595be31a7d7d2ef0..86eaa607c89b6f6131643c4980c0ea820dce3c98 100644 GIT binary patch delta 37 rcmaFO`I>WriWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-C7m^#}o?< delta 37 rcmaFO`I>WriWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-C7m^-ewC8 diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_own_light.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_own_light.png index 13e063d6a16e239b76e30f41602b6f22890f0658..c0ac2c43b0799cde7d38edf1403cb429e50839e0 100644 GIT binary patch delta 37 rcmX@db&hL-iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnod7EUz4;0` delta 37 rcmX@db&hL-iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bod7EU)k_LF diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_selected_dark.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_selected_dark.png index 25d222bed1fac863365f8954a9928c85cdf9ceca..b23c628a3b5d8522610219bce6533b6b5a3f3313 100644 GIT binary patch delta 37 rcmeBV?PQ&xBE?ed diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_selected_light.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_selected_light.png index 670bc5de2f56af1ba8d3aaf68d52739f507f7c81..c47907a1c0505d6c08fb7dcb1492e9c78508f0bf 100644 GIT binary patch delta 37 rcmaFL@|0zQiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnojMZ$!uSer delta 37 rcmaFL@|0zQiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bojMZ$+DZy< diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_unselected_dark.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_unselected_dark.png index 657eee5c62c2ae791a8b084b9cd1a57bbd7d5ae9..e7cac1e1a0371ee7dc10125104f473fd052fb6f1 100644 GIT binary patch delta 37 rcmdnOvV~=WiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-8V)6w#^FL delta 37 rcmdnOvV~=WiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-8V)6&L0Zf diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_unselected_light.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_unselected_light.png index c089b20c3bf0030fb215d028d38bd27405c466e3..a8d37e94bb20a193614d29703403888fbb18c9e2 100644 GIT binary patch delta 37 rcmaFM@|IGZx^prw85kH?ik&<|IDnvrBc)@bojwx)+?oo9 diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_dark.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_dark.png index f0b4c634f87ee44fa05f8a38d632b79e04379cea..3ea44c39935fa5286a26d79e03e6a1420938d59a 100644 GIT binary patch delta 37 rcmbOtG(~8FiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-8BvXv1kgO delta 37 rcmbOtG(~8FiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-8BvX$hr!i diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_light.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_light.png index d14ce8315555ddab573337b4af38f655835a32cd..dea0b18230f2d9bf0110d9df5c8ff79f87c79dd5 100644 GIT binary patch delta 37 rcmbOtGDT#9iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-8D`CvGoe0 delta 37 rcmbOtGDT#9iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-8D`C$wvyK diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_selected_dark.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_selected_dark.png index 068b97997fbc725467889948d76d8fd6bbf66082..3de2c605ae107a597306b22b8bd6a405d48b206c 100644 GIT binary patch delta 37 rcmdllykB^NiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT_Yy|y&Vdo delta 37 rcmdllykB^NiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bT_Yy|)Ncx+ diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_selected_light.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_selected_light.png index 0a3feaed269713e369bb75388dce2b41cf8f8fc3..6a1b7053306ef85874f2224c59e3bf9d563f54a8 100644 GIT binary patch delta 37 rcmcaAa#du4iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnoj4Z&!oUh= delta 37 rcmcaAa#du4iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@boj4Z&+7b$9 diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_dark.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_dark.png index 04a299b28343ffb5498b9536e58053d8e446abbf..38f89733ec058cc55cfcc1b653647d96ae8829d0 100644 GIT binary patch delta 37 rcmdlivsq?>iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-B(@!y&nq{ delta 37 rcmdlivsq?>iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-B(@!)NuGZx^prw85kH?ik&<|IDnvrBc)@b-D*An<5UYd diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_dark.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_dark.png index 845ea2d8d9e310b611d61062d9b01730476c80e0..6fb30a77235284f4e9da7e783c09fd041f1f13eb 100644 GIT binary patch delta 37 rcmew=^HpYoiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnojD%>&NK?! delta 37 rcmew=^HpYoiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bojD%><%SB| diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_light.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_light.png index 1a134c74f97e0c0c278d1926aacbe74bfd4af87b..9e26228e052ba0dee5e4a0c9b9ae3d6501bf01fa 100644 GIT binary patch delta 37 rcmbO#H&t$eiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-E}?ywEzmg delta 37 rcmbO#H&t$eiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-E}?y%u))! diff --git a/packages/stream_chat_flutter/test/src/scroll_view/draft_scroll_view/goldens/ci/stream_draft_list_tile_dark.png b/packages/stream_chat_flutter/test/src/scroll_view/draft_scroll_view/goldens/ci/stream_draft_list_tile_dark.png index 84d0bbdf08166848dde41b79a8ed4320b5074e88..c95b1aeca3b686bc3203e21c0a8f401102b97223 100644 GIT binary patch delta 37 rcmaFD`Gj+ViWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-7*#c#SRMi delta 37 rcmaFD`Gj+ViWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-7*#c++Yg$ diff --git a/packages/stream_chat_flutter/test/src/scroll_view/draft_scroll_view/goldens/ci/stream_draft_list_tile_light.png b/packages/stream_chat_flutter/test/src/scroll_view/draft_scroll_view/goldens/ci/stream_draft_list_tile_light.png index 5c2b31af7c7f2fc7f57783e2af566e97f2a062eb..37d559d977a5afd551c817b90ef575f32908cf00 100644 GIT binary patch delta 37 rcmX@bd5Uv_iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnT@MQYy^0E| delta 37 rcmX@bd5Uv_iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bT@MQY)Z7ZH diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png index fcf6f252cbce8c920e24ef88202564de8893235a..8de67dadfac50cace5938506bfadf155b9c54e97 100644 GIT binary patch delta 37 rcmew)`AKquiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-4-4I&4LR` delta 37 rcmew)`AKquiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-4-4InaZX}_iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWnod6F2!PE*| delta 37 rcmX>naZX}_iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@bod6F2*(M5H diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_dark.png b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_dark.png index bc3b26348a37eb1bd8f18f368572be60f68d1581..6e7a5ddc8751f6ebc6d34dea247d7b96e05e42bd 100644 GIT binary patch delta 37 rcmca7cu#PGiWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-2x5(#fA#{ delta 37 rcmca7cu#PGiWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-2x5(+}H~G diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_light.png b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_light.png index 3346a781da2839300bac5efdb340446a39d74445..7f4c1ceff8859f33e3fe442dd95a383b21ec8aa1 100644 GIT binary patch delta 37 rcmaDV_*8I$iWEz+lV=DA5Y%v_bTBY5Fct^7J29*~C-ZWn-Es~9$BYXP delta 37 rcmaDV_*8I$iWFmUkh>GZx^prw85kH?ik&<|IDnvrBc)@b-Es~9-rfrj diff --git a/packages/stream_chat_flutter_core/test/message_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_list_core_test.dart index 2398ae33b7..5392f7db16 100644 --- a/packages/stream_chat_flutter_core/test/message_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_list_core_test.dart @@ -14,14 +14,14 @@ void main() { int offset = 0, bool threads = false, }) { - final users = List.generate(count, (index) { - index = count + offset; + final users = List.generate(count, (i) { + final index = i + offset; return User(id: 'testUserId$index'); }); final messages = List.generate( count, - (index) { - index = index + offset; + (i) { + final index = i + offset; return Message( id: 'testMessageId$index', type: 'testType', @@ -39,8 +39,8 @@ void main() { ); final threadMessages = List.generate( count, - (index) { - index = index + offset; + (i) { + final index = i + offset; return Message( id: 'testThreadMessageId$index', type: 'testType', From d347eb170915fdb279ec77420da9aa3ce143c8b8 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 24 Feb 2026 15:49:56 +0530 Subject: [PATCH 07/33] refactor(ui)!: update design for context menu and message action (#2517) --- melos.yaml | 2 +- migrations/redesign/README.md | 1 + migrations/redesign/message_actions.md | 570 ++++++++++++++++++ .../lib/src/context_menu/context_menu.dart | 50 +- .../src/context_menu/context_menu_region.dart | 34 +- .../full_screen_media_desktop.dart | 59 +- .../src/message_action/message_action.dart | 192 ++++-- .../message_action/message_action_item.dart | 81 --- .../message_action/message_action_type.dart | 137 ----- .../message_actions_builder.dart | 157 +++-- .../message_modal/message_actions_modal.dart | 54 +- .../message_reactions_modal.dart | 18 +- .../moderated_message_actions_modal.dart | 23 +- .../src/message_widget/message_widget.dart | 123 ++-- .../lib/src/misc/stream_modal.dart | 2 +- .../src/reactions/picker/reaction_picker.dart | 5 +- .../lib/stream_chat_flutter.dart | 11 +- packages/stream_chat_flutter/pubspec.yaml | 2 +- .../message_action_item_test.dart | 169 ------ .../message_actions_builder_test.dart | 33 +- .../message_actions_modal_test.dart | 96 +-- .../message_reactions_modal_test.dart | 86 +-- .../moderated_message_actions_modal_test.dart | 87 +-- sample_app/lib/pages/channel_page.dart | 142 ++--- 24 files changed, 1121 insertions(+), 1013 deletions(-) create mode 100644 migrations/redesign/message_actions.md delete mode 100644 packages/stream_chat_flutter/lib/src/message_action/message_action_item.dart delete mode 100644 packages/stream_chat_flutter/lib/src/message_action/message_action_type.dart delete mode 100644 packages/stream_chat_flutter/test/src/message_action/message_action_item_test.dart diff --git a/melos.yaml b/melos.yaml index 9d31b072df..6f0166150a 100644 --- a/melos.yaml +++ b/melos.yaml @@ -95,7 +95,7 @@ command: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: f0af49b3cb1426efed625d0a24e58874363e9538 + ref: 5b95b4821fd98d77eccefba9d9afa7fdc071370b path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 diff --git a/migrations/redesign/README.md b/migrations/redesign/README.md index ee9a95e8ad..78184f48dd 100644 --- a/migrations/redesign/README.md +++ b/migrations/redesign/README.md @@ -44,6 +44,7 @@ You can also use the convenience factories `StreamTheme.light()` or `StreamTheme | Component | Migration Guide | |-----------|-----------------| | Stream Avatar | [stream_avatar.md](stream_avatar.md) | +| Message Actions | [message_actions.md](message_actions.md) | ## Need Help? diff --git a/migrations/redesign/message_actions.md b/migrations/redesign/message_actions.md new file mode 100644 index 0000000000..c20bf77def --- /dev/null +++ b/migrations/redesign/message_actions.md @@ -0,0 +1,570 @@ +# Message Actions Migration Guide + +This guide covers the migration for the redesigned message action components in Stream Chat Flutter SDK. + +--- + +## Table of Contents + +- [Quick Reference](#quick-reference) +- [StreamMessageAction → StreamContextMenuAction](#streammessageaction--streamcontextmenuaction) +- [StreamMessageActionItem](#streammessageactionitem) +- [StreamMessageActionsModal](#streammessageactionsmodal) +- [StreamMessageReactionsModal](#streammessagereactionsmodal) +- [ModeratedMessageActionsModal](#moderatedmessageactionsmodal) +- [StreamMessageWidget.customActions → actionsBuilder](#streammessagewidgetcustomactions) +- [StreamMessageActionsBuilder](#streammessageactionsbuilder) +- [New Components](#new-components) +- [Migration Checklist](#migration-checklist) + +--- + +## Quick Reference + +| Symbol | Change | +|--------|--------| +| `StreamMessageAction` | **Removed** — replaced by `StreamContextMenuAction` | +| `StreamMessageActionItem` | **Removed** — rendering built into `StreamContextMenuAction` | +| `StreamMessageActionsModal.onActionTap` | **Removed** — use `onTap` per-action or await the dialog return value | +| `StreamMessageActionsModal.messageActions` | **Type changed**: `List` → `List` | +| `StreamMessageReactionsModal.onReactionPicked` | **Removed** — await the dialog return value (`SelectReaction`) | +| `ModeratedMessageActionsModal.onActionTap` | **Removed** — use `onTap` per-action or await the dialog return value | +| `ModeratedMessageActionsModal.messageActions` | **Type changed**: `List` → `List` | +| `StreamMessageWidget.customActions` | **Removed** — replaced by `actionsBuilder` (`MessageActionsBuilder?`) | +| `StreamMessageWidget.onCustomActionTap` | **Removed** — use `onTap` directly on each `StreamContextMenuAction` in `actionsBuilder` | +| `CustomMessageAction` | **Removed** — no longer needed; custom actions use `onTap` directly | +| `OnMessageActionTap` | **Removed** — no longer needed | +| `StreamMessageWidget.actionsBuilder` | **New** — `MessageActionsBuilder?` for the normal long-press menu | +| `StreamMessageActionsBuilder.buildActions` | **Changed**: return type `List`, `customActions` param **removed** | +| `StreamMessageActionsBuilder.buildBouncedErrorActions` | **Return type changed**: `List` → `List` | +| `MessageActionsBuilder` | **New typedef** — `List Function(BuildContext, List>)` | +| `StreamContextMenu` | **New** — exported from `stream_core_flutter` | +| `StreamContextMenuAction` | **New** — exported from `stream_core_flutter` | +| `StreamContextMenuSeparator` | **New** — exported from `stream_core_flutter` | + +> **Note:** `MessageAction` and all its built-in subclasses (`SelectReaction`, `CopyMessage`, `DeleteMessage`, etc.) are **unchanged**. `CustomMessageAction` (the escape-hatch subclass) has been **removed** — it was only needed for the old `onCustomActionTap` dispatch pattern. + +--- + +## StreamMessageAction → StreamContextMenuAction + +The `StreamMessageAction` data class has been removed. It was a pure data object that described how an action should look and which `MessageAction` it represents. It is replaced by `StreamContextMenuAction`, which is a self-rendering widget that carries a typed `value` and handles dispatch automatically. + +### Breaking Change + +`StreamMessageAction` no longer exists. Replace every usage with `StreamContextMenuAction`. + +### Tap dispatch behaviour + +`StreamContextMenuAction` has two complementary dispatch mechanisms: + +- **`value`** — when the action is tapped inside a popup route (dialog, bottom sheet, etc.) it calls `Navigator.pop(value)` first, then calls `onTap` if provided. The route is already closed when `onTap` runs. +- **`onTap`** — an optional `VoidCallback?`. When the action is used *inline* (outside any popup route) this is the only callback that fires. + +You can use `value`, `onTap`, or both together. + +### Migration + +**Before:** +```dart +StreamMessageAction( + action: QuotedReply(message: message), + leading: const StreamSvgIcon(icon: StreamSvgIcons.reply), + title: Text(context.translations.replyLabel), +) +``` + +**After (value-based — recommended for modals):** +```dart +StreamContextMenuAction( + value: QuotedReply(message: message), + leading: Icon(context.streamIcons.arrowShareLeft), + label: Text(context.translations.replyLabel), +) +// The caller receives QuotedReply via the Future returned by showStreamDialog. +``` + +**After (onTap-based — for inline usage or when you prefer callbacks):** +```dart +StreamContextMenuAction( + value: QuotedReply(message: message), + leading: Icon(context.streamIcons.arrowShareLeft), + label: Text(context.translations.replyLabel), + onTap: () => onReply(message), // called after the route is dismissed +) +``` + +### Property mapping + +| `StreamMessageAction` | `StreamContextMenuAction` | +|-----------------------|--------------------------| +| `action: T` | `value: T?` | +| `title: Widget?` | `label: Widget` (required) | +| `leading: Widget?` | `leading: Widget?` | +| `isDestructive: bool` | `isDestructive: bool` (or use `.destructive` constructor) | +| `iconColor: Color?` | Controlled via `StreamContextMenuActionTheme` | +| `titleTextColor: Color?` | Controlled via `StreamContextMenuActionTheme` | +| `titleTextStyle: TextStyle?` | Controlled via `StreamContextMenuActionTheme` | +| `backgroundColor: Color?` | Controlled via `StreamContextMenuActionTheme` | +| — | `onTap: VoidCallback?` (new) | +| — | `trailing: Widget?` (new) | +| — | `enabled: bool` (new) | + +> **Important:** +> - **`label` is now required** — `title: Widget?` was optional in `StreamMessageAction`; `label: Widget` is a required, non-nullable parameter in `StreamContextMenuAction`. Any call site that omitted `title` will fail to compile; you must supply a non-null `label` widget (typically a `Text`). +> - `onTap` signature changed from `void Function(MessageAction)` to `VoidCallback?` — capture data in a closure instead +> - Per-item colours and text styles are now unified via `StreamContextMenuActionTheme` rather than individual properties + +--- + +## StreamMessageActionItem + +The `StreamMessageActionItem` widget has been removed. `StreamContextMenuAction` is now a full self-rendering widget — no separate "item" wrapper is needed. + +### Breaking Change + +`StreamMessageActionItem` no longer exists. Remove all direct usages. + +### Migration + +**Before:** +```dart +StreamMessageActionItem( + action: StreamMessageAction( + action: CopyMessage(message: message), + leading: const StreamSvgIcon(icon: StreamSvgIcons.copy), + title: Text('Copy'), + ), + onTap: (action) => _handle(action), +) +``` + +**After:** +```dart +StreamContextMenuAction( + value: CopyMessage(message: message), + leading: Icon(context.streamIcons.copy), + label: Text('Copy'), + onTap: () => _handle(message), +) +``` + +--- + +## StreamMessageActionsModal + +### Breaking Changes + +- `onActionTap: OnMessageActionTap?` parameter **removed** — the modal no longer holds a top-level callback; use `onTap` on individual actions or await the dialog's return value +- `messageActions` parameter type changed from `List` to `List` + +### Migration + +**Before:** +```dart +StreamMessageActionsModal( + message: message, + messageWidget: messageWidget, + messageActions: [ + StreamMessageAction( + action: CopyMessage(message: message), + leading: const StreamSvgIcon(icon: StreamSvgIcons.copy), + title: Text(context.translations.copyMessageLabel), + ), + ], + onActionTap: (action) { + if (action is CopyMessage) _copyMessage(action.message); + }, +) +``` + +**After (onTap per-action):** +```dart +StreamMessageActionsModal( + message: message, + messageWidget: messageWidget, + messageActions: [ + StreamContextMenuAction( + value: CopyMessage(message: message), + leading: Icon(context.streamIcons.copy), + label: Text(context.translations.copyMessageLabel), + onTap: () => _copyMessage(message), // called after route dismissal + ), + ], +) +``` + +**After (await return value):** + +> `showStreamDialog` is a Stream-themed wrapper around `showGeneralDialog` — see [New Components](#showstreamdialogt) for details. + +```dart +final action = await showStreamDialog( + context: context, + builder: (_) => StreamMessageActionsModal( + message: message, + messageWidget: messageWidget, + messageActions: [ + StreamContextMenuAction( + value: CopyMessage(message: message), + leading: Icon(context.streamIcons.copy), + label: Text(context.translations.copyMessageLabel), + ), + ], + ), +); + +if (action is CopyMessage) _copyMessage(action.message); +``` + +> **Important:** +> - `onActionTap` on the modal is gone — move handling to `onTap` on each item or await the `Future` +> - Replace `StreamMessageAction` entries with `StreamContextMenuAction` + +--- + +## StreamMessageReactionsModal + +### Breaking Changes + +- `onReactionPicked: OnMessageActionTap?` parameter **removed** — the modal now pops the route with a `SelectReaction`; await the dialog return value to handle it + +### Migration + +**Before:** +```dart +StreamMessageReactionsModal( + message: message, + messageWidget: messageWidget, + onReactionPicked: (SelectReaction action) { + _addReaction(action.reaction); + }, +) +``` + +**After:** +```dart +final action = await showStreamDialog( + context: context, + builder: (_) => StreamMessageReactionsModal( + message: message, + messageWidget: messageWidget, + ), +); + +if (action is SelectReaction) { + _addReaction(action.reaction); +} +``` + +> **Important:** +> - The old `onReactionPicked` already received a `SelectReaction`, not a raw `Reaction` — the migration only changes *where* you handle it (caller vs callback) + +--- + +## ModeratedMessageActionsModal + +### Breaking Changes + +- `onActionTap: OnMessageActionTap?` parameter **removed** — move handling to `onTap` on each action or await the dialog return value +- `messageActions` parameter type changed from `List` to `List` + +### Migration + +**Before:** +```dart +ModeratedMessageActionsModal( + message: message, + messageActions: [ + StreamMessageAction( + action: ResendMessage(message: message), + title: Text(context.translations.sendAnywayLabel), + ), + StreamMessageAction( + action: EditMessage(message: message), + title: Text(context.translations.editMessageLabel), + ), + StreamMessageAction( + isDestructive: true, + action: HardDeleteMessage(message: message), + title: Text(context.translations.deleteMessageLabel), + ), + ], + onActionTap: (action) { + if (action is ResendMessage) _resend(action.message); + }, +) +``` + +**After (onTap per-action):** +```dart +ModeratedMessageActionsModal( + message: message, + messageActions: [ + StreamContextMenuAction( + value: ResendMessage(message: message), + label: Text(context.translations.sendAnywayLabel), + onTap: () => _resend(message), + ), + StreamContextMenuAction( + value: EditMessage(message: message), + label: Text(context.translations.editMessageLabel), + ), + StreamContextMenuAction.destructive( + value: HardDeleteMessage(message: message), + label: Text(context.translations.deleteMessageLabel), + ), + ], +) +``` + +**After (await return value):** +```dart +final action = await showStreamDialog( + context: context, + builder: (_) => ModeratedMessageActionsModal( + message: message, + messageActions: [ + StreamContextMenuAction( + value: ResendMessage(message: message), + label: Text(context.translations.sendAnywayLabel), + ), + // ... + ], + ), +); + +if (action is ResendMessage) _resend(action.message); +``` + +--- + +## StreamMessageWidget.customActions + +### Breaking Change + +`customActions: List` has been **removed**. It is replaced by `actionsBuilder`: + +```dart +typedef MessageActionsBuilder = + List Function( + BuildContext context, + List> defaultActions, + ); +``` + +`StreamMessageWidget.actionsBuilder` is declared as `MessageActionsBuilder?` (i.e. `MessageActionsBuilder?`), so `defaultActions` is typed as `List>` — each item's `.props.value` is a `MessageAction?`. + +The `defaultActions` list passed into the builder is already filtered by the widget's `show*` flags, so callers always start from a clean, ready-to-render baseline. + +`actionsBuilder` returns `List` — any widget can be mixed in alongside the default `StreamContextMenuAction` items (e.g. `StreamContextMenuSeparator`). + +### Migration + +**Before (append a custom action):** +```dart +StreamMessageWidget( + message: message, + messageTheme: messageTheme, + customActions: [ + StreamMessageAction( + action: CustomMessageAction( + message: message, + extraData: const {'type': 'favourite'}, + ), + leading: const Icon(Icons.star), + title: Text('Favourite'), + ), + ], + onCustomActionTap: (CustomMessageAction action) { + _favourite(action.message); + }, +) +``` + +**After:** +```dart +StreamMessageWidget( + message: message, + messageTheme: messageTheme, + actionsBuilder: (context, defaultActions) => [ + ...defaultActions, + StreamContextMenuAction( + leading: const Icon(Icons.star), + label: Text('Favourite'), + onTap: () => _favourite(message), + ), + ], +) +``` + +**After (remove an existing action and add a custom one):** +```dart +StreamMessageWidget( + message: message, + messageTheme: messageTheme, + actionsBuilder: (context, defaultActions) => [ + ...defaultActions.where((a) => a.props.value is! DeleteMessage), + StreamContextMenuSeparator(), + StreamContextMenuAction( + leading: const Icon(Icons.star), + label: Text('Favourite'), + onTap: () => _favourite(message), + ), + ], +) +``` + +> **Important:** +> - `onCustomActionTap` is **removed** — put dispatch logic directly in `onTap` on each action +> - `actionsBuilder` receives the defaults **already** filtered by `show*` flags (e.g. `showDeleteMessage`) +> - When `actionsBuilder` is not provided, the default list is wrapped in `StreamContextMenuAction.partitioned` automatically + +--- + +## StreamMessageActionsBuilder + +### Breaking Changes + +Both static methods now return `List` instead of `List`. Additionally, the `customActions` parameter of `buildActions` has been **removed** — appending custom actions is now handled by `StreamMessageWidget.actionsBuilder`. + +| Method / Parameter | Old type | New type | +|--------------------|----------|----------| +| `buildActions` return | `List` | `List` | +| `buildBouncedErrorActions` return | `List` | `List` | +| `buildActions(customActions:)` | `Iterable?` | **Removed** | + +### Migration + +**Before:** +```dart +final List actions = + StreamMessageActionsBuilder.buildActions( + context: context, + message: message, + channel: channel, + currentUser: currentUser, + customActions: myCustomStreamMessageActions, +); +``` + +**After:** +```dart +// buildActions no longer accepts customActions — add extras via actionsBuilder +final List> actions = + StreamMessageActionsBuilder.buildActions( + context: context, + message: message, + channel: channel, + currentUser: currentUser, +); +``` + +--- + +## New Components + +### showStreamDialog\ + +A top-level function from `package:stream_chat_flutter/stream_chat_flutter.dart` that replaces direct calls to `showDialog` when presenting Stream modals. It wraps `showGeneralDialog` and: + +- **Re-wraps `StreamChatTheme`** across the route boundary so the theme is available inside the dialog even when `useRootNavigator: true` +- **Applies a blur + scale transition** for a consistent Stream look +- **Returns `Future`** — the value passed to `Navigator.pop` inside the dialog, which is how `StreamMessageActionsModal`, `StreamMessageReactionsModal`, and `ModeratedMessageActionsModal` deliver the selected action back to the caller + +```dart +// Replace showDialog with showStreamDialog when presenting Stream modals: +final action = await showStreamDialog( + context: context, + builder: (_) => StreamMessageActionsModal(/* … */), +); +``` + +> **Note:** If you were calling `showDialog` directly and passing Stream modals to it, switch to `showStreamDialog` to ensure theming works correctly across the route boundary. + +### StreamContextMenuAction + +A self-contained menu action widget from `stream_core_flutter` that replaces `StreamMessageAction` + `StreamMessageActionItem`. It renders itself and supports two dispatch mechanisms: + +- **Inside a popup route** (dialog/bottom sheet): pops `value` via `Navigator.pop` first, then calls `onTap` if provided. +- **Inline** (outside any popup route): only `onTap` fires. + +```dart +// Standard action — value-based (recommended for modals) +StreamContextMenuAction( + value: CopyMessage(message: message), + leading: Icon(context.streamIcons.copy), + label: Text('Copy'), +) + +// With optional onTap callback (called after route dismissal) +StreamContextMenuAction( + value: CopyMessage(message: message), + leading: Icon(context.streamIcons.copy), + label: Text('Copy'), + onTap: () => _copyMessage(message), +) + +// Destructive action +StreamContextMenuAction.destructive( + value: DeleteMessage(message: message), + leading: Icon(context.streamIcons.trashBin), + label: Text('Delete'), +) +``` + +#### Helper methods for grouping + +```dart +// Insert a separator between every item +StreamContextMenuAction.separated(items: actions) + +// Insert separators between logical groups (provide groups as separate lists) +StreamContextMenuAction.sectioned(sections: [normalActions, destructiveActions]) + +// Automatically partition into normal / destructive groups with a separator between them +StreamContextMenuAction.partitioned(items: actions) +``` + +All three methods return `List` because they interleave `StreamContextMenuSeparator` widgets. + +### StreamContextMenu + +A themed container that wraps a list of `StreamContextMenuAction` and `StreamContextMenuSeparator` widgets. + +```dart +StreamContextMenu( + children: StreamContextMenuAction.partitioned(items: actions), +) +``` + +### StreamContextMenuSeparator + +A thin horizontal divider for use inside `StreamContextMenu`. + +```dart +StreamContextMenu( + children: [ + StreamContextMenuAction(value: reply, label: Text('Reply')), + const StreamContextMenuSeparator(), + StreamContextMenuAction.destructive(value: delete, label: Text('Delete')), + ], +) +``` + +--- + +## Migration Checklist + +- [ ] Replace all `StreamMessageAction(action: ..., title: ..., leading: ...)` with `StreamContextMenuAction(value: ..., label: ..., leading: ...)` +- [ ] Add a non-null `label` widget wherever `title` was previously omitted — `label` is now a required parameter and code that relied on a null/omitted `title` will not compile +- [ ] Update `onTap` callsites: old type was `void Function(MessageAction)`, new type is `VoidCallback?` — capture needed data in a closure +- [ ] Remove all `StreamMessageActionItem` usages +- [ ] Remove `onActionTap` from `StreamMessageActionsModal`; handle via per-action `onTap` or await the dialog return value +- [ ] Remove `onReactionPicked` from `StreamMessageReactionsModal`; await a `SelectReaction` return value +- [ ] Remove `onActionTap` from `ModeratedMessageActionsModal`; handle via per-action `onTap` or await the dialog return value +- [ ] Replace `StreamMessageWidget.customActions` with `actionsBuilder` +- [ ] Update `StreamMessageActionsBuilder.buildActions` call sites — return type is now `List` and `customActions` parameter no longer exists +- [ ] Update `StreamMessageActionsBuilder.buildBouncedErrorActions` call sites — return type is now `List` +- [ ] Replace `StreamSvgIcon` leading widgets in custom actions with `Icon(context.streamIcons.*)` +- [ ] Replace per-action color/style properties (`iconColor`, `titleTextColor`, etc.) with `StreamContextMenuActionTheme` diff --git a/packages/stream_chat_flutter/lib/src/context_menu/context_menu.dart b/packages/stream_chat_flutter/lib/src/context_menu/context_menu.dart index 3636cae319..2158b77e31 100644 --- a/packages/stream_chat_flutter/lib/src/context_menu/context_menu.dart +++ b/packages/stream_chat_flutter/lib/src/context_menu/context_menu.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; const double _kContextMenuScreenPadding = 8; -const double _kContextMenuWidth = 222; -/// Signature for a builder function that wraps the context menu widget. +/// Signature for a builder function that wraps the context menu items. /// /// This builder can be used to customize the appearance of the menu -/// container by wrapping [child] in additional UI elements. -typedef ContextMenuBuilder = +/// container by wrapping [menuItems] in additional UI elements. +typedef ContextMenuContainerBuilder = Widget Function( BuildContext context, - Widget child, + List menuItems, ); /// A widget that displays a context menu anchored to a specific [Offset]. @@ -42,35 +42,20 @@ class ContextMenu extends StatelessWidget { /// Builds the outer container for the menu. /// - /// The [menuBuilder] receives the current context and a [child] widget - /// containing all the [menuItems]. + /// The [menuBuilder] receives the current context and the [menuItems] list, + /// and should return a widget wrapping all items. /// - /// Defaults to a card-style scrollable container with fixed width. - final ContextMenuBuilder menuBuilder; + /// Defaults to a [StreamContextMenu] wrapping all items. + final ContextMenuContainerBuilder menuBuilder; /// Default menu container with standard styling. /// /// Wraps the menu content in a card-like [Material] with scroll support, /// applying max width and height constraints. - static Widget _defaultMenuBuilder(BuildContext context, Widget child) { - final availableHeight = MediaQuery.of(context).size.height; - final maxHeight = availableHeight - _kContextMenuScreenPadding * 2; - - return ConstrainedBox( - constraints: BoxConstraints( - minWidth: _kContextMenuWidth, - maxWidth: _kContextMenuWidth, - maxHeight: maxHeight, - ), - child: Material( - elevation: 1, - type: MaterialType.card, - clipBehavior: Clip.antiAlias, - borderRadius: const BorderRadius.all(Radius.circular(7)), - child: SingleChildScrollView(child: child), - ), - ); - } + static Widget _defaultMenuBuilder( + BuildContext context, + List menuItems, + ) => StreamContextMenu(children: menuItems); @override Widget build(BuildContext context) { @@ -91,14 +76,7 @@ class ContextMenu extends StatelessWidget { delegate: DesktopTextSelectionToolbarLayoutDelegate( anchor: anchor - localAdjustment, ), - child: menuBuilder.call( - context, - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: menuItems, - ), - ), + child: menuBuilder.call(context, menuItems), ), ); } diff --git a/packages/stream_chat_flutter/lib/src/context_menu/context_menu_region.dart b/packages/stream_chat_flutter/lib/src/context_menu/context_menu_region.dart index fa43d085ed..9a30e03e3a 100644 --- a/packages/stream_chat_flutter/lib/src/context_menu/context_menu_region.dart +++ b/packages/stream_chat_flutter/lib/src/context_menu/context_menu_region.dart @@ -14,7 +14,7 @@ typedef ContextMenuBuilder = /// Displays a custom context menu as a general dialog. /// -/// The [contextMenuBuilder] is used to construct the contents of the +/// The [menuBuilder] is used to construct the contents of the /// context menu, typically positioned based on the triggering gesture. /// /// The dialog can be customized using parameters such as [barrierColor], @@ -23,7 +23,7 @@ typedef ContextMenuBuilder = /// Returns a [Future] that resolves when the menu is dismissed. Future showContextMenu({ required BuildContext context, - required WidgetBuilder contextMenuBuilder, + required WidgetBuilder menuBuilder, String? barrierLabel, Color? barrierColor, Duration transitionDuration = const Duration(milliseconds: 150), @@ -60,7 +60,7 @@ Future showContextMenu({ ); }, pageBuilder: (context, animation, secondaryAnimation) { - final pageChild = Builder(builder: contextMenuBuilder); + final pageChild = Builder(builder: menuBuilder); return capturedThemes.wrap(pageChild); }, ); @@ -74,11 +74,12 @@ class ContextMenuRegion extends StatefulWidget { /// Creates a [ContextMenuRegion]. /// /// The [child] is the widget wrapped by this region. When a gesture is - /// detected on it, the [contextMenuBuilder] is used to construct the menu. + /// detected on it, the [menuBuilder] is used to construct the menu. const ContextMenuRegion({ super.key, required this.child, - required this.contextMenuBuilder, + required this.menuBuilder, + this.onSelected, }); /// The widget below this widget in the tree. @@ -87,7 +88,14 @@ class ContextMenuRegion extends StatefulWidget { /// Called to build the context menu when the gesture is triggered. /// /// The builder is given the [BuildContext] and the [Offset] of the gesture. - final ContextMenuBuilder contextMenuBuilder; + final ContextMenuBuilder menuBuilder; + + /// Called with the value returned when the context menu is dismissed. + /// + /// When a menu item pops the route with a value (e.g. via + /// [Navigator.pop]), that value is forwarded here. If the menu is dismissed + /// without a selection the value will be `null`. + final ValueChanged? onSelected; @override State createState() => _ContextMenuRegionState(); @@ -108,14 +116,16 @@ class _ContextMenuRegionState extends State { super.dispose(); } - Future _showContextMenu(BuildContext context, Offset position) async { - print('ContextMenuRegion: Showing context menu at $position'); - await showContextMenu( + Future _showContextMenu( + BuildContext context, + Offset position, + ) async { + final result = await showContextMenu( context: context, - contextMenuBuilder: (context) { - return widget.contextMenuBuilder(context, position); - }, + menuBuilder: (context) => widget.menuBuilder(context, position), ); + + return widget.onSelected?.call(result); } @override diff --git a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart index 4b449732bd..214ad03621 100644 --- a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart +++ b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart @@ -121,12 +121,16 @@ class _FullScreenMediaDesktopState extends State { return Stack( children: [ ContextMenuRegion( - contextMenuBuilder: (context, anchor) { + menuBuilder: (context, anchor) { final index = _currentPage.value; final mediaAttachment = widget.mediaAttachmentPackages[index]; return ContextMenu( anchor: anchor, - menuItems: [_DownloadMenuItem(mediaAttachment: mediaAttachment)], + menuItems: [ + _DownloadMenuItem( + mediaAttachment: mediaAttachment.attachment, + ), + ], ); }, child: _PlaylistPlayer( @@ -348,16 +352,14 @@ class _FullScreenMediaDesktopState extends State { } return ContextMenuRegion( - contextMenuBuilder: (_, anchor) { - return ContextMenu( - anchor: anchor, - menuItems: [ - _DownloadMenuItem( - mediaAttachment: currentAttachmentPackage, - ), - ], - ); - }, + menuBuilder: (_, anchor) => ContextMenu( + anchor: anchor, + menuItems: [ + _DownloadMenuItem( + mediaAttachment: currentAttachmentPackage.attachment, + ), + ], + ), child: Video( controller: package.controller, ), @@ -382,8 +384,7 @@ class _FullScreenMediaDesktopState extends State { /// This widget displays a download option in a context menu, allowing users to /// download the attachment associated with a message. /// -/// It uses [StreamMessageActionItem] and [StreamMessageAction] to create a -/// consistent UI with other message actions. +/// It uses [StreamContextMenuAction] to stay consistent with message actions. /// {@endtemplate} class _DownloadMenuItem extends StatelessWidget { /// {@macro streamDownloadMenuItem} @@ -392,31 +393,17 @@ class _DownloadMenuItem extends StatelessWidget { }); /// The attachment package containing the message and attachment to download. - final StreamAttachmentPackage mediaAttachment; - static const String _attachmentKey = 'attachment'; + final Attachment mediaAttachment; @override Widget build(BuildContext context) { - return StreamMessageActionItem( - action: StreamMessageAction( - leading: const StreamSvgIcon(icon: StreamSvgIcons.download), - title: Text(context.translations.downloadLabel), - action: CustomMessageAction( - message: mediaAttachment.message, - extraData: {_attachmentKey: mediaAttachment.attachment}, - ), - ), - // TODO: Use a callback to handle the action instead of onTap. - onTap: (action) async { - if (action is! CustomMessageAction) return; - final attachment = action.extraData[_attachmentKey] as Attachment?; - if (attachment == null) return; - - final popped = await Navigator.of(context).maybePop(); - if (popped) { - final handler = StreamAttachmentHandler.instance; - return handler.downloadAttachment(attachment).ignore(); - } + final icons = context.streamIcons; + return StreamContextMenuAction( + leading: Icon(icons.arrowDown), + label: Text(context.translations.downloadLabel), + onTap: () { + final handler = StreamAttachmentHandler.instance; + return handler.downloadAttachment(mediaAttachment).ignore(); }, ); } diff --git a/packages/stream_chat_flutter/lib/src/message_action/message_action.dart b/packages/stream_chat_flutter/lib/src/message_action/message_action.dart index 27e4fb00fa..27f950eae8 100644 --- a/packages/stream_chat_flutter/lib/src/message_action/message_action.dart +++ b/packages/stream_chat_flutter/lib/src/message_action/message_action.dart @@ -1,70 +1,138 @@ import 'package:flutter/widgets.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; -part 'message_action_type.dart'; - -/// {@template streamMessageAction} -/// A class that represents an action that can be performed on a message. +/// {@template messageActionsBuilder} +/// Signature for a builder that customizes the list of message actions shown +/// in the long-press context menu. +/// +/// [defaultActions] are the pre-built actions already filtered by the widget's +/// `show*` flags. Return a modified list to add, remove, or reorder actions. /// -/// This class is used to define actions that appear in message action menus -/// or option lists, providing a consistent structure for message-related -/// actions including their visual representation and behavior. +/// The return type is [List] so any widget — including +/// [StreamContextMenuSeparator] or fully custom items — can be mixed in +/// alongside the default [StreamContextMenuAction] items. /// {@endtemplate} -class StreamMessageAction { - /// {@macro streamMessageAction} - const StreamMessageAction({ - required this.action, - this.isDestructive = false, - this.leading, - this.iconColor, - this.title, - this.titleTextColor, - this.titleTextStyle, - this.backgroundColor, +typedef MessageActionsBuilder = + List Function( + BuildContext context, + List> defaultActions, + ); + +/// {@template messageAction} +/// A sealed class that represents different actions that can be performed on a +/// message. +/// {@endtemplate} +sealed class MessageAction { + /// {@macro messageAction} + const MessageAction({required this.message}); + + /// The message this action applies to. + final Message message; +} + +/// Action to show reaction selector for adding reactions to a message +final class SelectReaction extends MessageAction { + /// Create a new select reaction action + const SelectReaction({ + required super.message, + required this.reaction, + this.enforceUnique = false, + }); + + /// The reaction to be added or removed from the message. + final Reaction reaction; + + /// Whether to enforce unique reactions. + final bool enforceUnique; +} + +/// Action to copy message content to clipboard +final class CopyMessage extends MessageAction { + /// Create a new copy message action + const CopyMessage({required super.message}); +} + +/// Action to delete a message from the conversation +final class DeleteMessage extends MessageAction { + /// Create a new delete message action + const DeleteMessage({required super.message}); +} + +/// Action to hard delete a message permanently from the conversation +final class HardDeleteMessage extends MessageAction { + /// Create a new hard delete message action + const HardDeleteMessage({required super.message}); +} + +/// Action to modify content of an existing message +final class EditMessage extends MessageAction { + /// Create a new edit message action + const EditMessage({required super.message}); +} + +/// Action to flag a message for moderator review +final class FlagMessage extends MessageAction { + /// Create a new flag message action + const FlagMessage({required super.message}); +} + +/// Action to mark a message as unread for later viewing +final class MarkUnread extends MessageAction { + /// Create a new mark unread action + const MarkUnread({required super.message}); +} + +/// Action to mute a user to prevent notifications from their messages +final class MuteUser extends MessageAction { + /// Create a new mute user action + const MuteUser({ + required super.message, + required this.user, }); - /// The [MessageAction] that this item represents. - final T action; - - /// Whether the action is destructive. - /// - /// Destructive actions are typically displayed with a red color to indicate - /// that they will remove or delete content. - /// - /// Defaults to `false`. - final bool isDestructive; - - /// A widget to display before the title. - /// - /// Typically an [Icon] or a [CircleAvatar] widget. - final Widget? leading; - - /// The color for the [leading] icon. - /// - /// If this property is null, the icon will use the default color provided by - /// the theme or parent widget. - final Color? iconColor; - - /// The primary content of the action item. - /// - /// Typically a [Text] widget. - /// - /// This should not wrap. To enforce the single line limit, use - /// [Text.maxLines]. - final Widget? title; - - /// The color for the text in the [title]. - /// - /// If this property is null, the text will use the default color provided by - /// the theme or parent widget. - final Color? titleTextColor; - - /// The text style for the [title]. - /// - /// If this property is null, the title will use the default text style - /// provided by the theme or parent widget. - final TextStyle? titleTextStyle; - - /// Defines the background color of the action item. - final Color? backgroundColor; + /// The user to be muted. + final User user; +} + +/// Action to unmute a user to receive notifications from their messages +final class UnmuteUser extends MessageAction { + /// Create a new unmute user action + const UnmuteUser({ + required super.message, + required this.user, + }); + + /// The user to be unmuted. + final User user; +} + +/// Action to pin a message to make it prominently visible in the channel +final class PinMessage extends MessageAction { + /// Create a new pin message action + const PinMessage({required super.message}); +} + +/// Action to remove a previously pinned message +final class UnpinMessage extends MessageAction { + /// Create a new unpin message action + const UnpinMessage({required super.message}); +} + +/// Action to attempt to resend a message that failed to send +final class ResendMessage extends MessageAction { + /// Create a new resend message action + const ResendMessage({required super.message}); +} + +/// Action to create a reply with quoted original message content +final class QuotedReply extends MessageAction { + /// Create a new quoted reply action + const QuotedReply({required super.message}); +} + +/// Action to start a threaded conversation from a message +final class ThreadReply extends MessageAction { + /// Create a new thread reply action + const ThreadReply({required super.message}); } diff --git a/packages/stream_chat_flutter/lib/src/message_action/message_action_item.dart b/packages/stream_chat_flutter/lib/src/message_action/message_action_item.dart deleted file mode 100644 index fa4af9328c..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_action/message_action_item.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/message_action/message_action.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; - -/// {@template streamMessageActionItem} -/// A widget that represents an action item within a message interface. -/// -/// This widget is typically used in action menus or option lists related to -/// messages, providing a consistent appearance for selectable actions with an -/// optional icon and title. -/// {@endtemplate} -class StreamMessageActionItem extends StatelessWidget { - /// {@macro streamMessageActionItem} - const StreamMessageActionItem({ - super.key, - required this.action, - this.onTap, - }); - - /// The underlying action that this item represents. - final StreamMessageAction action; - - /// Called when the user taps this action item. - /// - /// This callback provides the tap handling for the action item, and is - /// typically used to execute the associated action or dismiss menus. - final OnMessageActionTap? onTap; - - @override - Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); - final textTheme = theme.textTheme; - final colorTheme = theme.colorTheme; - - final iconColor = switch (action.isDestructive) { - true => action.iconColor ?? colorTheme.accentError, - false => action.iconColor ?? colorTheme.textLowEmphasis, - }; - - final titleTextColor = switch (action.isDestructive) { - true => action.titleTextColor ?? colorTheme.accentError, - false => action.titleTextColor ?? colorTheme.textHighEmphasis, - }; - - final titleTextStyle = action.titleTextStyle ?? textTheme.body; - final backgroundColor = action.backgroundColor ?? colorTheme.barsBg; - - return InkWell( - onTap: switch (onTap) { - final onTap? => () => onTap(action.action), - _ => null, - }, - child: Ink( - color: backgroundColor, - child: IconTheme.merge( - data: IconThemeData(color: iconColor), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 9, - horizontal: 16, - ), - child: Row( - spacing: 16, - mainAxisSize: MainAxisSize.min, - children: [ - if (action.leading case final leading?) leading, - if (action.title case final title?) - DefaultTextStyle( - style: titleTextStyle.copyWith( - color: titleTextColor, - ), - child: title, - ), - ], - ), - ), - ), - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_action/message_action_type.dart b/packages/stream_chat_flutter/lib/src/message_action/message_action_type.dart deleted file mode 100644 index 5aea4980b1..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_action/message_action_type.dart +++ /dev/null @@ -1,137 +0,0 @@ -part of 'message_action.dart'; - -/// {@template onMessageActionTap} -/// Signature for a function that is called when a message action is tapped. -/// {@endtemplate} -typedef OnMessageActionTap = void Function(T action); - -/// {@template messageAction} -/// A sealed class that represents different actions that can be performed on a -/// message. -/// {@endtemplate} -sealed class MessageAction { - /// {@macro messageAction} - const MessageAction({required this.message}); - - /// The message this action applies to. - final Message message; -} - -/// Action to show reaction selector for adding reactions to a message -final class SelectReaction extends MessageAction { - /// Create a new select reaction action - const SelectReaction({ - required super.message, - required this.reaction, - this.enforceUnique = false, - }); - - /// The reaction to be added or removed from the message. - final Reaction reaction; - - /// Whether to enforce unique reactions. - final bool enforceUnique; -} - -/// Action to copy message content to clipboard -final class CopyMessage extends MessageAction { - /// Create a new copy message action - const CopyMessage({required super.message}); -} - -/// Action to delete a message from the conversation -final class DeleteMessage extends MessageAction { - /// Create a new delete message action - const DeleteMessage({required super.message}); -} - -/// Action to hard delete a message permanently from the conversation -final class HardDeleteMessage extends MessageAction { - /// Create a new hard delete message action - const HardDeleteMessage({required super.message}); -} - -/// Action to modify content of an existing message -final class EditMessage extends MessageAction { - /// Create a new edit message action - const EditMessage({required super.message}); -} - -/// Action to flag a message for moderator review -final class FlagMessage extends MessageAction { - /// Create a new flag message action - const FlagMessage({required super.message}); -} - -/// Action to mark a message as unread for later viewing -final class MarkUnread extends MessageAction { - /// Create a new mark unread action - const MarkUnread({required super.message}); -} - -/// Action to mute a user to prevent notifications from their messages -final class MuteUser extends MessageAction { - /// Create a new mute user action - const MuteUser({ - required super.message, - required this.user, - }); - - /// The user to be muted. - final User user; -} - -/// Action to unmute a user to receive notifications from their messages -final class UnmuteUser extends MessageAction { - /// Create a new unmute user action - const UnmuteUser({ - required super.message, - required this.user, - }); - - /// The user to be unmuted. - final User user; -} - -/// Action to pin a message to make it prominently visible in the channel -final class PinMessage extends MessageAction { - /// Create a new pin message action - const PinMessage({required super.message}); -} - -/// Action to remove a previously pinned message -final class UnpinMessage extends MessageAction { - /// Create a new unpin message action - const UnpinMessage({required super.message}); -} - -/// Action to attempt to resend a message that failed to send -final class ResendMessage extends MessageAction { - /// Create a new resend message action - const ResendMessage({required super.message}); -} - -/// Action to create a reply with quoted original message content -final class QuotedReply extends MessageAction { - /// Create a new quoted reply action - const QuotedReply({required super.message}); -} - -/// Action to start a threaded conversation from a message -final class ThreadReply extends MessageAction { - /// Create a new thread reply action - const ThreadReply({required super.message}); -} - -/// Custom message action that allows for additional data to be passed -/// along with the message. -class CustomMessageAction extends MessageAction { - /// Create a new custom message action - const CustomMessageAction({ - required super.message, - this.extraData = const {}, - }); - - /// Map of extra data associated with the action. - final Map extraData; -} diff --git a/packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart b/packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart index 4523ccdf5a..1bae56bdc8 100644 --- a/packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart +++ b/packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart @@ -1,9 +1,9 @@ -import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; +import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/message_action/message_action.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template streamMessageActionsBuilder} /// A utility class that provides a builder for message actions @@ -15,35 +15,39 @@ class StreamMessageActionsBuilder { /// Returns a list of message actions for the "bounced with error" state. /// - /// This method builds a list of [StreamMessageAction]s that are applicable to + /// This method builds a list of [StreamContextMenuAction]s that are + /// applicable to /// the given [message] when it is in the "bounced with error" state. /// /// The actions include options to retry sending the message, edit or delete /// the message. - static List buildBouncedErrorActions({ + static List> buildBouncedErrorActions({ required BuildContext context, required Message message, }) { // If the message is not bounced with an error, we don't show any actions. if (!message.isBouncedWithError) return []; - return [ - StreamMessageAction( - action: ResendMessage(message: message), - iconColor: StreamChatTheme.of(context).colorTheme.accentPrimary, - title: Text(context.translations.sendAnywayLabel), - leading: const StreamSvgIcon(icon: StreamSvgIcons.circleUp), + final icons = context.streamIcons; + + return >[ + StreamContextMenuAction( + value: ResendMessage(message: message), + label: Text(context.translations.sendAnywayLabel), + leading: Icon( + icons.paperPlaneTopRight, + color: StreamChatTheme.of(context).colorTheme.accentPrimary, + ), ), - StreamMessageAction( - action: EditMessage(message: message), - title: Text(context.translations.editMessageLabel), - leading: const StreamSvgIcon(icon: StreamSvgIcons.edit), + StreamContextMenuAction( + value: EditMessage(message: message), + label: Text(context.translations.editMessageLabel), + leading: Icon(icons.editBig), ), - StreamMessageAction( - isDestructive: true, - action: HardDeleteMessage(message: message), - title: Text(context.translations.deleteMessageLabel), - leading: const StreamSvgIcon(icon: StreamSvgIcons.delete), + StreamContextMenuAction.destructive( + value: HardDeleteMessage(message: message), + label: Text(context.translations.deleteMessageLabel), + leading: Icon(icons.trashBin), ), ]; } @@ -51,40 +55,40 @@ class StreamMessageActionsBuilder { /// Returns a list of message actions based on the provided message and /// channel capabilities. /// - /// This method builds a list of [StreamMessageAction]s that are applicable to + /// This method builds a list of [StreamContextMenuAction]s that are + /// applicable to /// the given [message] in the [channel], considering the permissions of the /// [currentUser] and the current state of the message. - static List buildActions({ + static List> buildActions({ required BuildContext context, required Message message, required Channel channel, OwnUser? currentUser, - Iterable? customActions, }) { final messageState = message.state; // If the message is deleted, we don't show any actions. if (messageState.isDeleted) return []; + final icons = context.streamIcons; + if (messageState.isFailed) { return [ if (messageState.isSendingFailed || messageState.isUpdatingFailed) ...[ - StreamMessageAction( - action: ResendMessage(message: message), - leading: const StreamSvgIcon(icon: StreamSvgIcons.circleUp), - iconColor: StreamChatTheme.of(context).colorTheme.accentPrimary, - title: Text( + StreamContextMenuAction( + value: ResendMessage(message: message), + leading: Icon(icons.paperPlaneTopRight), + label: Text( context.translations.toggleResendOrResendEditedMessage( isUpdateFailed: messageState.isUpdatingFailed, ), ), ), if (messageState.isSendingFailed) - StreamMessageAction( - isDestructive: true, - action: HardDeleteMessage(message: message), - leading: const StreamSvgIcon(icon: StreamSvgIcons.delete), - title: Text( + StreamContextMenuAction.destructive( + value: HardDeleteMessage(message: message), + leading: Icon(icons.trashBin), + label: Text( context.translations.toggleDeleteRetryDeleteMessageText( isDeleteFailed: false, ), @@ -92,11 +96,10 @@ class StreamMessageActionsBuilder { ), ], if (message.state.isDeletingFailed) - StreamMessageAction( - isDestructive: true, - action: ResendMessage(message: message), - leading: const StreamSvgIcon(icon: StreamSvgIcons.delete), - title: Text( + StreamContextMenuAction.destructive( + value: ResendMessage(message: message), + leading: Icon(icons.trashBin), + label: Text( context.translations.toggleDeleteRetryDeleteMessageText( isDeleteFailed: true, ), @@ -123,34 +126,34 @@ class StreamMessageActionsBuilder { (attachment) => attachment.type == AttachmentType.giphy, ); - final messageActions = []; + final messageActions = >[]; if (canQuoteMessage) { messageActions.add( - StreamMessageAction( - action: QuotedReply(message: message), - title: Text(context.translations.replyLabel), - leading: const StreamSvgIcon(icon: StreamSvgIcons.reply), + StreamContextMenuAction( + value: QuotedReply(message: message), + label: Text(context.translations.replyLabel), + leading: Icon(icons.arrowShareLeft), ), ); } if (canSendReply && !isThreadMessage) { messageActions.add( - StreamMessageAction( - action: ThreadReply(message: message), - title: Text(context.translations.threadReplyLabel), - leading: const StreamSvgIcon(icon: StreamSvgIcons.threadReply), + StreamContextMenuAction( + value: ThreadReply(message: message), + label: Text(context.translations.threadReplyLabel), + leading: Icon(icons.bubbleText6ChatMessage), ), ); } if (canReceiveReadEvents) { - StreamMessageAction markUnreadAction() { - return StreamMessageAction( - action: MarkUnread(message: message), - title: Text(context.translations.markAsUnreadLabel), - leading: const StreamSvgIcon(icon: StreamSvgIcons.messageUnread), + StreamContextMenuAction markUnreadAction() { + return StreamContextMenuAction( + value: MarkUnread(message: message), + label: Text(context.translations.markAsUnreadLabel), + leading: Icon(icons.bubbleWideNotificationChatMessage), ); } @@ -168,10 +171,10 @@ class StreamMessageActionsBuilder { if (message.text case final text? when text.isNotEmpty) { messageActions.add( - StreamMessageAction( - action: CopyMessage(message: message), - title: Text(context.translations.copyMessageLabel), - leading: const StreamSvgIcon(icon: StreamSvgIcons.copy), + StreamContextMenuAction( + value: CopyMessage(message: message), + label: Text(context.translations.copyMessageLabel), + leading: Icon(icons.squareBehindSquare2Copy), ), ); } @@ -179,10 +182,10 @@ class StreamMessageActionsBuilder { if (!containsPoll && !containsGiphy) { if (canUpdateAnyMessage || (canUpdateOwnMessage && isSentByCurrentUser)) { messageActions.add( - StreamMessageAction( - action: EditMessage(message: message), - title: Text(context.translations.editMessageLabel), - leading: const StreamSvgIcon(icon: StreamSvgIcons.edit), + StreamContextMenuAction( + value: EditMessage(message: message), + label: Text(context.translations.editMessageLabel), + leading: Icon(icons.editBig), ), ); } @@ -201,10 +204,10 @@ class StreamMessageActionsBuilder { }; messageActions.add( - StreamMessageAction( - action: action, - title: Text(label.call(pinned: isPinned)), - leading: const StreamSvgIcon(icon: StreamSvgIcons.pin), + StreamContextMenuAction( + value: action, + label: Text(label.call(pinned: isPinned)), + leading: Icon(icons.pin), ), ); } @@ -213,21 +216,20 @@ class StreamMessageActionsBuilder { final label = context.translations.toggleDeleteRetryDeleteMessageText; messageActions.add( - StreamMessageAction( - isDestructive: true, - action: DeleteMessage(message: message), - leading: const StreamSvgIcon(icon: StreamSvgIcons.delete), - title: Text(label.call(isDeleteFailed: false)), + StreamContextMenuAction.destructive( + value: DeleteMessage(message: message), + leading: Icon(icons.trashBin), + label: Text(label.call(isDeleteFailed: false)), ), ); } if (!isSentByCurrentUser) { messageActions.add( - StreamMessageAction( - action: FlagMessage(message: message), - title: Text(context.translations.flagMessageLabel), - leading: const StreamSvgIcon(icon: StreamSvgIcons.flag), + StreamContextMenuAction( + value: FlagMessage(message: message), + label: Text(context.translations.flagMessageLabel), + leading: Icon(icons.flag2), ), ); } @@ -243,17 +245,14 @@ class StreamMessageActionsBuilder { }; messageActions.add( - StreamMessageAction( - action: action, - title: Text(label.call(isMuted: isMuted)), - leading: const StreamSvgIcon(icon: StreamSvgIcons.mute), + StreamContextMenuAction( + value: action, + label: Text(label.call(isMuted: isMuted)), + leading: Icon(icons.mute), ), ); } - // Add all the remaining custom actions if provided. - if (customActions case final actions?) messageActions.addAll(actions); - return messageActions; } } diff --git a/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart index 4487487733..0046ca0d0d 100644 --- a/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart @@ -22,7 +22,6 @@ class StreamMessageActionsModal extends StatelessWidget { this.reverse = false, this.showReactionPicker = false, this.reactionPickerBuilder = StreamReactionPicker.builder, - this.onActionTap, }); /// The message object that actions will be performed on. @@ -30,11 +29,12 @@ class StreamMessageActionsModal extends StatelessWidget { /// This is the message the user selected to see available actions. final Message message; - /// List of custom actions that will be displayed in the modal. + /// List of widgets that will be displayed as actions in the modal. /// - /// Each action is represented by a [StreamMessageAction] object which defines - /// the action's appearance and behavior. - final List messageActions; + /// Typically built by [StreamMessageActionsBuilder] and optionally modified + /// by [StreamMessageWidget.actionsBuilder]. Each item is rendered directly + /// as a child of [StreamContextMenu]. + final List messageActions; /// The widget representing the message being acted upon. /// @@ -62,26 +62,17 @@ class StreamMessageActionsModal extends StatelessWidget { /// {@macro reactionPickerBuilder} final ReactionPickerBuilder reactionPickerBuilder; - /// Callback triggered when a message action is tapped. - /// - /// Provides the tapped [MessageAction] object to the callback. - final OnMessageActionTap? onActionTap; - @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); - final alignment = switch (reverse) { true => AlignmentDirectional.centerEnd, false => AlignmentDirectional.centerStart, }; - final onReactionPicked = switch (onActionTap) { - null => null, - final onActionTap => (reaction) => onActionTap( - SelectReaction(message: message, reaction: reaction), - ), - }; + void onReactionPicked(Reaction reaction) { + final action = SelectReaction(message: message, reaction: reaction); + return Navigator.pop(context, action); // Pop the modal with the selected reaction action + } return StreamMessageDialog( spacing: 4, @@ -102,32 +93,7 @@ class StreamMessageActionsModal extends StatelessWidget { ), ); }, - contentBuilder: (context) { - final actions = Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: { - ...messageActions.map( - (action) => StreamMessageActionItem( - action: action, - onTap: onActionTap, - ), - ), - }.insertBetween(Divider(height: 1, color: theme.colorTheme.borders)), - ); - - return FractionallySizedBox( - widthFactor: 0.78, - child: Material( - type: MaterialType.transparency, - clipBehavior: Clip.antiAlias, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - child: actions, - ), - ); - }, + contentBuilder: (context) => StreamContextMenu(children: messageActions), ); } } diff --git a/packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart b/packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart index 1cdb41d5d2..41f4ff50c0 100644 --- a/packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart @@ -24,7 +24,6 @@ class StreamMessageReactionsModal extends StatelessWidget { this.reverse = false, this.showReactionPicker = true, this.reactionPickerBuilder = StreamReactionPicker.builder, - this.onReactionPicked, this.onUserAvatarTap, }); @@ -52,11 +51,6 @@ class StreamMessageReactionsModal extends StatelessWidget { /// {@macro reactionPickerBuilder} final ReactionPickerBuilder reactionPickerBuilder; - /// Callback triggered when a user adds or toggles a reaction. - /// - /// Provides the selected [Reaction] object to the callback. - final OnMessageActionTap? onReactionPicked; - /// Callback triggered when a user avatar is tapped in the reactions list. /// /// Provides the [User] object associated with the tapped avatar. @@ -69,14 +63,10 @@ class StreamMessageReactionsModal extends StatelessWidget { false => AlignmentDirectional.centerStart, }; - final onReactionPicked = switch (this.onReactionPicked) { - null => null, - final onPicked => (reaction) { - return onPicked.call( - SelectReaction(message: message, reaction: reaction), - ); - }, - }; + void onReactionPicked(Reaction reaction) { + final action = SelectReaction(message: message, reaction: reaction); + return Navigator.pop(context, action); // Pop the modal with the selected reaction action + } return StreamMessageDialog( spacing: 4, diff --git a/packages/stream_chat_flutter/lib/src/message_modal/moderated_message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_modal/moderated_message_actions_modal.dart index da655979a7..7edc807901 100644 --- a/packages/stream_chat_flutter/lib/src/message_modal/moderated_message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_modal/moderated_message_actions_modal.dart @@ -1,11 +1,10 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; -import 'package:stream_chat_flutter/src/message_action/message_action.dart'; import 'package:stream_chat_flutter/src/misc/adaptive_dialog_action.dart'; -import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template moderatedMessageActionsModal} /// A modal that is shown when a message is flagged by moderation policies. @@ -24,7 +23,6 @@ class ModeratedMessageActionsModal extends StatelessWidget { super.key, required this.message, required this.messageActions, - this.onActionTap, }); /// The message object that actions will be performed on. @@ -34,14 +32,8 @@ class ModeratedMessageActionsModal extends StatelessWidget { /// List of custom actions that will be displayed in the modal. /// - /// Each action is represented by a [StreamMessageAction] object which defines - /// the action's appearance and behavior. - final List messageActions; - - /// Callback triggered when a moderated message action is tapped. - /// - /// Provides the tapped [MessageAction] object to the callback. - final OnMessageActionTap? onActionTap; + /// Each action is represented by a [StreamContextMenuAction] object. + final List messageActions; @override Widget build(BuildContext context) { @@ -52,12 +44,9 @@ class ModeratedMessageActionsModal extends StatelessWidget { final actions = [ ...messageActions.map( (action) => AdaptiveDialogAction( - onPressed: switch (onActionTap) { - final onTap? => () => onTap.call(action.action), - _ => null, - }, - isDestructiveAction: action.isDestructive, - child: action.title ?? const Empty(), + onPressed: () => Navigator.pop(context, action.props.value), + isDestructiveAction: action.props.isDestructive, + child: action.props.label, ), ), ]; diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart index 0c0961c0ef..0298759cb5 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart @@ -92,8 +92,7 @@ class StreamMessageWidget extends StatefulWidget { this.attachmentPadding = EdgeInsets.zero, this.widthFactor = 0.78, this.onQuotedMessageTap, - this.customActions = const [], - this.onCustomActionTap, + this.actionsBuilder, this.onAttachmentTap, this.imageAttachmentThumbnailSize = const Size(400, 400), this.imageAttachmentThumbnailResizeType = 'clip', @@ -368,15 +367,8 @@ class StreamMessageWidget extends StatefulWidget { /// Note: Only used in desktop devices (web and desktop). final OnReactionsHover? onReactionsHover; - /// {@template customActions} - /// List of custom actions shown on message long tap - /// {@endtemplate} - final List customActions; - - /// Callback for when a custom message action is tapped. - /// - /// {@macro onMessageActionTap} - final OnMessageActionTap? onCustomActionTap; + /// {@macro messageActionsBuilder} + final MessageActionsBuilder? actionsBuilder; /// {@macro onAttachmentWidgetTap} final OnAttachmentWidgetTap? onAttachmentTap; @@ -463,8 +455,7 @@ class StreamMessageWidget extends StatefulWidget { OnMessageLongPress? onMessageLongPress, OnReactionsTap? onReactionsTap, OnReactionsHover? onReactionsHover, - List? customActions, - OnMessageActionTap? onCustomActionTap, + MessageActionsBuilder? actionsBuilder, OnAttachmentWidgetTap? onAttachmentTap, Widget Function(BuildContext, User)? userAvatarBuilder, Size? imageAttachmentThumbnailSize, @@ -527,8 +518,7 @@ class StreamMessageWidget extends StatefulWidget { onMessageLongPress: onMessageLongPress ?? this.onMessageLongPress, onReactionsTap: onReactionsTap ?? this.onReactionsTap, onReactionsHover: onReactionsHover ?? this.onReactionsHover, - customActions: customActions ?? this.customActions, - onCustomActionTap: onCustomActionTap ?? this.onCustomActionTap, + actionsBuilder: actionsBuilder ?? this.actionsBuilder, onAttachmentTap: onAttachmentTap ?? this.onAttachmentTap, userAvatarBuilder: userAvatarBuilder ?? this.userAvatarBuilder, imageAttachmentThumbnailSize: imageAttachmentThumbnailSize ?? this.imageAttachmentThumbnailSize, @@ -677,11 +667,16 @@ class _StreamMessageWidgetState extends State // show any context menu actions. if (messageState.isDeleted || messageState.isOutgoing) return child; + final channel = StreamChannel.of(context).channel; final menuItems = _buildDesktopOrWebActions(context, message); if (menuItems.isEmpty) return MouseRegion(child: child); return ContextMenuRegion( - contextMenuBuilder: (_, anchor) => ContextMenu( + onSelected: (result) { + if (result is! MessageAction) return; + return _onActionTap(context, channel, result).ignore(); + }, + menuBuilder: (_, anchor) => ContextMenu( anchor: anchor, menuItems: menuItems, ), @@ -761,24 +756,21 @@ class _StreamMessageWidgetState extends State ); } - List _buildBouncedErrorMessageActions({ + List _buildBouncedErrorMessageActions({ required BuildContext context, required Message message, }) { - final actions = StreamMessageActionsBuilder.buildBouncedErrorActions( + return StreamMessageActionsBuilder.buildBouncedErrorActions( context: context, message: message, ); - - return actions; } - List _buildMessageActions({ + List _buildMessageActions({ required BuildContext context, required Message message, required Channel channel, OwnUser? currentUser, - List? customActions, }) { final actions = StreamMessageActionsBuilder.buildActions( @@ -786,9 +778,8 @@ class _StreamMessageWidgetState extends State message: message, channel: channel, currentUser: currentUser, - customActions: customActions, )..retainWhere( - (it) => switch (it.action) { + (it) => switch (it.props.value) { QuotedReply() => widget.showReplyMessage, ThreadReply() => widget.showThreadReplyMessage, MarkUnread() => widget.showMarkUnreadMessage, @@ -802,7 +793,11 @@ class _StreamMessageWidgetState extends State }, ); - return actions; + if (widget.actionsBuilder case final builder?) { + return builder(context, actions); + } + + return StreamContextMenuAction.partitioned(items: actions); } List _buildDesktopOrWebActions( @@ -820,24 +815,12 @@ class _StreamMessageWidgetState extends State BuildContext context, Message message, ) { - final channel = StreamChannel.of(context).channel; - - final actions = _buildBouncedErrorMessageActions( + final actions = StreamMessageActionsBuilder.buildBouncedErrorActions( context: context, message: message, ); - return [ - ...actions.map((action) { - return StreamMessageActionItem( - action: action, - onTap: (action) async { - final popped = await Navigator.of(context).maybePop(); - if (popped) return _onActionTap(context, channel, action).ignore(); - }, - ); - }), - ]; + return StreamContextMenuAction.partitioned(items: actions); } List _buildMessageDesktopOrWebActions( @@ -853,45 +836,27 @@ class _StreamMessageWidgetState extends State message: message, channel: channel, currentUser: currentUser, - customActions: widget.customActions, ); - void onActionTap(MessageAction action) async { - final popped = await Navigator.of(context).maybePop(); - if (popped) return _onActionTap(context, channel, action).ignore(); + void onReactionPicked(Reaction reaction) { + final action = SelectReaction(message: message, reaction: reaction); + return Navigator.pop(context, action); // Pop the modal with the selected reaction action } return [ - if (showPicker) - widget.reactionPickerBuilder( - context, - message, - (reaction) => onActionTap( - SelectReaction(message: message, reaction: reaction), - ), - ), - ...actions.map((action) { - return StreamMessageActionItem( - action: action, - onTap: onActionTap, - ); - }), + if (showPicker) widget.reactionPickerBuilder(context, message, onReactionPicked), + ...actions, ]; } Future _showMessageReactionsModal( BuildContext context, Message message, - ) { + ) async { final channel = StreamChannel.of(context).channel; final showPicker = widget.showReactionPicker && channel.canSendReaction; - void onReactionPicked(SelectReaction action) async { - final popped = await Navigator.of(context).maybePop(); - if (popped) return _onActionTap(context, channel, action).ignore(); - } - - return showStreamDialog( + final action = await showStreamDialog( context: context, useRootNavigator: false, builder: (_) => StreamChatConfiguration( @@ -904,7 +869,6 @@ class _StreamMessageWidgetState extends State onUserAvatarTap: widget.onUserAvatarTap, showReactionPicker: showPicker, reactionPickerBuilder: widget.reactionPickerBuilder, - onReactionPicked: onReactionPicked, messageWidget: StreamChannel( channel: channel, child: widget.copyWith( @@ -926,6 +890,9 @@ class _StreamMessageWidgetState extends State ), ), ); + + if (action is! MessageAction) return; + return _onActionTap(context, channel, action).ignore(); } Future _onMessageLongPressed( @@ -961,20 +928,17 @@ class _StreamMessageWidgetState extends State message: message, ); - void onActionTap(MessageAction action) async { - final popped = await Navigator.of(context).maybePop(); - if (popped) return _onActionTap(context, channel, action).ignore(); - } - - return showStreamDialog( + final action = await showStreamDialog( context: context, useRootNavigator: false, builder: (_) => ModeratedMessageActionsModal( message: message, messageActions: actions, - onActionTap: onActionTap, ), ); + + if (action is! MessageAction) return; + return _onActionTap(context, channel, action).ignore(); } Future _onMessageActions( @@ -991,7 +955,7 @@ class _StreamMessageWidgetState extends State Future _showMessageActionModalDialog( BuildContext context, Message message, - ) { + ) async { final channel = StreamChannel.of(context).channel; final currentUser = channel.client.state.currentUser; final showPicker = widget.showReactionPicker && channel.canSendReaction; @@ -1001,15 +965,9 @@ class _StreamMessageWidgetState extends State message: message, channel: channel, currentUser: currentUser, - customActions: widget.customActions, ); - void onActionTap(MessageAction action) async { - final popped = await Navigator.of(context).maybePop(); - if (popped) return _onActionTap(context, channel, action).ignore(); - } - - return showStreamDialog( + final action = await showStreamDialog( context: context, useRootNavigator: false, builder: (_) => StreamChatConfiguration( @@ -1022,7 +980,6 @@ class _StreamMessageWidgetState extends State messageActions: actions, showReactionPicker: showPicker, reactionPickerBuilder: widget.reactionPickerBuilder, - onActionTap: onActionTap, messageWidget: StreamChannel( channel: channel, child: widget.copyWith( @@ -1044,6 +1001,9 @@ class _StreamMessageWidgetState extends State ), ), ); + + if (action is! MessageAction) return; + return _onActionTap(context, channel, action).ignore(); } Future _onActionTap( @@ -1066,7 +1026,6 @@ class _StreamMessageWidgetState extends State ResendMessage() => channel.retryMessage(action.message), QuotedReply() => widget.onReplyTap?.call(action.message), ThreadReply() => widget.onThreadTap?.call(action.message), - CustomMessageAction() => widget.onCustomActionTap?.call(action), }; } diff --git a/packages/stream_chat_flutter/lib/src/misc/stream_modal.dart b/packages/stream_chat_flutter/lib/src/misc/stream_modal.dart index 19269ebd9c..d25ee5ad6c 100644 --- a/packages/stream_chat_flutter/lib/src/misc/stream_modal.dart +++ b/packages/stream_chat_flutter/lib/src/misc/stream_modal.dart @@ -55,7 +55,7 @@ Future showStreamDialog({ }, pageBuilder: (context, animation, secondaryAnimation) { final pageChild = Builder(builder: builder); - return capturedThemes.wrap(pageChild); + return capturedThemes.wrap(StreamChatTheme(data: theme, child: pageChild)); }, ); } diff --git a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart b/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart index bbaa7f0cca..d078eb4cff 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template onReactionPicked} /// Callback called when a reaction is picked. @@ -114,8 +115,6 @@ class StreamReactionPicker extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); - final ownReactions = [...?message.ownReactions]; final ownReactionsMap = {for (final it in ownReactions) it.type: it}; @@ -157,7 +156,7 @@ class StreamReactionPicker extends StatelessWidget { return Material( borderRadius: borderRadius, clipBehavior: Clip.antiAlias, - color: backgroundColor ?? theme.colorTheme.barsBg, + color: backgroundColor ?? StreamColors.transparent, child: Padding( padding: padding.add(extraPadding), child: switch (scrollable) { diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 0f6dba1a6f..edfb37280a 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -3,7 +3,15 @@ export 'package:photo_manager/photo_manager.dart' show ThumbnailSize, ThumbnailF export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; export 'package:stream_core_flutter/stream_core_flutter.dart' - show StreamAvatarSize, StreamTheme, StreamComponentFactory; + show + StreamAvatarSize, + StreamTheme, + StreamIcons, + StreamThemeExtension, + StreamComponentFactory, + StreamContextMenu, + StreamContextMenuAction, + StreamContextMenuSeparator; export 'src/ai_assistant/ai_typing_indicator_view.dart'; export 'src/ai_assistant/stream_typewriter_builder.dart'; @@ -57,7 +65,6 @@ export 'src/keyboard_shortcuts/keyboard_shortcut_runner.dart'; export 'src/localization/stream_chat_localizations.dart'; export 'src/localization/translations.dart' show DefaultTranslations; export 'src/message_action/message_action.dart'; -export 'src/message_action/message_action_item.dart'; export 'src/message_action/message_actions_builder.dart'; export 'src/message_input/attachment_picker/stream_attachment_picker.dart'; export 'src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart'; diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 8512892e71..867e947803 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: f0af49b3cb1426efed625d0a24e58874363e9538 + ref: 5b95b4821fd98d77eccefba9d9afa7fdc071370b path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 diff --git a/packages/stream_chat_flutter/test/src/message_action/message_action_item_test.dart b/packages/stream_chat_flutter/test/src/message_action/message_action_item_test.dart deleted file mode 100644 index 5dd3a2ac1e..0000000000 --- a/packages/stream_chat_flutter/test/src/message_action/message_action_item_test.dart +++ /dev/null @@ -1,169 +0,0 @@ -import 'package:alchemist/alchemist.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -void main() { - final message = Message( - id: 'test-message', - text: 'Hello, world!', - user: User(id: 'test-user'), - ); - - testWidgets('renders with title and icon', (tester) async { - await tester.pumpWidget( - _wrapWithMaterialApp( - StreamMessageActionItem( - action: StreamMessageAction( - title: const Text('Reply'), - leading: const Icon(Icons.reply), - action: QuotedReply(message: message), - ), - ), - ), - ); - - expect(find.text('Reply'), findsOneWidget); - expect(find.byIcon(Icons.reply), findsOneWidget); - }); - - testWidgets('calls onTap when tapped', (tester) async { - Message? tappedMessage; - - await tester.pumpWidget( - _wrapWithMaterialApp( - StreamMessageActionItem( - onTap: (action) => tappedMessage = action.message, - action: StreamMessageAction( - title: const Text('Reply'), - leading: const Icon(Icons.reply), - action: QuotedReply(message: message), - ), - ), - ), - ); - - await tester.tap(find.byType(InkWell)); - await tester.pump(); - - expect(tappedMessage, message); - }); - - testWidgets( - 'applies destructive styling when isDestructive is true', - (tester) async { - await tester.pumpWidget( - _wrapWithMaterialApp( - StreamMessageActionItem( - action: StreamMessageAction( - isDestructive: true, - title: const Text('Delete'), - leading: const Icon(Icons.delete), - action: DeleteMessage(message: message), - ), - ), - ), - ); - - expect(find.text('Delete'), findsOneWidget); - expect(find.byIcon(Icons.delete), findsOneWidget); - - // The icon and text should have the error color - final theme = StreamChatTheme.of(tester.element(find.text('Delete'))); - final iconTheme = IconTheme.of(tester.element(find.byIcon(Icons.delete))); - - expect(iconTheme.color, theme.colorTheme.accentError); - }, - ); - - group('StreamMessageActionItem Golden Tests', () { - for (final brightness in Brightness.values) { - final theme = brightness.name; - - // Test standard action - goldenTest( - 'StreamMessageActionItem (Reply) in $theme theme', - fileName: 'stream_message_action_item_reply_$theme', - constraints: const BoxConstraints(maxWidth: 300, maxHeight: 60), - builder: () => _wrapWithMaterialApp( - brightness: brightness, - StreamMessageActionItem( - action: StreamMessageAction( - title: const Text('Reply'), - leading: const Icon(Icons.reply), - action: QuotedReply(message: message), - ), - ), - ), - ); - - // Test destructive action (like delete) - goldenTest( - 'StreamMessageActionItem (Delete) in $theme theme', - fileName: 'stream_message_action_item_delete_$theme', - constraints: const BoxConstraints(maxWidth: 300, maxHeight: 60), - builder: () => _wrapWithMaterialApp( - brightness: brightness, - StreamMessageActionItem( - action: StreamMessageAction( - isDestructive: true, - title: const Text('Delete Message'), - leading: const Icon(Icons.delete), - action: DeleteMessage(message: message), - ), - ), - ), - ); - - // Test with custom styling - goldenTest( - 'StreamMessageActionItem with custom styling in $theme theme', - fileName: 'stream_message_action_item_custom_styling_$theme', - constraints: const BoxConstraints(maxWidth: 300, maxHeight: 60), - builder: () => _wrapWithMaterialApp( - brightness: brightness, - StreamMessageActionItem( - action: StreamMessageAction( - title: const Text('Styled Action'), - titleTextStyle: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - color: Colors.purple[700], - ), - leading: const Icon(Icons.favorite), - iconColor: Colors.pink[400], - backgroundColor: Colors.amber[100], - action: CustomMessageAction(message: message), - ), - ), - ), - ); - } - }); -} - -Widget _wrapWithMaterialApp( - Widget child, { - Brightness? brightness, -}) { - return MaterialApp( - debugShowCheckedModeBanner: false, - home: StreamChatTheme( - data: StreamChatThemeData(brightness: brightness), - child: Builder( - builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: child, - ), - ), - ); - }, - ), - ), - ); -} diff --git a/packages/stream_chat_flutter/test/src/message_action/message_actions_builder_test.dart b/packages/stream_chat_flutter/test/src/message_action/message_actions_builder_test.dart index 7d3cf82fc0..427dd53215 100644 --- a/packages/stream_chat_flutter/test/src/message_action/message_actions_builder_test.dart +++ b/packages/stream_chat_flutter/test/src/message_action/message_actions_builder_test.dart @@ -132,31 +132,6 @@ void main() { expect(actions.isEmpty, isTrue); }); - testWidgets('includes custom actions', (tester) async { - final context = await _getContext(tester); - final customAction = StreamMessageAction( - title: const Text('Custom'), - leading: const Icon(Icons.star), - action: CustomMessageAction( - message: message, - extraData: {'customKey': 'customValue'}, - ), - ); - - final channel = _getChannelWithCapabilities(allChannelCapabilities); - final actions = StreamMessageActionsBuilder.buildActions( - context: context, - message: message, - channel: channel, - currentUser: currentUser, - customActions: [customAction], - ); - - actions.expects( - reason: 'Custom action should be included', - ); - }); - group('permission-based actions', () { testWidgets( 'includes/excludes edit action based on authorship', @@ -557,15 +532,15 @@ void main() { }); } -/// Extension on Set to simplify action type checks. -extension StreamMessageActionSetExtension on List { +/// Extension on action lists to simplify message action type checks. +extension StreamMessageActionSetExtension on List { void expects({String? reason}) { - final containsActionType = this.any((it) => it.action is T); + final containsActionType = this.any((it) => it.props.value is T); return expect(containsActionType, isTrue, reason: reason); } void notExpects({String? reason}) { - final containsActionType = this.any((it) => it.action is T); + final containsActionType = this.any((it) => it.props.value is T); return expect(containsActionType, isFalse, reason: reason); } } diff --git a/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart index 9f1dbab6ee..8d77a506b7 100644 --- a/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart @@ -14,27 +14,26 @@ void main() { user: User(id: 'test-user', name: 'Test User'), ); - final messageActions = [ - StreamMessageAction( - title: const Text('Reply'), + final messageActions = >[ + StreamContextMenuAction( + label: const Text('Reply'), leading: const StreamSvgIcon(icon: StreamSvgIcons.reply), - action: QuotedReply(message: message), + value: QuotedReply(message: message), ), - StreamMessageAction( - title: const Text('Thread Reply'), + StreamContextMenuAction( + label: const Text('Thread Reply'), leading: const StreamSvgIcon(icon: StreamSvgIcons.threadReply), - action: ThreadReply(message: message), + value: ThreadReply(message: message), ), - StreamMessageAction( - title: const Text('Copy Message'), + StreamContextMenuAction( + label: const Text('Copy Message'), leading: const StreamSvgIcon(icon: StreamSvgIcons.copy), - action: CopyMessage(message: message), + value: CopyMessage(message: message), ), - StreamMessageAction( - isDestructive: true, - title: const Text('Delete Message'), + StreamContextMenuAction.destructive( + label: const Text('Delete Message'), leading: const StreamSvgIcon(icon: StreamSvgIcons.delete), - action: DeleteMessage(message: message), + value: DeleteMessage(message: message), ), ]; @@ -77,7 +76,7 @@ void main() { }); testWidgets( - 'calls onActionTap with SelectReaction when reaction is selected', + 'pops with SelectReaction when reaction is selected', (tester) async { MessageAction? messageAction; @@ -95,16 +94,26 @@ void main() { await tester.pumpWidget( _wrapWithMaterialApp( - StreamMessageActionsModal( - message: message, - messageActions: messageActions, - messageWidget: const Text('Message Widget'), - showReactionPicker: true, - onActionTap: (action) => messageAction = action, - ), reactionIcons: testReactionIcons, + Builder( + builder: (context) => TextButton( + onPressed: () async { + messageAction = await showStreamDialog( + context: context, + builder: (_) => StreamMessageActionsModal( + message: message, + messageActions: messageActions, + messageWidget: const Text('Message Widget'), + showReactionPicker: true, + ), + ); + }, + child: const Text('Open Dialog'), + ), + ), ), ); + await tester.tap(find.text('Open Dialog')); // Use a longer timeout to ensure everything is rendered await tester.pumpAndSettle(const Duration(seconds: 1)); @@ -119,17 +128,20 @@ void main() { await tester.pumpAndSettle(); expect(messageAction, isA()); - // Verify callback was called with correct reaction type + // Verify the popped value has correct reaction type expect((messageAction! as SelectReaction).reaction.type, 'like'); - // Find and tap the second reaction (love) + // Open dialog again and tap the second reaction (love) + await tester.tap(find.text('Open Dialog')); + await tester.pumpAndSettle(const Duration(seconds: 1)); + final loveIconFinder = find.byIcon(Icons.favorite); expect(loveIconFinder, findsOneWidget); await tester.tap(loveIconFinder); await tester.pumpAndSettle(); expect(messageAction, isA()); - // Verify callback was called with correct reaction type + // Verify the popped value has correct reaction type expect((messageAction! as SelectReaction).reaction.type, 'love'); }, ); @@ -234,27 +246,29 @@ Widget _wrapWithMaterialApp( return Portal( child: MaterialApp( debugShowCheckedModeBanner: false, - home: StreamChatConfiguration( + theme: ThemeData(brightness: brightness), + builder: (context, child) => StreamChatConfiguration( data: StreamChatConfigurationData(reactionIcons: reactionIcons), child: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder( - builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: ColoredBox( - color: theme.colorTheme.overlay, - child: Padding( - padding: const EdgeInsets.all(8), - child: child, - ), - ), - ); - }, - ), + child: child ?? const SizedBox.shrink(), ), ), + home: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: ColoredBox( + color: theme.colorTheme.overlay, + child: Padding( + padding: const EdgeInsets.all(8), + child: child, + ), + ), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart index ed301bb778..dbc2b34769 100644 --- a/packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart @@ -111,7 +111,7 @@ void main() { ); testWidgets( - 'calls onReactionPicked with SelectReaction when reaction is selected', + 'pops with SelectReaction when reaction is selected', (tester) async { MessageAction? messageAction; @@ -139,38 +139,52 @@ void main() { _wrapWithMaterialApp( client: mockClient, reactionIcons: testReactionIcons, - StreamMessageReactionsModal( - message: message, - messageWidget: const Text('Message Widget'), - onReactionPicked: (action) => messageAction = action, + Builder( + builder: (context) => TextButton( + onPressed: () async { + messageAction = await showStreamDialog( + context: context, + builder: (_) => StreamMessageReactionsModal( + message: message, + messageWidget: const Text('Message Widget'), + ), + ); + }, + child: const Text('Open Dialog'), + ), ), ), ); + await tester.tap(find.text('Open Dialog')); + // Use a longer timeout to ensure everything is rendered await tester.pumpAndSettle(const Duration(seconds: 1)); // Verify reaction picker is shown expect(find.byType(StreamReactionPicker), findsOneWidget); - // Find and tap the camera reaction (camera) + // Find and tap the camera reaction final reactionIconFinder = find.byIcon(Icons.camera); expect(reactionIconFinder, findsOneWidget); await tester.tap(reactionIconFinder); await tester.pumpAndSettle(); expect(messageAction, isA()); - // Verify callback was called with correct reaction type + // Verify the popped value has correct reaction type expect((messageAction! as SelectReaction).reaction.type, 'camera'); - // Find and tap the call reaction (call) - final loveIconFinder = find.byIcon(Icons.call); - expect(loveIconFinder, findsOneWidget); - await tester.tap(loveIconFinder); + // Open dialog again and tap the call reaction + await tester.tap(find.text('Open Dialog')); + await tester.pumpAndSettle(const Duration(seconds: 1)); + + final callIconFinder = find.byIcon(Icons.call); + expect(callIconFinder, findsOneWidget); + await tester.tap(callIconFinder); await tester.pumpAndSettle(); expect(messageAction, isA()); - // Verify callback was called with correct reaction type + // Verify the popped value has correct reaction type expect((messageAction! as SelectReaction).reaction.type, 'call'); }, ); @@ -214,7 +228,6 @@ void main() { StreamMessageReactionsModal( message: message, messageWidget: buildMessageWidget(), - onReactionPicked: (_) {}, ), ), ); @@ -230,7 +243,6 @@ void main() { message: message, messageWidget: buildMessageWidget(reverse: true), reverse: true, - onReactionPicked: (_) {}, ), ), ); @@ -244,35 +256,33 @@ Widget _wrapWithMaterialApp( Brightness? brightness, List? reactionIcons, }) { - return Portal( - child: MaterialApp( - debugShowCheckedModeBanner: false, - home: StreamChat( + return MaterialApp( + debugShowCheckedModeBanner: false, + theme: ThemeData(brightness: brightness), + builder: (context, child) => Portal( + child: StreamChat( client: client, // Mock the connectivity stream to always return wifi. connectivityStream: Stream.value([ConnectivityResult.wifi]), - child: StreamChatConfiguration( - data: StreamChatConfigurationData(reactionIcons: reactionIcons), - child: StreamChatTheme( - data: StreamChatThemeData(brightness: brightness), - child: Builder( - builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: ColoredBox( - color: theme.colorTheme.overlay, - child: Padding( - padding: const EdgeInsets.all(8), - child: child, - ), - ), - ); - }, + streamChatThemeData: StreamChatThemeData(brightness: brightness), + streamChatConfigData: StreamChatConfigurationData(reactionIcons: reactionIcons), + child: child, + ), + ), + home: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: ColoredBox( + color: theme.colorTheme.overlay, + child: Padding( + padding: const EdgeInsets.all(8), + child: child, ), ), - ), - ), + ); + }, ), ); } diff --git a/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart index fa8a649810..ca8c5467ea 100644 --- a/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart @@ -18,22 +18,21 @@ void main() { ), ); - final messageActions = [ - StreamMessageAction( - title: const Text('Send Anyway'), + final messageActions = >[ + StreamContextMenuAction( + label: const Text('Send Anyway'), leading: const StreamSvgIcon(icon: StreamSvgIcons.send), - action: ResendMessage(message: message), + value: ResendMessage(message: message), ), - StreamMessageAction( - title: const Text('Edit Message'), + StreamContextMenuAction( + label: const Text('Edit Message'), leading: const StreamSvgIcon(icon: StreamSvgIcons.edit), - action: EditMessage(message: message), + value: EditMessage(message: message), ), - StreamMessageAction( - isDestructive: true, - title: const Text('Delete Message'), + StreamContextMenuAction.destructive( + label: const Text('Delete Message'), leading: const StreamSvgIcon(icon: StreamSvgIcons.delete), - action: HardDeleteMessage(message: message), + value: HardDeleteMessage(message: message), ), ]; @@ -61,19 +60,30 @@ void main() { expect(find.text('Delete Message'), findsOneWidget); }); - testWidgets('action buttons call the correct callbacks', (tester) async { + testWidgets('action buttons pop with the correct value', (tester) async { MessageAction? messageAction; await tester.pumpWidget( _wrapWithMaterialApp( - ModeratedMessageActionsModal( - message: message, - messageActions: messageActions, - onActionTap: (action) => messageAction = action, + Builder( + builder: (context) => TextButton( + onPressed: () async { + messageAction = await showStreamDialog( + context: context, + builder: (_) => ModeratedMessageActionsModal( + message: message, + messageActions: messageActions, + ), + ); + }, + child: const Text('Open Dialog'), + ), ), ), ); + // Open dialog and tap Send Anyway + await tester.tap(find.text('Open Dialog')); await tester.pumpAndSettle(); // Tap on Send Anyway button @@ -81,12 +91,16 @@ void main() { await tester.pumpAndSettle(); expect(messageAction, isA()); - // Tap on Edit Message button + // Open dialog and tap Edit Message + await tester.tap(find.text('Open Dialog')); + await tester.pumpAndSettle(); await tester.tap(find.text('Edit Message')); await tester.pumpAndSettle(); expect(messageAction, isA()); - // Tap on Delete Message button + // Open dialog and tap Delete Message + await tester.tap(find.text('Open Dialog')); + await tester.pumpAndSettle(); await tester.tap(find.text('Delete Message')); await tester.pumpAndSettle(); expect(messageAction, isA()); @@ -119,23 +133,28 @@ Widget _wrapWithMaterialApp( }) { return MaterialApp( debugShowCheckedModeBanner: false, - home: StreamChatTheme( - data: StreamChatThemeData(brightness: brightness), - child: Builder( - builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: ColoredBox( - color: theme.colorTheme.overlay, - child: Padding( - padding: const EdgeInsets.all(8), - child: child, - ), - ), - ); - }, + theme: ThemeData(brightness: brightness), + builder: (context, child) => StreamChatConfiguration( + data: StreamChatConfigurationData(), + child: StreamChatTheme( + data: StreamChatThemeData(brightness: brightness), + child: child ?? const SizedBox.shrink(), ), ), + home: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: ColoredBox( + color: theme.colorTheme.overlay, + child: Padding( + padding: const EdgeInsets.all(8), + child: child, + ), + ), + ); + }, + ), ); } diff --git a/sample_app/lib/pages/channel_page.dart b/sample_app/lib/pages/channel_page.dart index ba52c7cb8f..66975951fd 100644 --- a/sample_app/lib/pages/channel_page.dart +++ b/sample_app/lib/pages/channel_page.dart @@ -205,6 +205,7 @@ class _ChannelPageState extends State { StreamMessageWidget defaultMessageWidget, ) { final theme = StreamChatTheme.of(context); + final icons = context.streamIcons; final textTheme = theme.textTheme; final colorTheme = theme.colorTheme; @@ -217,46 +218,53 @@ class _ChannelPageState extends State { final isSentByCurrentUser = message.user?.id == currentUser?.id; final canDeleteOwnMessage = channel.canDeleteOwnMessage; - final customOptions = [ - if (isSentByCurrentUser && canDeleteOwnMessage) - StreamMessageAction( - isDestructive: true, - title: const Text('Delete Message for Me'), - action: DeleteMessageForMe(message: message), - leading: const StreamSvgIcon(icon: StreamSvgIcons.delete), - ), - if (channelConfig?.userMessageReminders == true) ...[ - if (reminder != null) ...[ - StreamMessageAction( - title: const Text('Edit Reminder'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.time), - action: EditReminder(message: message, reminder: reminder), - ), - StreamMessageAction( - title: const Text('Remove from later'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.checkAll), - action: RemoveReminder(message: message, reminder: reminder), - ), - ] else ...[ - StreamMessageAction( - title: const Text('Remind me'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.time), - action: CreateReminder(message: message), - ), - StreamMessageAction( - title: const Text('Save for later'), - leading: const Icon(Icons.bookmark_border), - action: CreateBookmark(message: message), - ), + List actionsBuilder( + BuildContext context, + List defaultActions, + ) { + return StreamContextMenuAction.partitioned( + items: [ + ...defaultActions, + if (isSentByCurrentUser && canDeleteOwnMessage) + StreamContextMenuAction.destructive( + label: const Text('Delete Message for Me'), + leading: Icon(icons.trashBin), + onTap: () => _deleteMessageForMe(message), + ), + if (channelConfig?.userMessageReminders == true) ...[ + if (reminder != null) ...[ + StreamContextMenuAction( + label: const Text('Edit Reminder'), + leading: Icon(icons.clock), + onTap: () => _editReminder(message, reminder), + ), + StreamContextMenuAction( + label: const Text('Remove from later'), + leading: Icon(icons.checkmark2), + onTap: () => _removeReminder(message, reminder), + ), + ] else ...[ + StreamContextMenuAction( + label: const Text('Remind me'), + leading: Icon(icons.bellNotification), + onTap: () => _createReminder(message), + ), + StreamContextMenuAction( + label: const Text('Save for later'), + leading: Icon(icons.fileBend), + onTap: () => _createBookmark(message), + ), + ], + ], + if (channelConfig?.deliveryEvents == true) + StreamContextMenuAction( + label: const Text('Message Info'), + leading: Icon(icons.circleInfoTooltip), + onTap: () => _showMessageInfo(message), + ), ], - ], - if (channelConfig?.deliveryEvents == true) - StreamMessageAction( - title: const Text('Message Info'), - leading: const Icon(Icons.info_outline_rounded), - action: ShowMessageInfo(message: message), - ), - ]; + ); + } final locationAttachmentBuilder = LocationAttachmentBuilder( onAttachmentTap: (location) => showLocationDetailDialog( @@ -300,17 +308,8 @@ class _ChannelPageState extends State { ), defaultMessageWidget.copyWith( onReplyTap: _reply, - customActions: customOptions, + actionsBuilder: actionsBuilder, showEditMessage: message.sharedLocation == null, - onCustomActionTap: (it) async => await switch (it) { - CreateReminder() => _createReminder(it.message), - CreateBookmark() => _createBookmark(it.message), - EditReminder() => _editReminder(it.message, it.reminder), - RemoveReminder() => _removeReminder(it.message, it.reminder), - DeleteMessageForMe() => _deleteMessageForMe(it.message), - ShowMessageInfo() => _showMessageInfo(it.message), - _ => null, - }, attachmentBuilders: [locationAttachmentBuilder], onShowMessage: (message, channel) => GoRouter.of(context).goNamed( Routes.CHANNEL_PAGE.name, @@ -412,48 +411,3 @@ class _ChannelPageState extends State { return true; } } - -class ReminderMessageAction extends CustomMessageAction { - const ReminderMessageAction({ - required super.message, - this.reminder, - }); - - final MessageReminder? reminder; -} - -final class CreateReminder extends ReminderMessageAction { - const CreateReminder({required super.message}); -} - -final class CreateBookmark extends ReminderMessageAction { - const CreateBookmark({required super.message}); -} - -final class EditReminder extends ReminderMessageAction { - const EditReminder({ - required super.message, - required this.reminder, - }) : super(reminder: reminder); - - @override - final MessageReminder reminder; -} - -final class RemoveReminder extends ReminderMessageAction { - const RemoveReminder({ - required super.message, - required this.reminder, - }) : super(reminder: reminder); - - @override - final MessageReminder reminder; -} - -final class DeleteMessageForMe extends CustomMessageAction { - const DeleteMessageForMe({required super.message}); -} - -final class ShowMessageInfo extends CustomMessageAction { - const ShowMessageInfo({required super.message}); -} From b7333016d859fdd192b89010022fa079b720cf79 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Tue, 24 Feb 2026 15:22:45 +0100 Subject: [PATCH 08/33] feat(UI): composer attachments (#2514) * Add quoted message and basic attachment picker * fix incoming/outgoing style * Add basic og Attachments * improve legacy attachment list * Fix client state in test * Add documentation * skip input tests for now * bug fix on inputfield * chore: Update Goldens * use new MessageComposerAttachmentLinkPreview # Conflicts: # melos.yaml # packages/stream_chat_flutter/pubspec.yaml * improve attachments in the composer * Start with support for all attachments * added option for recording in composer * working voice recording feature * minor improvements in voice attachments * code cleanup * don't use list for file attachment * chore: Update Goldens * Fix composer attachment tests * Make trailing button stateless * renamings from core * remove default value * remove unused import * chore: Update Goldens --------- Co-authored-by: renefloor <15101411+renefloor@users.noreply.github.com> --- melos.yaml | 8 +- .../lib/src/audio/audio_playlist_state.dart | 5 + .../message_composer_factory.dart | 27 +- .../message_composer_input_header.dart | 116 ++++- .../message_composer_input_leading.dart | 20 +- .../message_composer_input_trailing.dart | 57 +- .../message_composer_leading.dart | 49 +- .../message_composer_recording_locked.dart | 122 +++++ .../message_composer_recording_ongoing.dart | 88 ++++ .../message_composer_trailing.dart | 6 + .../stream_chat_message_composer.dart | 275 +++++++++- .../stream_attachment_picker.dart | 35 -- .../audio_recorder/stream_audio_recorder.dart | 29 +- .../message_input/stream_message_input.dart | 12 +- .../stream_message_input_attachment_list.dart | 492 +++++++++--------- .../lib/src/utils/extensions.dart | 1 + packages/stream_chat_flutter/pubspec.yaml | 6 +- .../src/avatars/goldens/ci/user_avatar_0.png | Bin 2716 -> 2747 bytes .../goldens/ci/edit_message_sheet_0.png | Bin 5520 -> 5501 bytes ...io_recorder_button_recording_hold_dark.png | Bin 4885 -> 4719 bytes ...o_recorder_button_recording_hold_light.png | Bin 4838 -> 4582 bytes ..._recorder_button_recording_locked_dark.png | Bin 6711 -> 6755 bytes ...recorder_button_recording_locked_light.png | Bin 6595 -> 6584 bytes ...recorder_button_recording_stopped_dark.png | Bin 5896 -> 5927 bytes ...ecorder_button_recording_stopped_light.png | Bin 5814 -> 5795 bytes .../message_input_attachment_list_test.dart | 14 +- .../src/message_input/message_input_test.dart | 13 + .../moderated_message_actions_modal_dark.png | Bin 2500 -> 2496 bytes .../moderated_message_actions_modal_light.png | Bin 2534 -> 2519 bytes .../ci/stream_message_actions_modal_dark.png | Bin 4796 -> 4823 bytes .../ci/stream_message_actions_modal_light.png | Bin 5087 -> 5218 bytes ...am_message_actions_modal_reversed_dark.png | Bin 4880 -> 4837 bytes ...m_message_actions_modal_reversed_light.png | Bin 5074 -> 5191 bytes ...ons_modal_reversed_with_reactions_dark.png | Bin 8213 -> 8230 bytes ...ns_modal_reversed_with_reactions_light.png | Bin 8796 -> 8867 bytes ...sage_actions_modal_with_reactions_dark.png | Bin 8112 -> 8174 bytes ...age_actions_modal_with_reactions_light.png | Bin 8735 -> 8808 bytes .../stream_message_reactions_modal_dark.png | Bin 10996 -> 10510 bytes .../stream_message_reactions_modal_light.png | Bin 11095 -> 11106 bytes ..._message_reactions_modal_reversed_dark.png | Bin 11095 -> 10586 bytes ...message_reactions_modal_reversed_light.png | Bin 11078 -> 11117 bytes .../stream_chat_flutter/test/src/mocks.dart | 1 + .../ci/stream_reaction_picker_dark.png | Bin 3635 -> 2324 bytes .../ci/stream_reaction_picker_light.png | Bin 3817 -> 2580 bytes .../stream_reaction_picker_selected_dark.png | Bin 3701 -> 2495 bytes .../stream_reaction_picker_selected_light.png | Bin 3861 -> 2645 bytes .../android/app/src/main/AndroidManifest.xml | 5 + sample_app/devtools_options.yaml | 3 + sample_app/lib/app.dart | 2 +- 49 files changed, 1005 insertions(+), 381 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_recording_locked.dart create mode 100644 packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_recording_ongoing.dart create mode 100644 sample_app/devtools_options.yaml diff --git a/melos.yaml b/melos.yaml index 6f0166150a..a3e47fa6be 100644 --- a/melos.yaml +++ b/melos.yaml @@ -73,7 +73,7 @@ command: package_info_plus: ">=8.3.0 <10.0.0" path: ^1.8.3 path_provider: ^2.1.3 - photo_manager: ^3.2.0 + photo_manager: ^3.8.3 photo_view: ^0.15.0 provider: ^6.0.5 rate_limiter: ^1.0.0 @@ -93,9 +93,9 @@ command: svg_icon_widget: ^0.0.1 # TODO: Replace with hosted version before merging PR stream_core_flutter: - git: + git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 5b95b4821fd98d77eccefba9d9afa7fdc071370b + ref: da18aa04ad48d4d5bb429c9e90d9f0253c418fae path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 @@ -106,7 +106,7 @@ command: # List of all the dev_dependencies used in the project. dev_dependencies: - alchemist: ">=0.11.0 <0.14.0" + alchemist: ^0.13.0 build_runner: ^2.4.9 connectivity_plus_platform_interface: ^2.0.0 drift_dev: ^2.28.0 diff --git a/packages/stream_chat_flutter/lib/src/audio/audio_playlist_state.dart b/packages/stream_chat_flutter/lib/src/audio/audio_playlist_state.dart index 204dddd0c3..69b40caa83 100644 --- a/packages/stream_chat_flutter/lib/src/audio/audio_playlist_state.dart +++ b/packages/stream_chat_flutter/lib/src/audio/audio_playlist_state.dart @@ -149,6 +149,7 @@ class PlaylistTrack { /// {@macro playlistTrack} const PlaylistTrack({ required this.uri, + this.key, this.title, this.waveform = const [], this.duration = Duration.zero, @@ -156,6 +157,9 @@ class PlaylistTrack { this.state = TrackState.idle, }); + /// The key to identify the track. + final Object? key; + /// The uri of the track. final Uri uri; @@ -202,6 +206,7 @@ class PlaylistTrack { TrackState? state, }) { return PlaylistTrack( + key: key, uri: uri ?? this.uri, title: title ?? this.title, duration: duration ?? this.duration, diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_factory.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_factory.dart index 9cd9a238ca..281c525c43 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_factory.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_factory.dart @@ -2,8 +2,10 @@ import 'package:flutter/widgets.dart'; import 'package:stream_chat_flutter/src/components/message_composer/stream_chat_message_composer.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart' show StreamComponentBuilder; +/// A factory for building the message composer components. class StreamMessageComposerFactory { - StreamMessageComposerFactory({ + /// Creates a new instance of [StreamMessageComposerFactory]. + const StreamMessageComposerFactory({ this.messageComposer, this.leading, this.trailing, @@ -13,16 +15,35 @@ class StreamMessageComposerFactory { this.inputTrailing, }); + /// The main message composer component. Provide a builder if you want full control over the message composer. final StreamComponentBuilder? messageComposer; + + /// The leading component of the message composer. This is shown before the text input. + /// By default this contains a button to add attachments. final StreamComponentBuilder? leading; + + /// The trailing component of the message composer. This is shown after the text input. + /// By default this area is empty. final StreamComponentBuilder? trailing; + + /// The input component of the message composer. final StreamComponentBuilder? input; + + /// The leading component of the input area. + /// This is shown before the text input, but inside of the input area. + /// By default this area is empty. final StreamComponentBuilder? inputLeading; + + /// The header component of the input area. This is shown above the text input. + /// By default it shows the quoted message, attachments and OG attachment previews. final StreamComponentBuilder? inputHeader; + + /// The trailing component of the input area. This is shown after the text input. + /// By default it shows the send button and the microphone button. final StreamComponentBuilder? inputTrailing; - // placeholder for real implementation + /// placeholder for real implementation static StreamMessageComposerFactory? maybeOf(BuildContext context) { - return StreamMessageComposerFactory(); + return const StreamMessageComposerFactory(); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart index 8a7e043b03..8b249ba10f 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart @@ -1,28 +1,134 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_factory.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; +/// A widget that shows the input header of the message composer. +/// Uses the factory to show custom components or used the default implementation. class StreamMessageComposerInputHeader extends StatelessWidget { + /// Creates a new instance of [StreamMessageComposerInputHeader]. + /// [props] contains the properties for the message composer component. const StreamMessageComposerInputHeader({super.key, required this.props}); + /// The properties for the message composer component. final MessageComposerComponentProps props; @override Widget build(BuildContext context) { return StreamMessageComposerFactory.maybeOf(context)?.inputHeader?.call(context, props) ?? - DefaultStreamMessageComposerInputHeader(props: props); + _DefaultStreamMessageComposerInputHeader(props: props); } } -class DefaultStreamMessageComposerInputHeader extends StatelessWidget { - const DefaultStreamMessageComposerInputHeader({super.key, required this.props}); +class _DefaultStreamMessageComposerInputHeader extends StatelessWidget { + const _DefaultStreamMessageComposerInputHeader({required this.props}); final MessageComposerComponentProps props; + StreamMessageInputController get controller => props.controller; @override Widget build(BuildContext context) { - // TODO: implement header for attachments and quoted message - return const SizedBox.shrink(); + final quotedMessage = props.controller.message.quotedMessage; + final ogAttachment = props.controller.ogAttachment; + final nonOGAttachments = controller.attachments + .where((it) { + return it.titleLink == null; + }) + .toList(growable: false); + + final hasAttachments = nonOGAttachments.isNotEmpty; + final hasContent = quotedMessage != null || hasAttachments || ogAttachment != null; + + final spacing = context.streamSpacing; + final contentPadding = EdgeInsets.only( + left: spacing.xs, + right: spacing.xs, + ); + + return AnimatedSize( + duration: const Duration(milliseconds: 200), + alignment: Alignment.topCenter, + child: Padding( + padding: EdgeInsets.only( + top: hasContent ? spacing.xs : 0, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (quotedMessage != null) + Padding( + padding: contentPadding, + child: _QuotedMessageInHeader( + quotedMessage: quotedMessage, + onRemovePressed: controller.clearQuotedMessage, + currentUserId: props.currentUserId, + ), + ), + if (hasAttachments) + StreamMessageInputAttachmentList( + attachments: nonOGAttachments, + onRemovePressed: _onAttachmentRemovePressed, + ), + if (ogAttachment != null) + Padding( + padding: contentPadding, + child: MessageComposerLinkPreviewAttachment( + title: ogAttachment.title, + subtitle: ogAttachment.text, + image: ogAttachment.imageUrl != null ? CachedNetworkImageProvider(ogAttachment.imageUrl!) : null, + url: ogAttachment.titleLink, + onRemovePressed: () { + controller.clearOGAttachment(); + props.focusNode?.unfocus(); + }, + ), + ), + ], + ), + ), + ); + } + + // Default callback for removing an attachment. + Future _onAttachmentRemovePressed(Attachment attachment) async { + final file = attachment.file; + final uploadState = attachment.uploadState; + + if (file != null && !uploadState.isSuccess && !isWeb) { + await StreamAttachmentHandler.instance.deleteAttachmentFile( + attachmentFile: file, + ); + } + + controller.removeAttachmentById(attachment.id); + } +} + +class _QuotedMessageInHeader extends StatelessWidget { + const _QuotedMessageInHeader({ + required this.quotedMessage, + required this.onRemovePressed, + required this.currentUserId, + }); + + final Message quotedMessage; + final VoidCallback onRemovePressed; + final String? currentUserId; + + @override + Widget build(BuildContext context) { + final isIncoming = currentUserId != quotedMessage.user?.id; + + return + // TODO: show image if available + // TODO: localize strings + MessageComposerReplyAttachment( + title: isIncoming ? 'Reply to ${quotedMessage.user?.name}' : 'You', + subtitle: quotedMessage.text ?? '', + onRemovePressed: onRemovePressed, + style: isIncoming ? .incoming : .outgoing, + ); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart index ef257df5b3..16d291893d 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart @@ -2,26 +2,18 @@ import 'package:flutter/widgets.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_factory.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +/// A widget that shows the input leading of the message composer. +/// Uses the factory to show custom components, but is empty by default. class StreamMessageComposerInputLeading extends StatelessWidget { + /// Creates a new instance of [StreamMessageComposerInputLeading]. + /// [props] contains the properties for the message composer component. const StreamMessageComposerInputLeading({super.key, required this.props}); + /// The properties for the message composer component. final MessageComposerComponentProps props; @override Widget build(BuildContext context) { - return StreamMessageComposerFactory.maybeOf(context)?.inputLeading?.call(context, props) ?? - DefaultStreamMessageComposerInputLeading(props: props); - } -} - -class DefaultStreamMessageComposerInputLeading extends StatelessWidget { - const DefaultStreamMessageComposerInputLeading({super.key, required this.props}); - - final MessageComposerComponentProps props; - - @override - Widget build(BuildContext context) { - // Doesn't show anything by default - return const SizedBox.shrink(); + return StreamMessageComposerFactory.maybeOf(context)?.inputLeading?.call(context, props) ?? const SizedBox.shrink(); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart index cf965352c7..6830e3cf07 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart @@ -1,21 +1,64 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_factory.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/stream_chat_message_composer.dart'; -import 'package:stream_core_flutter/stream_core_flutter.dart' as core; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; +/// A widget that shows the input trailing of the message composer. +/// Uses the factory to show custom components or the default implementation. +/// By default it shows the send button and the microphone button. class StreamMessageComposerInputTrailing extends StatelessWidget { + /// Creates a new instance of [StreamMessageComposerInputTrailing]. + /// [props] contains the properties for the message composer component. const StreamMessageComposerInputTrailing({super.key, required this.props}); + /// The properties for the message composer component. final MessageComposerComponentProps props; @override Widget build(BuildContext context) { return StreamMessageComposerFactory.maybeOf(context)?.inputTrailing?.call(context, props) ?? - core.StreamMessageComposerInputTrailing( - controller: props.controller, - onSendPressed: props.onSendPressed, - onMicrophonePressed: props.onMicrophonePressed, - ); + DefaultStreamMessageComposerInputTrailing(props: props); + } +} + +/// Default implementation of the input trailing of the message composer. +/// Shows the send button or the microphone button based on the state of the message composer. +/// It shows no button when the audio recording flow is locked or stopped. +class DefaultStreamMessageComposerInputTrailing extends StatelessWidget { + /// Creates a new instance of [DefaultStreamMessageComposerInputTrailing]. + /// [props] contains the properties for the message composer component. + const DefaultStreamMessageComposerInputTrailing({super.key, required this.props}); + + /// The properties for the message composer component. + final MessageComposerComponentProps props; + + StreamMessageInputController get _controller => props.controller; + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: _controller, + builder: (context, value, child) { + final hasText = _controller.text.isNotEmpty; + var buttonState = StreamMessageComposerInputTrailingState.microphone; + if (props.isAudioRecordingFlowActive) { + buttonState = StreamMessageComposerInputTrailingState.voiceRecordingActive; + } + + if (hasText || _controller.attachments.isNotEmpty) { + buttonState = StreamMessageComposerInputTrailingState.send; + } + + return props.isAudioRecordingFlowLocked || props.isAudioRecordingFlowStopped + ? const SizedBox.shrink() + : StreamCoreMessageComposerInputTrailing( + controller: _controller.textFieldController, + onSendPressed: props.onSendPressed, + voiceRecordingCallback: props.voiceRecordingCallback, + buttonState: buttonState, + ); + }, + ); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart index ce441479bc..67408526f8 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart @@ -3,37 +3,56 @@ import 'package:stream_chat_flutter/src/components/message_composer/message_comp import 'package:stream_chat_flutter/src/components/message_composer/stream_chat_message_composer.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart'; +/// A widget that shows the leading of the message composer. +/// Uses the factory to show custom components or the default implementation. +/// By default this contains a button to add attachments. class StreamMessageComposerLeading extends StatelessWidget { + /// Creates a new instance of [StreamMessageComposerLeading]. + /// [props] contains the properties for the message composer component. const StreamMessageComposerLeading({super.key, required this.props}); + + /// The properties for the message composer component. final MessageComposerComponentProps props; @override Widget build(BuildContext context) { return StreamMessageComposerFactory.maybeOf(context)?.leading?.call(context, props) ?? - DefaultStreamMessageComposerLeading(props: props); + _DefaultStreamMessageComposerLeading(props: props); } } -class DefaultStreamMessageComposerLeading extends StatelessWidget { - const DefaultStreamMessageComposerLeading({super.key, required this.props}); +class _DefaultStreamMessageComposerLeading extends StatelessWidget { + const _DefaultStreamMessageComposerLeading({required this.props}); final MessageComposerComponentProps props; @override Widget build(BuildContext context) { - return Row( - children: [ - StreamButton.icon( - icon: context.streamIcons.plusLarge, - style: StreamButtonStyle.secondary, - type: StreamButtonType.outline, - size: StreamButtonSize.large, - onTap: () { - // TODO: Implement attachment picker - }, + return AnimatedOpacity( + opacity: props.isAudioRecordingFlowActive ? 0.0 : 1.0, + duration: props.isAudioRecordingFlowActive ? Duration.zero : const Duration(milliseconds: 200), + curve: Curves.easeInQuint, + child: AnimatedSize( + duration: const Duration(milliseconds: 200), + alignment: Alignment.bottomCenter, + child: Row( + children: [ + if (!props.isAudioRecordingFlowActive) ...[ + StreamButton.icon( + icon: context.streamIcons.plusLarge, + style: StreamButtonStyle.secondary, + type: StreamButtonType.outline, + size: StreamButtonSize.large, + isFloating: props.isFloating, + onTap: () { + props.onAttachmentButtonPressed?.call(); + }, + ), + SizedBox(width: context.streamSpacing.xs), + ], + ], ), - SizedBox(width: context.streamSpacing.xs), - ], + ), ); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_recording_locked.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_recording_locked.dart new file mode 100644 index 0000000000..194c668411 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_recording_locked.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// Widget to display the recording locked state. +/// This widget can be used inside of the [StreamBaseMessageComposer] instead of the default `inputBody`. +class MessageComposerRecordingLocked extends StatelessWidget { + /// Creates a new instance of [MessageComposerRecordingLocked]. + /// [audioRecorderController] is the controller for the audio recorder. + /// [feedback] is the feedback for the audio recorder. + /// [messageInputController] is the controller for the message input. + /// [sendMessageCallback] is the callback for when the message is sent automatically. + const MessageComposerRecordingLocked({ + super.key, + required this.audioRecorderController, + required this.feedback, + required this.messageInputController, + required this.sendMessageCallback, + }); + + /// The controller for the audio recorder. + final StreamAudioRecorderController audioRecorderController; + + /// The feedback for the audio recorder. + final AudioRecorderFeedback feedback; + + /// The controller for the message input. + final StreamMessageInputController messageInputController; + + /// The callback for when the message is sent automatically. + /// This callback should be null when the message is not supposed to be sent automatically. + final VoidCallback? sendMessageCallback; + + @override + Widget build(BuildContext context) { + final icons = context.streamIcons; + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + height: 48, + width: 48, + alignment: Alignment.center, + child: Icon( + icons.microphone, + color: context.streamColorScheme.accentError, + size: 20, + ), + ), + ValueListenableBuilder( + valueListenable: audioRecorderController, + builder: (context, state, child) { + final duration = switch (state) { + RecordStateRecording() => state.duration, + RecordStateStopped() => state.audioRecording.duration, + RecordStateIdle() => Duration.zero, + }; + + return Text( + duration.toMinutesAndSeconds(), + style: textTheme.captionEmphasis.copyWith( + color: colorScheme.textPrimary, + fontFeatures: [const FontFeature.tabularFigures()], + ), + ); + }, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + StreamButton.icon( + key: const ValueKey('cancel-record-button'), + style: StreamButtonStyle.secondary, + type: StreamButtonType.outline, + size: StreamButtonSize.small, + icon: icons.trashBin, + onTap: audioRecorderController.cancelRecord, + ), + if (audioRecorderController.value is RecordStateRecording) + StreamButton.icon( + key: const ValueKey('stop-record-button'), + style: StreamButtonStyle.destructive, + type: StreamButtonType.outline, + size: StreamButtonSize.small, + icon: icons.stop, + onTap: audioRecorderController.stopRecord, + ), + StreamButton.icon( + key: const ValueKey('finish-record-button'), + style: StreamButtonStyle.primary, + type: StreamButtonType.solid, + size: StreamButtonSize.small, + icon: icons.checkmark2Small, + onTap: () async { + await feedback.onRecordFinish(context); + final audio = await audioRecorderController.finishRecord(); + if (audio != null) { + messageInputController.addAttachment(audio); + } + + // Once the recording is finished, cancel the recorder. + audioRecorderController.cancelRecord(discardTrack: false); + + // Send the message if the user has enabled the option to + // send the voice recording automatically. + sendMessageCallback?.call(); + }, + ), + ], + ), + ], + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_recording_ongoing.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_recording_ongoing.dart new file mode 100644 index 0000000000..38bdfcb26d --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_recording_ongoing.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Widget to display the recording ongoing state. +/// This widget can be used inside of the [StreamBaseMessageComposer] instead of the default `inputBody`. +/// It shows a hint to slide to cancel the recording. +class StreamMessageComposerRecordingOngoing extends StatelessWidget { + /// Creates a new instance of [StreamMessageComposerRecordingOngoing]. + /// [audioRecorderController] is the controller for the audio recorder. + const StreamMessageComposerRecordingOngoing({super.key, required this.audioRecorderController}); + + /// The controller for the audio recorder. + final StreamAudioRecorderController audioRecorderController; + + @override + Widget build(BuildContext context) { + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; + final icons = context.streamIcons; + + return ConstrainedBox( + constraints: const BoxConstraints( + minHeight: 48, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + height: 48, + width: 48, + alignment: Alignment.center, + child: Icon( + icons.microphone, + color: context.streamColorScheme.accentError, + size: 20, + ), + ), + ValueListenableBuilder( + valueListenable: audioRecorderController, + builder: (context, state, child) { + final duration = state is RecordStateRecording ? state.duration : Duration.zero; + return Text( + duration.toMinutesAndSeconds(), + style: textTheme.captionEmphasis.copyWith( + color: colorScheme.textPrimary, + fontFeatures: [const FontFeature.tabularFigures()], + ), + ); + }, + ), + const SizedBox(width: 23), + _GradientText( + 'Slide to cancel', + style: context.streamTextTheme.bodyDefault, + gradient: LinearGradient( + colors: [colorScheme.textPrimary, colorScheme.textTertiary], + ), + ), + SizedBox(width: context.streamSpacing.xxs), + Icon(icons.chevronLeft, color: colorScheme.textTertiary, size: 20), + ], + ), + ); + } +} + +class _GradientText extends StatelessWidget { + const _GradientText( + this.text, { + required this.gradient, + this.style, + }); + + final String text; + final TextStyle? style; + final Gradient gradient; + + @override + Widget build(BuildContext context) { + return ShaderMask( + blendMode: BlendMode.srcIn, + shaderCallback: (bounds) => gradient.createShader( + Rect.fromLTWH(0, 0, bounds.width, bounds.height), + ), + child: Text(text, style: style), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart index af02ea317d..0d60105250 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart @@ -2,9 +2,15 @@ import 'package:flutter/widgets.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_factory.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +/// A widget that shows the trailing of the message composer. +/// Uses the factory to show custom components or the default implementation. +/// By default this area is empty. class StreamMessageComposerTrailing extends StatelessWidget { + /// Creates a new instance of [StreamMessageComposerTrailing]. + /// [props] contains the properties for the message composer component. const StreamMessageComposerTrailing({super.key, required this.props}); + /// The properties for the message composer component. final MessageComposerComponentProps props; @override diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart index f87bace493..b2390058ea 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart @@ -1,39 +1,73 @@ import 'package:flutter/material.dart'; +import 'package:flutter_portal/flutter_portal.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_factory.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_input_header.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_input_leading.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_input_trailing.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_leading.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_recording_locked.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_recording_ongoing.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_trailing.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart' as core; +/// A widget that shows the message composer. +/// Uses the factory to show custom components or the default implementation. class StreamChatMessageComposer extends StatefulWidget { + /// Creates a new instance of [StreamChatMessageComposer]. + /// [controller] is the controller for the message composer. + /// [onSendPressed] is the callback for when the send button is pressed. + /// [onMicrophonePressed] is the callback for when the microphone button is pressed. + /// [onAttachmentButtonPressed] is the callback for when the attachment button is pressed. + /// [focusNode] is the focus node for the message composer. + /// [currentUserId] is the current user id. + /// [placeholder] is the placeholder text of the message composer. StreamChatMessageComposer({ super.key, this.controller, required VoidCallback onSendPressed, - VoidCallback? onMicrophonePressed, + VoidCallback? onAttachmentButtonPressed, FocusNode? focusNode, + String? currentUserId, String placeholder = '', + StreamAudioRecorderController? audioRecorderController, + this.sendVoiceRecordingAutomatically = false, }) : props = MessageComposerProps( isFloating: false, message: null, onSendPressed: onSendPressed, - onMicrophonePressed: onMicrophonePressed, + onAttachmentButtonPressed: onAttachmentButtonPressed, focusNode: focusNode, + currentUserId: currentUserId, placeholder: placeholder, + audioRecorderController: audioRecorderController, ); + /// The controller for the message composer. final StreamMessageInputController? controller; + + /// The properties for the message composer. final MessageComposerProps props; + /// Whether the voice recording should be sent automatically. + /// If enabled, the voice recording will be sent automatically when the recording is finished. + /// If disabled, the voice recording will be added as an attachment to the message + /// and the user will need to send the message manually. + final bool sendVoiceRecordingAutomatically; + @override State createState() => _StreamChatMessageComposerState(); } class _StreamChatMessageComposerState extends State { - late MessageTextFieldController _controller; + late StreamMessageInputController _controller; + final AudioRecorderFeedback feedback = const AudioRecorderFeedback(); + + /// The threshold to lock the recording. + final double lockRecordThreshold = 50; + + /// The threshold to cancel the recording. + final double cancelRecordThreshold = 75; @override void initState() { @@ -57,7 +91,7 @@ class _StreamChatMessageComposerState extends State { } void _initController() { - _controller = widget.controller?.textFieldController ?? MessageTextFieldController(); + _controller = widget.controller ?? StreamMessageInputController(); } void _disposeController(StreamChatMessageComposer widget) { @@ -68,63 +102,254 @@ class _StreamChatMessageComposerState extends State { @override Widget build(BuildContext context) { - final componentProps = MessageComposerComponentProps( - controller: _controller, - isFloating: widget.props.isFloating, - message: widget.props.message, - onSendPressed: widget.props.onSendPressed, - onMicrophonePressed: widget.props.onMicrophonePressed, - ); - - return StreamMessageComposerFactory.maybeOf(context)?.messageComposer?.call(context, widget.props) ?? - core.StreamBaseMessageComposer( - placeholder: widget.props.placeholder, + final voiceRecordingCallback = createVoiceRecordingCallback(); + + MessageComposerComponentProps componentProps({required AudioRecorderState audioRecorderState}) => + MessageComposerComponentProps( controller: _controller, isFloating: widget.props.isFloating, + message: widget.props.message, + currentUserId: widget.props.currentUserId, + onSendPressed: widget.props.onSendPressed, + voiceRecordingCallback: voiceRecordingCallback, + onAttachmentButtonPressed: widget.props.onAttachmentButtonPressed, + audioRecorderState: audioRecorderState, focusNode: widget.props.focusNode, - composerLeading: StreamMessageComposerLeading(props: componentProps), - composerTrailing: StreamMessageComposerTrailing(props: componentProps), - inputHeader: StreamMessageComposerInputHeader(props: componentProps), - inputTrailing: StreamMessageComposerInputTrailing(props: componentProps), - inputLeading: StreamMessageComposerInputLeading(props: componentProps), ); + + Widget createComposer({ + Widget? body, + AudioRecorderState audioRecorderState = const RecordStateIdle(), + }) => + StreamMessageComposerFactory.maybeOf(context)?.messageComposer?.call(context, widget.props) ?? + core.StreamCoreMessageComposer( + placeholder: widget.props.placeholder, + controller: _controller.textFieldController, + isFloating: widget.props.isFloating, + focusNode: widget.props.focusNode, + composerLeading: StreamMessageComposerLeading(props: componentProps(audioRecorderState: audioRecorderState)), + composerTrailing: StreamMessageComposerTrailing( + props: componentProps(audioRecorderState: audioRecorderState), + ), + inputHeader: StreamMessageComposerInputHeader(props: componentProps(audioRecorderState: audioRecorderState)), + inputTrailing: StreamMessageComposerInputTrailing( + props: componentProps(audioRecorderState: audioRecorderState), + ), + inputLeading: StreamMessageComposerInputLeading( + props: componentProps(audioRecorderState: audioRecorderState), + ), + inputBody: body, + ); + + if (widget.props.audioRecorderController case final controller?) { + return ValueListenableBuilder( + valueListenable: controller, + builder: (context, state, _) { + final body = switch (state) { + RecordStateRecordingLocked() || RecordStateStopped() => MessageComposerRecordingLocked( + audioRecorderController: controller, + feedback: feedback, + messageInputController: _controller, + sendMessageCallback: widget.sendVoiceRecordingAutomatically ? widget.props.onSendPressed : null, + ), + RecordStateRecording() => StreamMessageComposerRecordingOngoing(audioRecorderController: controller), + _ => null, + }; + + final streamSpacing = context.streamSpacing; + + return PortalTarget( + anchor: Aligned( + offset: Offset(-streamSpacing.md, -streamSpacing.md), + target: Alignment.topRight, + follower: Alignment.bottomRight, + ), + visible: state is RecordStateRecording, + portalFollower: SwipeToLockButton(isLocked: state is RecordStateRecordingLocked), + child: createComposer(body: body, audioRecorderState: state), + ); + }, + ); + } + + return createComposer(); + } + + core.VoiceRecordingCallback? createVoiceRecordingCallback() { + if (widget.props.audioRecorderController case final controller?) { + return core.VoiceRecordingCallback( + onLongPressStart: () async { + // Return if the recording is already started. + if (controller.isRecording) return; + + await feedback.onRecordStart(context); + return controller.startRecord(); + }, + onLongPressEnd: (_) async { + // Return if the recording not yet started or already locked. + if (!controller.isRecording || controller.isLocked) return; + + await feedback.onRecordFinish(context); + final audio = await controller.finishRecord(); + if (audio != null) { + _controller.addAttachment(audio); + } + + // Once the recording is finished, cancel the recorder. + controller.cancelRecord(discardTrack: false); + + // Send the message if the user has enabled the option to + // send the voice recording automatically. + if (widget.sendVoiceRecordingAutomatically) { + return widget.props.onSendPressed.call(); + } + }, + onLongPressCancel: () async { + // Return if the recording is already started. + if (controller.isRecording) return; + + // Notify the parent that the recorder is canceled before it starts. + await feedback.onRecordStartCancel(context); + // Show a message to the user to hold to record. + controller.showInfo( + context.translations.holdToRecordLabel, + ); + }, + onLongPressMoveUpdate: (details) async { + // Return if the recording not yet started or already locked. + if (!controller.isRecording || controller.isLocked) return; + final dragOffset = details.offsetFromOrigin; + + // Lock recording if the drag offset is greater than the threshold. + if (dragOffset.dy <= -lockRecordThreshold) { + await feedback.onRecordLock(context); + return controller.lockRecord(); + } + // Cancel recording if the drag offset is greater than the threshold. + if (dragOffset.dx <= -cancelRecordThreshold) { + await feedback.onRecordCancel(context); + return controller.cancelRecord(); + } + + // Update the drag offset. + return controller.dragRecord(dragOffset); + }, + ); + } + return null; } } /// Properties to build the main message composer component class MessageComposerProps { + /// Creates a new instance of [MessageComposerProps]. + /// [isFloating] is whether the message composer is floating. + /// [message] is the message for the message composer. + /// [placeholder] is the placeholder text of the message composer. + /// [onSendPressed] is the callback for when the send button is pressed. + /// [onMicrophonePressed] is the callback for when the microphone button is pressed. + /// [onAttachmentButtonPressed] is the callback for when the attachment button is pressed. + /// [focusNode] is the focus node for the message composer. + /// [currentUserId] is the current user id. const MessageComposerProps({ this.isFloating = false, this.message, this.placeholder = '', required this.onSendPressed, - this.onMicrophonePressed, + this.onAttachmentButtonPressed, this.focusNode, + this.currentUserId, + this.audioRecorderController, }); + /// Whether the message composer is floating. final bool isFloating; + + /// The message for the message composer. final Message? message; + + /// The placeholder text of the message composer. final String placeholder; + + /// The callback for when the send button is pressed. final VoidCallback onSendPressed; - final VoidCallback? onMicrophonePressed; + + /// The callback for when the attachment button is pressed. + final VoidCallback? onAttachmentButtonPressed; + + /// The focus node for the message composer. final FocusNode? focusNode; + + /// The current user id. + final String? currentUserId; + + /// The audio recorder controller. + final StreamAudioRecorderController? audioRecorderController; } /// Properties to build any of the sub-components. /// These properties are all the same, so features such as 'add attachment', /// can be added to any of the sub-components. class MessageComposerComponentProps { + /// Creates a new instance of [MessageComposerComponentProps]. + /// [controller] is the controller for the message composer component. + /// [isFloating] is whether the message composer is floating. + /// [message] is the message for the message composer component. + /// [onSendPressed] is the callback for when the send button is pressed. + /// [onMicrophonePressed] is the callback for when the microphone button is pressed. + /// [onAttachmentButtonPressed] is the callback for when the attachment button is pressed. + /// [focusNode] is the focus node for the message composer component. + /// [currentUserId] is the current user id. const MessageComposerComponentProps({ required this.controller, this.isFloating = false, this.message, required this.onSendPressed, - this.onMicrophonePressed, + this.voiceRecordingCallback, + this.onAttachmentButtonPressed, + this.focusNode, + this.currentUserId, + this.audioRecorderState = const RecordStateIdle(), }); - final MessageTextFieldController controller; + /// The controller for the message composer component. + final StreamMessageInputController controller; + + /// Whether the message composer is floating. final bool isFloating; + + /// The message for the message composer component. final Message? message; + + /// The callback for when the send button is pressed. final VoidCallback onSendPressed; - final VoidCallback? onMicrophonePressed; + + /// The callback for when the microphone button is pressed. + final core.VoiceRecordingCallback? voiceRecordingCallback; + + /// The callback for when the attachment button is pressed. + final VoidCallback? onAttachmentButtonPressed; + + /// The focus node for the message composer component. + final FocusNode? focusNode; + + /// The current user id. + final String? currentUserId; + + /// Whether the audio recording flow is active. + final AudioRecorderState audioRecorderState; + + /// Whether the audio recording flow is active. + bool get isAudioRecordingFlowActive => audioRecorderState is RecordStateRecording || isAudioRecordingFlowStopped; + + /// Whether the audio recording flow is locked. + bool get isAudioRecordingFlowLocked => audioRecorderState is RecordStateRecordingLocked; + + /// Whether the audio recording flow is stopped. + bool get isAudioRecordingFlowStopped => audioRecorderState is RecordStateStopped; +} + +extension on StreamAudioRecorderController { + bool get isRecording => value is RecordStateRecording; + bool get isLocked => isRecording && value is! RecordStateRecordingHold; } diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart index 0444963628..feb7b57fb5 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart @@ -449,13 +449,6 @@ Widget tabbedAttachmentPickerBuilder({ AttachmentPickerType.images, AttachmentPickerType.videos, ], - isEnabled: (value) { - // Enable if nothing has been selected yet. - if (value.isEmpty) return true; - - // Otherwise, enable only if there is at least a image or a video. - return value.attachments.any((it) => it.isImage || it.isVideo); - }, optionViewBuilder: (context, controller) { final attachment = controller.value.attachments; final selectedIds = attachment.map((it) => it.id); @@ -480,13 +473,6 @@ Widget tabbedAttachmentPickerBuilder({ key: 'file-picker', icon: const StreamSvgIcon(icon: StreamSvgIcons.files), supportedTypes: [AttachmentPickerType.files], - isEnabled: (value) { - // Enable if nothing has been selected yet. - if (value.isEmpty) return true; - - // Otherwise, enable only if there is at least a file. - return value.attachments.any((it) => it.isFile); - }, optionViewBuilder: (context, controller) => StreamFilePicker( onFilePicked: (file) async { final result = await _handleSingePick(controller, file); @@ -498,13 +484,6 @@ Widget tabbedAttachmentPickerBuilder({ key: 'image-picker', icon: const StreamSvgIcon(icon: StreamSvgIcons.camera), supportedTypes: [AttachmentPickerType.images], - isEnabled: (value) { - // Enable if nothing has been selected yet. - if (value.isEmpty) return true; - - // Otherwise, enable only if there is at least a image. - return value.attachments.any((it) => it.isImage); - }, optionViewBuilder: (context, controller) => StreamImagePicker( onImagePicked: (image) async { final result = await _handleSingePick(controller, image); @@ -516,13 +495,6 @@ Widget tabbedAttachmentPickerBuilder({ key: 'video-picker', icon: const StreamSvgIcon(icon: StreamSvgIcons.record), supportedTypes: [AttachmentPickerType.videos], - isEnabled: (value) { - // Enable if nothing has been selected yet. - if (value.isEmpty) return true; - - // Otherwise, enable only if there is at least a video. - return value.attachments.any((it) => it.isVideo); - }, optionViewBuilder: (context, controller) => StreamVideoPicker( onVideoPicked: (video) async { final result = await _handleSingePick(controller, video); @@ -534,13 +506,6 @@ Widget tabbedAttachmentPickerBuilder({ key: 'poll-creator', icon: const StreamSvgIcon(icon: StreamSvgIcons.polls), supportedTypes: [AttachmentPickerType.poll], - isEnabled: (value) { - // Enable if nothing has been selected yet. - if (value.isEmpty) return true; - - // Otherwise, enable only if there is a poll. - return value.poll != null; - }, optionViewBuilder: (context, controller) { final initialPoll = controller.value.poll; return StreamPollCreator( diff --git a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart index 66ddfe0394..e77b8e811b 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart @@ -13,6 +13,7 @@ import 'package:stream_chat_flutter/src/misc/audio_waveform.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template audioRecorderBuilder} /// A builder function for constructing the audio recorder UI. @@ -643,29 +644,31 @@ class SwipeToLockButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); + final streamIcons = context.streamIcons; + final colorScheme = context.streamColorScheme; + return Container( - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.all(10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(24), - color: theme.colorTheme.inputBg, + color: colorScheme.backgroundElevation1, + border: Border.all(color: colorScheme.borderDefault), + boxShadow: context.streamBoxShadow.elevation1, ), child: Column( spacing: 8, mainAxisSize: MainAxisSize.min, children: [ - StreamSvgIcon( - icon: StreamSvgIcons.lock, - size: kDefaultMessageInputIconSize, - color: switch (isLocked) { - true => theme.colorTheme.accentPrimary, - false => theme.colorTheme.textLowEmphasis, - }, + Icon( + isLocked ? streamIcons.lock : streamIcons.unlocked, + size: 20, + color: colorScheme.textPrimary, ), if (!isLocked) ...[ - StreamSvgIcon( - icon: StreamSvgIcons.up, - color: theme.colorTheme.textLowEmphasis, + Icon( + streamIcons.chevronTop, + size: 20, + color: colorScheme.textPrimary, ), ], ], diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart index 946f1c0ec5..27caf99f8b 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart @@ -1,3 +1,6 @@ +// ignore_for_file: unused_element +// TODO: remove unused elements + import 'dart:async'; import 'dart:math'; @@ -758,13 +761,18 @@ class StreamMessageInputState extends State with Restoration StreamMessageEditingController controller, FocusNode focusNode, ) { + final currentUserId = StreamChat.of(context).currentUser?.id; + return StreamMessageValueListenableBuilder( valueListenable: controller, builder: (context, value, _) => StreamChatMessageComposer( controller: controller, + currentUserId: currentUserId, + onAttachmentButtonPressed: _onAttachmentButtonPressed, placeholder: _getHint(context) ?? '', focusNode: focusNode, onSendPressed: sendMessage, + audioRecorderController: _audioRecorderController, ), ); } @@ -1387,12 +1395,10 @@ class StreamMessageInputState extends State with Restoration child: StreamMessageInputAttachmentList( attachments: nonOGAttachments, onRemovePressed: _onAttachmentRemovePressed, - fileAttachmentListBuilder: widget.fileAttachmentListBuilder, - mediaAttachmentListBuilder: widget.mediaAttachmentListBuilder, + attachmentListBuilder: widget.mediaAttachmentListBuilder, voiceRecordingAttachmentBuilder: widget.voiceRecordingAttachmentBuilder, fileAttachmentBuilder: widget.fileAttachmentBuilder, mediaAttachmentBuilder: widget.mediaAttachmentBuilder, - voiceRecordingAttachmentListBuilder: widget.voiceRecordingAttachmentListBuilder, ), ); } diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart index ede7608ab7..a8949bed4b 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart @@ -1,14 +1,14 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/attachment/file_attachment.dart'; import 'package:stream_chat_flutter/src/attachment/thumbnail/media_attachment_thumbnail.dart'; import 'package:stream_chat_flutter/src/attachment/voice_recording_attachment.dart'; import 'package:stream_chat_flutter/src/audio/audio_playlist_controller.dart'; import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; -import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; +import 'package:stream_chat_flutter/src/indicators/upload_progress_indicator.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/utils.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// WidgetBuilder used to build the message input attachment list. /// @@ -39,7 +39,7 @@ typedef AttachmentItemBuilder = /// separately. /// /// You can customize the list of file attachments and media attachments using -/// [fileAttachmentListBuilder] and [mediaAttachmentListBuilder] respectively. +/// [fileAttachmentListBuilder] and [attachmentListBuilder] respectively. /// /// You can also customize the attachment item using [fileAttachmentBuilder] and /// [mediaAttachmentBuilder] respectively. @@ -47,7 +47,7 @@ typedef AttachmentItemBuilder = /// You can override the default action of removing an attachment by providing /// [onRemovePressed]. /// {@endtemplate} -class StreamMessageInputAttachmentList extends StatelessWidget { +class StreamMessageInputAttachmentList extends StatefulWidget { /// {@macro stream_message_input_attachment_list} const StreamMessageInputAttachmentList({ super.key, @@ -56,9 +56,7 @@ class StreamMessageInputAttachmentList extends StatelessWidget { this.fileAttachmentBuilder, this.mediaAttachmentBuilder, this.voiceRecordingAttachmentBuilder, - this.fileAttachmentListBuilder, - this.mediaAttachmentListBuilder, - this.voiceRecordingAttachmentListBuilder, + this.attachmentListBuilder, }); /// List of attachments to display thumbnails for. @@ -75,87 +73,80 @@ class StreamMessageInputAttachmentList extends StatelessWidget { /// Builder used to build the voice recording attachment item. final AttachmentItemBuilder? voiceRecordingAttachmentBuilder; - /// Builder used to build the file attachment list. - final AttachmentListBuilder? fileAttachmentListBuilder; - /// Builder used to build the media attachment list. - final AttachmentListBuilder? mediaAttachmentListBuilder; - - /// Builder used to build the voice recording attachment list. - final AttachmentListBuilder? voiceRecordingAttachmentListBuilder; + final AttachmentListBuilder? attachmentListBuilder; /// Callback called when the remove button is pressed. final ValueSetter? onRemovePressed; + List get _audioAttachments => + attachments.where((it) => it.type == AttachmentType.audio || it.type == AttachmentType.voiceRecording).toList(); + @override - Widget build(BuildContext context) { - final groupedAttachments = attachments.groupListsBy((it) => it.type); - final (:files, :media, :voices) = ( - files: [...?groupedAttachments[AttachmentType.file]], - voices: [...?groupedAttachments[AttachmentType.voiceRecording]], - media: [ - ...?groupedAttachments[AttachmentType.image], - ...?groupedAttachments[AttachmentType.video], - ...?groupedAttachments[AttachmentType.giphy], - ...?groupedAttachments[AttachmentType.audio], - ], - ); + State createState() => _StreamMessageInputAttachmentListState(); +} + +class _StreamMessageInputAttachmentListState extends State { + late List _audioAttachments = widget._audioAttachments; + + late final _controller = StreamAudioPlaylistController(_audioAttachments.toPlaylist()); + late final _scrollController = ScrollController(); - // If there are no attachments, return an empty widget. - if (files.isEmpty && media.isEmpty && voices.isEmpty) { - return const Empty(); + @override + void initState() { + super.initState(); + _controller.initialize(); + } + + @override + void didUpdateWidget( + covariant StreamMessageInputAttachmentList oldWidget, + ) { + super.didUpdateWidget(oldWidget); + final equals = const ListEquality().equals; + final newAudioAttachments = widget._audioAttachments; + if (!equals(newAudioAttachments, _audioAttachments)) { + // If the attachments have changed, update the playlist. + _audioAttachments = newAudioAttachments; + _controller.updatePlaylist(newAudioAttachments.toPlaylist()); + } + if (oldWidget.attachments.length < widget.attachments.length) { + // If an attachment has been added, scroll to the end. + WidgetsBinding.instance.addPostFrameCallback( + (_) { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + ); + }, + ); } + } - return SingleChildScrollView( - padding: const EdgeInsets.only(top: 6), - child: Column( - mainAxisSize: MainAxisSize.min, - children: - [ - if (media.isNotEmpty) - Flexible( - child: switch (mediaAttachmentListBuilder) { - final builder? => builder(context, media, onRemovePressed), - _ => MessageInputMediaAttachments( - attachments: media, - attachmentBuilder: mediaAttachmentBuilder, - onRemovePressed: onRemovePressed, - ), - }, - ), - if (voices.isNotEmpty) - Flexible( - child: switch (voiceRecordingAttachmentListBuilder) { - final builder? => builder(context, voices, onRemovePressed), - _ => MessageInputVoiceRecordingAttachments( - attachments: voices, - attachmentBuilder: voiceRecordingAttachmentBuilder, - onRemovePressed: onRemovePressed, - ), - }, - ), - if (files.isNotEmpty) - Flexible( - child: switch (fileAttachmentListBuilder) { - final builder? => builder(context, files, onRemovePressed), - _ => MessageInputFileAttachments( - attachments: files, - attachmentBuilder: fileAttachmentBuilder, - onRemovePressed: onRemovePressed, - ), - }, - ), - ].insertBetween( - Divider( - height: 16, - indent: 16, - endIndent: 16, - thickness: 1, - color: StreamChatTheme.of(context).colorTheme.disabled, - ), - ), + @override + void dispose() { + _controller.dispose(); + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final attachmentsList = widget.attachments.toList(); + + return switch (widget.attachmentListBuilder) { + final builder? => builder(context, attachmentsList, widget.onRemovePressed), + _ => MessageInputMediaAttachments( + scrollController: _scrollController, + attachments: attachmentsList, + attachmentBuilder: widget.mediaAttachmentBuilder, + audioPlaylistController: _controller, + voiceRecordingAttachmentBuilder: widget.voiceRecordingAttachmentBuilder, + fileAttachmentBuilder: widget.fileAttachmentBuilder, + onRemovePressed: widget.onRemovePressed, ), - ); + }; } } @@ -195,22 +186,11 @@ class MessageInputFileAttachments extends StatelessWidget { return builder(context, attachment, onRemovePressed); } - // Otherwise, use the default builder. - return StreamFileAttachment( - message: Message(), // Dummy message - file: attachment, - constraints: BoxConstraints.loose( - Size( - MediaQuery.of(context).size.width * 0.65, - 56, - ), - ), - trailing: Padding( - padding: const EdgeInsets.all(8), - child: RemoveAttachmentButton( - onPressed: onRemovePressed != null ? () => onRemovePressed!(attachment) : null, - ), - ), + return MessageComposerFileAttachment( + fileTypeIcon: StreamFileTypeIcon.fromMimeType(mimeType: attachment.file?.mediaType?.mimeType ?? ''), + title: attachment.title ?? context.translations.fileText, + subtitle: _FileAttachmentSubtitle(attachment: attachment), + onRemovePressed: onRemovePressed != null ? () => onRemovePressed!(attachment) : null, ); }, ) @@ -219,123 +199,107 @@ class MessageInputFileAttachments extends StatelessWidget { } } +class _FileAttachmentSubtitle extends StatelessWidget { + const _FileAttachmentSubtitle({ + required this.attachment, + }); + + final Attachment attachment; + + @override + Widget build(BuildContext context) { + final theme = StreamChatTheme.of(context); + final size = attachment.file?.size ?? attachment.extraData['file_size']; + final textStyle = theme.textTheme.footnote.copyWith( + color: theme.colorTheme.textLowEmphasis, + ); + return attachment.uploadState.when( + preparing: () => Text(fileSize(size), style: textStyle), + inProgress: (sent, total) => StreamUploadProgressIndicator( + uploaded: sent, + total: total, + showBackground: false, + textStyle: textStyle, + progressIndicatorColor: theme.colorTheme.accentPrimary, + ), + success: () => Text(fileSize(size), style: textStyle), + failed: (_) => Text( + context.translations.uploadErrorLabel, + style: textStyle, + ), + ); + } +} + /// Widget used to display the list of voice recording type attachments added to /// the message input. -class MessageInputVoiceRecordingAttachments extends StatefulWidget { +class MessageInputVoiceRecordingAttachment extends StatelessWidget { /// Creates a new MessageInputVoiceRecordingAttachments widget. - const MessageInputVoiceRecordingAttachments({ + const MessageInputVoiceRecordingAttachment({ super.key, - required this.attachments, - this.attachmentBuilder, + required this.attachment, + required this.index, + required this.controller, this.onRemovePressed, }); - /// List of voice recording type attachments to display thumbnails for. - /// - /// Only attachments of type [AttachmentType.voiceRecording] are supported. - final List attachments; + /// Attachment to display. + final Attachment attachment; - /// Builder used to build the voice recording type attachment item. - final AttachmentItemBuilder? attachmentBuilder; + /// Index of the track in the playlist. + final int index; + + /// Controller to use to control the audio playback. + final StreamAudioPlaylistController controller; /// Callback called when the remove button is pressed. final ValueSetter? onRemovePressed; - @override - State createState() => _MessageInputVoiceRecordingAttachmentsState(); -} - -class _MessageInputVoiceRecordingAttachmentsState extends State { - late final _controller = StreamAudioPlaylistController( - widget.attachments.toPlaylist(), - ); - - @override - void initState() { - super.initState(); - _controller.initialize(); - } - - @override - void didUpdateWidget( - covariant MessageInputVoiceRecordingAttachments oldWidget, - ) { - super.didUpdateWidget(oldWidget); - final equals = const ListEquality().equals; - if (!equals(widget.attachments, oldWidget.attachments)) { - // If the attachments have changed, update the playlist. - _controller.updatePlaylist(widget.attachments.toPlaylist()); - } - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - @override Widget build(BuildContext context) { return ValueListenableBuilder( - valueListenable: _controller, + valueListenable: controller, builder: (context, state, _) { - return MediaQuery.removePadding( - context: context, - // Workaround for the bottom padding issue. - // Link: https://github.com/flutter/flutter/issues/156149 - removeTop: true, - removeBottom: true, - child: ListView.separated( - shrinkWrap: true, - padding: const EdgeInsets.symmetric(horizontal: 8), - physics: const NeverScrollableScrollPhysics(), - itemCount: state.tracks.length, - separatorBuilder: (_, __) => const SizedBox(height: 8), - itemBuilder: (context, index) { - final track = state.tracks[index]; - - return StreamVoiceRecordingAttachment( - track: track, - speed: state.speed, - trailingBuilder: (_, __, ___, ____) { - final attachment = widget.attachments[index]; - return RemoveAttachmentButton( - onPressed: switch (widget.onRemovePressed) { - final callback? => () => callback(attachment), - _ => null, - }, - ); - }, - onTrackPause: _controller.pause, - onChangeSpeed: _controller.setSpeed, - onTrackPlay: () async { - // Play the track directly if it is already loaded. - if (state.currentIndex == index) return _controller.play(); - // Otherwise, load the track first and then play it. - return _controller.skipToItem(index); - }, - // Only allow seeking if the current track is the one being - // interacted with. - onTrackSeekStart: (_) async { - if (state.currentIndex != index) return; - return _controller.pause(); - }, - onTrackSeekEnd: (_) async { - if (state.currentIndex != index) return; - return _controller.play(); - }, - onTrackSeekChanged: (progress) async { - if (state.currentIndex != index) return; - - final duration = track.duration.inMicroseconds; - final seekPosition = (duration * progress).toInt(); - final seekDuration = Duration(microseconds: seekPosition); - - return _controller.seek(seekDuration); - }, - ); - }, - ), + final track = state.tracks.where((it) => it.key == attachment).first; + + return StreamVoiceRecordingAttachment( + track: track, + speed: state.speed, + trailingBuilder: (_, __, ___, ____) { + return RemoveAttachmentButton( + onPressed: switch (onRemovePressed) { + final callback? => () => callback(attachment), + _ => null, + }, + ); + }, + onTrackPause: controller.pause, + onChangeSpeed: controller.setSpeed, + onTrackPlay: () async { + // Play the track directly if it is already loaded. + if (state.currentIndex == index) return controller.play(); + // Otherwise, load the track first and then play it. + return controller.skipToItem(index); + }, + // Only allow seeking if the current track is the one being + // interacted with. + onTrackSeekStart: (_) async { + if (state.currentIndex != index) return; + return controller.pause(); + }, + onTrackSeekEnd: (_) async { + if (state.currentIndex != index) return; + return controller.play(); + }, + onTrackSeekChanged: (progress) async { + if (state.currentIndex != index) return; + + final duration = track.duration.inMicroseconds; + final seekPosition = (duration * progress).toInt(); + final seekDuration = Duration(microseconds: seekPosition); + + return controller.seek(seekDuration); + }, ); }, ); @@ -349,8 +313,12 @@ class MessageInputMediaAttachments extends StatelessWidget { const MessageInputMediaAttachments({ super.key, required this.attachments, + this.audioPlaylistController, this.attachmentBuilder, + this.voiceRecordingAttachmentBuilder, + this.fileAttachmentBuilder, this.onRemovePressed, + this.scrollController, }); /// List of media type attachments to display thumbnails for. @@ -362,33 +330,89 @@ class MessageInputMediaAttachments extends StatelessWidget { /// Builder used to build the media type attachment item. final AttachmentItemBuilder? attachmentBuilder; + /// Builder used to build the voice recording type attachment item. + final AttachmentItemBuilder? voiceRecordingAttachmentBuilder; + + /// Builder used to build the file type attachment item. + final AttachmentItemBuilder? fileAttachmentBuilder; + /// Callback called when the remove button is pressed. final ValueSetter? onRemovePressed; + /// Controller to use to control the audio playback. + final StreamAudioPlaylistController? audioPlaylistController; + + /// Scroll controller to use to control the scroll position. + final ScrollController? scrollController; + @override Widget build(BuildContext context) { return SizedBox( - height: 104, + height: 80, child: ListView( + controller: scrollController, scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 8), + padding: EdgeInsets.symmetric(horizontal: context.streamSpacing.xs), cacheExtent: 104 * 10, // Cache 10 items ahead. - children: attachments - .map( - (attachment) { - // If a custom builder is provided, use it. - final builder = attachmentBuilder; - if (builder != null) { - return builder(context, attachment, onRemovePressed); - } - - return StreamMediaAttachmentBuilder( + children: attachments.map( + (attachment) { + if (attachment.type == AttachmentType.file) { + // If a custom builder is provided, use it. + if (fileAttachmentBuilder case final builder?) { + return builder(context, attachment, onRemovePressed); + } + + return SizedBox( + width: 268, + child: MessageComposerFileAttachment( + fileTypeIcon: StreamFileTypeIcon.fromMimeType(mimeType: attachment.file?.mediaType?.mimeType ?? ''), + title: attachment.title ?? context.translations.fileText, + subtitle: _FileAttachmentSubtitle(attachment: attachment), + onRemovePressed: onRemovePressed != null ? () => onRemovePressed!(attachment) : null, + ), + ); + } + + if (attachment.type == AttachmentType.audio || attachment.type == AttachmentType.voiceRecording) { + // If a custom builder is provided, use it. + if (voiceRecordingAttachmentBuilder case final builder?) { + return builder(context, attachment, onRemovePressed); + } + + if (audioPlaylistController == null) { + return const SizedBox.shrink(); + } + + final hasTrack = audioPlaylistController!.value.tracks.any((it) => it.key == attachment); + + if (!hasTrack) { + return const SizedBox.shrink(); + } + + final trackIndex = audioPlaylistController!.value.tracks.indexWhere((it) => it.key == attachment); + + return SizedBox( + width: 268, + child: MessageInputVoiceRecordingAttachment( attachment: attachment, + index: trackIndex, + controller: audioPlaylistController!, onRemovePressed: onRemovePressed, - ); - }, - ) - .insertBetween(const SizedBox(width: 8)), + ), + ); + } + + // If a custom builder is provided, use it. + if (attachmentBuilder case final builder?) { + return builder(context, attachment, onRemovePressed); + } + + return StreamMediaAttachmentBuilder( + attachment: attachment, + onRemovePressed: onRemovePressed, + ); + }, + ).toList(), ), ); } @@ -407,44 +431,20 @@ class StreamMediaAttachmentBuilder extends StatelessWidget { @override Widget build(BuildContext context) { - final colorTheme = StreamChatTheme.of(context).colorTheme; - final shape = RoundedRectangleBorder( - side: BorderSide( - color: colorTheme.borders, - strokeAlign: BorderSide.strokeAlignOutside, - ), - borderRadius: BorderRadius.circular(14), - ); + final mediaBadge = attachment.type == AttachmentType.video + ? const StreamMediaBadge(type: MediaBadgeType.video) + : null; return Container( key: Key(attachment.id), - clipBehavior: Clip.hardEdge, - decoration: ShapeDecoration(shape: shape), - child: AspectRatio( - aspectRatio: 1, - child: Stack( - alignment: Alignment.center, - children: [ - StreamMediaAttachmentThumbnail( - media: attachment, - width: double.infinity, - height: double.infinity, - fit: BoxFit.cover, - ), - if (attachment.type == AttachmentType.video) - const Positioned( - left: 8, - bottom: 8, - child: StreamSvgIcon(icon: StreamSvgIcons.videoCall), - ), - Positioned( - top: 8, - right: 8, - child: RemoveAttachmentButton( - onPressed: onRemovePressed != null ? () => onRemovePressed!(attachment) : null, - ), - ), - ], + child: MessageComposerMediaFileAttachment( + mediaBadge: mediaBadge, + onRemovePressed: onRemovePressed != null ? () => onRemovePressed!(attachment) : null, + child: StreamMediaAttachmentThumbnail( + media: attachment, + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, ), ), ); diff --git a/packages/stream_chat_flutter/lib/src/utils/extensions.dart b/packages/stream_chat_flutter/lib/src/utils/extensions.dart index 9f5ddd94ea..7d048adeea 100644 --- a/packages/stream_chat_flutter/lib/src/utils/extensions.dart +++ b/packages/stream_chat_flutter/lib/src/utils/extensions.dart @@ -670,6 +670,7 @@ extension AttachmentPlaylistExtension on Iterable { title: it.title, waveform: it.waveform, duration: it.duration, + key: it, ); }).nonNulls, ]; diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 867e947803..0b2afe5d99 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -51,7 +51,7 @@ dependencies: media_kit_video: ^2.0.0 meta: ^1.9.1 path_provider: ^2.1.3 - photo_manager: ^3.2.0 + photo_manager: ^3.8.3 photo_view: ^0.15.0 rate_limiter: ^1.0.0 record: ">=5.2.0 <7.0.0" @@ -62,7 +62,7 @@ dependencies: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 5b95b4821fd98d77eccefba9d9afa7fdc071370b + ref: da18aa04ad48d4d5bb429c9e90d9f0253c418fae path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 @@ -71,7 +71,7 @@ dependencies: video_player: ^2.8.7 dev_dependencies: - alchemist: ">=0.11.0 <0.14.0" + alchemist: ^0.13.0 connectivity_plus_platform_interface: ^2.0.0 faker_dart: ^0.2.1 flutter_test: diff --git a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_0.png b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_0.png index ee6d1dff5214644a526f83c3039ffa843e559924..0c6fc5447fd9248600871c9bb7e198184b082921 100644 GIT binary patch delta 1825 zcmV++2j2Lc6}uIXL4RdQL_t(|obBDokEHiq$MH{9b$9hXGu<=s%y=O-;2FpEfmjhF z;Q$vRFx)snqzJ>63kM?p0S*Xm93mmXjhv8>kb^`HY#~u1WJH9GjT0}iJzi#d@$^

edbVy227_?`3TRJy00V$qC9}~8wE>gx2nv5O zHR%DUZM5G@f9JjQduwTiyBY8Ar5WudN!p#cw5I0Mo}Np0b~W9F)9FmjUVH6!e=o0I zd^-!>xtu+IGD&h_^HR2YyE*r%hqKt7o7m^#Xle}xgYl$?pqTL>+iS0+zjGnu(Qdlk z>2$l(X}8*GcUtMRuk5&wnkLO?oMwOAq~9N=*B_)kwUqAsnY^_>pNCh^fF{_4EINQaXoqF{I4cS z{_&L;^5_5bH+gIQ{Upi5D-Y!NfB84_OOO9tlH@<%dNrRBEv^zQ4mKWSfA@d2Y`ph! z=4Pj|w7ihk9lq7paGYlG^I2I~&R=}%@A7Bg{Q6C|`|6i|JHPXVXS25RVbUbav-5ZL z5x74%=*|I}^fq74habG0Qzw_Rd>64wlK=T|HY*Fu`R?m4eQd0f_iTM? z6D)43xAFZ9d+S*_ar3iycWSDA9eVu?w5;CP~gc@W_GNK5_b|k|ghJUcBdXP?KPBz40LH@4b-Km3zBq^YJ$!c)tR(q^e|~>H51;w@oLIQ|PtT2= z%lYAtzLlRozM938pS`QKxPSOb$Tj`lwVYT!oNJz*Tp4G3`#bsGyGvP^JDIa5PA5q& zY`mB4-HSQDyq%|3lFhA)Sv+|tBT;~0kt7)nw=y2>W@_sAp{=<%-O4j7+ex;Q-Wy4h z<+&ttNzzI(m1ci`BctI~I@3#sHWx>ivxFo``rB`(+dcT&sKYipJCpv-JCh!RBSNr9 zk_>w5M>1I5nduC#UJ3z%MUtf1@1@;3dS8cjyOn0YH|a4rB97iSl!<6Y{j@uGe{=J_ z)^4{m8uceV21kToktAvM``4ZndX#iJ?KFbLRY&g|%4B~W!JmWhfBBY@Bx!fL88ywM z$GE|0)TG_%PI?TE2*Dz`8mwk?^lo}+n&!Iyw8Q^zf<=;~-I+}@K6-mtO*2l*9u`*# z7DGjk^I{`onY=&rEs@ju64(n#Gm# z+1l!5Jf8g7!tr>V?VWxWSI$p*6pj$V;v?h~H*#uqA?*p>s?#)0F0Sq5)T6(av^;;ZjgQ#F;(D#Atekl!YwMRMHo~Q~ z%UL=5IkDmfg2fFwa}Q?W#N*l6+&=WRH#WDjw0bU`SqIy>kzjFCv&TP^nYoi$+c>;E zt+n;bnVCPG?y)BhZB1$sEIvjnbF0r}c5eAl_Ov!Ow=y@kl)079UVWZB+{i(94$!2x z@nU~=Hh+-SmF0VWAiimuTw1%FrIp9hJ^r-6p?o62;+Bl|-pu;DFJ!hmo#mzZv|4w! z7c?G^b9rkw{r)f~&U`MNxrgrAtlYl@i(A+1W_Ro~(_MHlof(gc;m!#bP=-@7vk?h(0h90uO&0$P<_#o|ep*N{ P00000NkvXXu0mjfZZhmP literal 2716 zcmeHJ>r)bl7N>4?+kDh)JL~#Lt0lKaEguwD@m*@9Vq0x$KC-Ju%pg8cQ9+oisoS-D zfMzD>b}^YY5KB!Ai8WIe;*n5lB9i&Kt^x@HJ_0U%xPQdX?3r`UZ_YWtIdjhWbZ(u8 zMcM6f+5-Rp?4W1QTmS&Jd493oyR4bw(wYJ*>_CS@FYUGpZgO=$zk;qtU>u~&Oj=)lFs0wjP;#bkYyX&e=PA)^A5Fgp@*8LLE3S6B zaKiU+h|Uk2LWVI$Z-6NJec$*Um>*eic2ry9I!NVfB?_zdB&m8JjQmNSjS(tqZ;p}0 zwAfb7+E!9~7VpZEaPF2Tth;e1P#IKq(fH{B$fRU`h%vw_GT*%FLTgUTwRBKjkla>| z$ZcMj7h=QN5Q++d$BvIgk#LxSIwk9q97@d2GQnS%m6is#z0XKMi7{TMU&MQG*@kW? zF}{=A7+oC~Qt5CyqHLHG8|5Fc&O~x`CEsqM6H^n;?i0KYu#C5lFyq$Ttk)`9MXA;Boomy}4&vfn~#0Ej8#O)%5^yV7^|B=`6qr^jC~85|kMwI*cnmlSc~cVhxQB zHm+!r@$DXujVX>ucPJjQwBOpQsi`L901lmFs+WYtmJO>qs7Q{ELuUp}Hon_J-7X{o z39Bp2(z!wgkWe@iPXb~|GwID0CCQ?>Q-4ni5&n~jmDsFsEiW}eZ_ExMmhczrj3%A5 zzj|X~xJla!=8~ugwEPsBQD`=NjMp}zLr4m~w*(^ze)FVAn{aLQUkl#Yq!6)IAWet7 z8su6I5_ET%N1*1pmrc6Khs=e&62c*g{<0QFI7!Gc+Z3y$@j71H=SCp^F*Zb0u`$m( zwYPWuXHTbosOn0 zXC3OimMi--lC-3M-t9VTZ!+U5x3VQAwy>`T4?}&OkQ70heu3@ihWdX_>S8BFQC`R% zC{PdmorG5AEg7cLcfTm(Ug6@JV!I60=eIiiBkF5UB2YB?X^a#eM}pBPkzK5%HOd3w z$T1vbx237O0SINwUbiFE>90vAn?VRnU{*tVSwx;+f@p3{vGxWe%CaD;EXp0#Ux;yj zL8Tt_j^>Wcis9I@2x10)Cb@EGmCj=g<$%PW%YLZIi?J@gCJt;F%mFR?sJ*|tElqmv zz3lUkBVRhu*yKX^u~{mg9@U>27<>+k;+Ni>I-D>iO)3c(y4WLdvgIvNim#8Q=lpy3 zw|(Tqtz?5?S2GN5LCoX_i0+uKiRq=NG3uA9+YMv)fVp`^eLz@3wEB!<(;{(l`ieF`**iMuhKKoGv^r}V9{_S$YROiX2UU;f-VN5NIWIgL| z>5aof>8$d!+$!!T@C@mK2OLY*u+!|GN9B!-cL%)jW?mWbsH75r~RdQ9t;sf zAw1C$@T7C^nO_QS(R-d@3{Shd$Q8ZOV7LF;WAa8R*c-kRc&{x; zh+uP4@I7~*(mGX^`g*ZSwnV`WD$fTK({iu6SH(Zo%QO#_%hxlDeHddyjIs_HqR?pX z>r&IbDQuD^78&YA0#bAriy#WKFP#2;daT92`)xFLJhpFZHJI;QTkHE>4LY4rH{V;G z7-)Ro&RL&`T;Ly0z~N-qP)M`)5T~sjQ6tqzXkcY5TXBx9fYH#OpP_saQc&#u>-T4F zkO$Km3TZ9VS~O3YmR*drb*oKVJ?~Cs*cz4M^&CG@MgxEJsAz88=cEb-ru1+0$T|5A ze}j;ZGlS*c=^-@(pz?+u3j^;V<~pp1@-Bqn-1}4_(I^tnVcN8UZ`g8J<@5 EKUPx# diff --git a/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/edit_message_sheet_0.png b/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/edit_message_sheet_0.png index b3c8d9a2772b061dc6a3661e545b862bd10d5b84..2a09d8b397ad1ad5e5be545ea8031fbfe86da48d 100644 GIT binary patch literal 5501 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 R;QuKP*!a#(+zr=f{{y^mkhTB- literal 5520 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 CR^w^_ literal 4885 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 diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_light.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_light.png index 379343902f00503c3bb6cdfdc06d528dcd6606cc..f9b5242a39c1cacae319ef4b80bfa0e7697dd6d1 100644 GIT binary patch literal 4582 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 BBN+ey diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_dark.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_dark.png index c1647b8b257c0cf735c30eb9dd6a0bb1298b35e4..d976fab03b303666cd79c6c84e3546ad25bf3997 100644 GIT binary patch literal 6755 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 literal 6711 zcmb7pcT`i&yY8kVy(2wzm8Ntd^w7J~q(qcnMQZ3BLQxbxM4AXHB0W??7lhC|1XPeB zO}Y?zIUDb9t^3_S?pfz#?KRnZvge(5p66{dNiZ_dq$Fb{0|0~Yt)Jw=)}4HaAL@n+>!BQRnJ zob0Q*5-wOOE&n@;lBV-mLB(v$B$T|XPl)ava8RCjX5 zBRp;%OIvaqr~n%qhlVig8)GZDmKLlgLfDKgwz7wph;9=lHq*Xs&+oQ zrX+(R&*bM?S8f8zf6l6r8|PNepPm8{&Lz<+%dOG1I8LCgoBAi2!&%Ne%GX9SeA}VO zbC-nF(!W(Tw{oi!y!XyoUvt^aY z5kI!_dBAun>+<<$v0TUAgPV~;kQHf1*w%ep|{^H2|tscZ(Y$w)&YE2 z@uHV~ab04}@`s=*o#KuXZcldiA!WNV<-yn2*(8yO&I%+r92i?CDg(FX+kY;DIK0-T#*SD(#2JaUO1b zwG^W+z3lxLH)C;H0-7A$6*{h^T&dNxZ)PsVPnUQz^1Fx~o7w-|$yhMA0D0CjHH}eA z-(EC+SF2w(mmW(gDt%Zc_K9Wrgxywa}r$0s2@j%8o{um*V{K&K;reR zF?3-xnBbOcq-f(L)bORA`v^h;6WU6s409P`O111gjYBh+q5T zT5gM<04g!~BU^s)EseNSCxvO=E4}=_+KcClP65@Oa$uNRlZ=Z$xdj)NW&9h7k|CsC zXa%p}Gn!8HU6b5KCVtjyEQ-h>kQm=wI(1sk3NC|;kB%4((9HczC zV{ws@DN%8;vN6N^`#t^V(B(Nxdhlf)@tCX7{r^n_S781pKRhNQ%n2!^KO~FgHk#+z$e79v-zT3+sO2(%K%xYf;z4 zI5Q*jg{!DTIh+d~igQSQ_8b7DJ%4|Vqs)GUvh+Is!{2naPyxaZGyW>lxUIaNXllg3 zB526oG5>}D08%hXxs9Ic_s8zVba1mnE15Gy;Q&xmZjn$nBR{u`7{LZmR%``coA)9G zhHL%gzsXN_*o!ZYcJ3jTmR9mzSo<9%{5M+&L`2H8K90(UosmB0*u(>l2>hndJ~FJV z*|(+Tq6kRf9TP$s03c*mu7RE0JVqTf{lPDIsWpv)2kl}3AgMFa>PaL0fNlnFrrzA@ zOU@j*KnlQ!xfC;)+i2YS9+y6%@m)p--h3nT> zL16)GN7d6K!JB08z~ex^G;N@5(5C(|?zhyi6o=DLZdXflFaQ{Qq;K>@bLbEB z(Ilxq{k#GU<|8y2i#Z-3S}!kQc5trb>%}DsM{od@zH8p@-&=e%&~+5e)#cJTiMds#Jv-lVEEUgqw9dJu9M7U*-wj9w^Y7IhaOLg<*

(=FrD>#&$ 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 literal 6595 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 SG;M)SH%LPT0WXD_2mKcjZVu!C literal 5896 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 literal 5814 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 RecordPlatform.instance = FakeRecordPlatform()); @@ -19,6 +20,7 @@ void main() { testWidgets( 'checks message input features', + skip: true, (WidgetTester tester) async { await tester.pumpWidget( buildWidget( @@ -36,6 +38,7 @@ void main() { testWidgets( 'checks message input slow mode', + skip: true, (WidgetTester tester) async { final client = MockClient(); final clientState = MockClientState(); @@ -111,6 +114,7 @@ void main() { testWidgets( 'allows setting padding on message input', + skip: true, (WidgetTester tester) async { await tester.pumpWidget( buildWidget( @@ -135,6 +139,7 @@ void main() { testWidgets( 'allows setting explicit margin on text field', + skip: true, (WidgetTester tester) async { await tester.pumpWidget( buildWidget( @@ -179,6 +184,7 @@ void main() { testWidgets( 'should send message when Enter key is pressed on desktop', + skip: true, (tester) async { when(() => channel.sendMessage(any())).thenAnswer( (i) async => SendMessageResponse() @@ -220,6 +226,7 @@ void main() { testWidgets( 'should not send message when Shift+Enter key is pressed on desktop', + skip: true, (tester) async { when(() => channel.sendMessage(any())).thenAnswer( (_) async => SendMessageResponse() @@ -265,6 +272,7 @@ void main() { testWidgets( 'should clear quoted message when Esc key is pressed on desktop', + skip: true, (tester) async { final quotedMessage = Message(text: 'I am a quoted message'); final initialMessage = Message(quotedMessage: quotedMessage); @@ -313,6 +321,7 @@ void main() { testWidgets( 'should not clear quoted message contains text and Esc key is pressed on desktop', + skip: true, (tester) async { final quotedMessage = Message(text: 'I am a quoted message'); final initialMessage = Message(quotedMessage: quotedMessage); @@ -397,6 +406,7 @@ void main() { testWidgets( 'should not show DmCheckboxListTile when hideSendAsDm is true', + skip: true, (tester) async { await tester.pumpWidget( MaterialApp( @@ -422,6 +432,7 @@ void main() { testWidgets( 'should not show DmCheckboxListTile when not in a thread', + skip: true, (tester) async { await tester.pumpWidget( MaterialApp( @@ -445,6 +456,7 @@ void main() { testWidgets( 'should show DmCheckboxListTile when in a thread and hideSendAsDm is false', + skip: true, (tester) async { // Set up a message controller with a parent message ID (thread) final messageInputController = StreamMessageInputController( @@ -475,6 +487,7 @@ void main() { testWidgets( 'should toggle showInChannel value when DmCheckboxListTile is tapped', + skip: true, (tester) async { // Set up a message controller with a parent message ID (thread) final messageInputController = StreamMessageInputController( diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/moderated_message_actions_modal_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/moderated_message_actions_modal_dark.png index 5ed84feb64877f62643b4737d0bbf68a8f4438dd..d472818e0ea5b4d4adea728528f1074ded77c4fa 100644 GIT binary patch delta 1434 zcma)6e>l^59RF_S$Ig%W5xe8C@|1eIl#t59D3R+y#c*S|5-~E**<@lHmwHmseY;a9 zQ^%#IQ87H&=n!&D&9;Wwm=bGEPtBMgyG_sC-}l$&^Soc5*XRBD@p`>_Y`Sd90w`%A zfdP@{?_KV0UrxPhv(zbAPBFA@A2QII7FLBZNd&3+2z`_PkS&3v(JyL+?PABRUV8 zkDect-!gc`|1cS^P$XDcNH5A)IM=IunV?MeGCn!@o3eWO{I7T+J}<9UwTY70T}gMF zp97b-hr3jp_GcetM%RlU%w>;}zw*amc9jom&K5)tzIm#;$+G9;V#G;XoVt$9g5J&I zq{#A#EW??3mV6r0aq~Ve6~`vCRaaT|q5dTGA|8)#SD8_{EEXHrp0~nGc{H>g`YLpr z{?NMm?k3&1&1IQ#La?3Q%ZVk@XiGDVsi*fWQL}N;jbgFD1E7zmH~U=iW)~s)DQLO- zDiZn{h4a3`eQ1tG`E*mPs6!sbc%jr_uXyZ%vIrJ*%1l`Q%sA=091o!4jDC(T3h`d5 z1=8=j$F&!0&4LK729Ie$1ciNpKXCTY#&>{3BL|9^R*u?CAgI^Jobc-{3H)mGT2e-- z@!=`hUq)D2j-_hw_fGY&#tC861ONFN>AW)Rvy!6`OZXq;nSxbk<*4Y=CA`G&uoMPG zHfdSPQGGovE7E!O4U#$Q?*WA(M?#zBZ)4F+1_QCa`U$)1IySs883uEcj(Jlk$H#?; z`S;!3x-J!hpYHB}&}Kbviyt^-u0CckoYtlki~+*RHoc`?6B5^F*2Xvm$Az4aYhU&>eHWdgdRL0BXE35WRisc- zeKQ?4WMUHc{YYcCL^yz!FbCcTu{F<2v0Pem8)Lporersrn`{%4p+!9YGckoZdrxzQ ztsQTRY?%Us^H~-jCNq6$<^+mA?YekvYRqeu`lwjD1*k~*qQTsP$$yb0>YYk@GaVrOSX)~nuyM;+f*kO1?4<-j zDry=Ud5r=Ww~PhI+zQ981l$n)^h^z-So=I@0rHS>uwb!?Tx*#K=nO7Pp@heBV@giI zl2};&^i1o9jrJdK>UE6q`i(l;S6k*j!3VAimzPCn!<@bDQ$Ob?gue~0-r=*yP3K_x z-{^1orQ^XiWtyenAM^m3MxByJ*XbR;);}pFRkzwoBXsc--GFOgDineUs(B8HgmoU8 z<(Eq9j`{7+xCvSANzZ9mTx=Lk+RG@i+XK|`8M<(BRGIr#TO7q;{!1l35hp*;)vwA= uC;??49KIFa5r!-|apB0oC(s+7yNP!zu5H-iq%If%Amm6`VEv)Q!hZnw=H^@g delta 1471 zcmai!Yf#cz7{>pA*VVk?r09@Lt-9^zCCye%5M7nbytRw1z zmJu2h@LPVx+^BzPTr%>qOnQ50bSSomHerjae(rKG*Yet~W>Xgj;OgZcy>@Q1&NT`M zD~bvV4!=^~_nj-<;NZMdVE5pgGspr`9_eFLnUS^Qwbh}kiB+BweDhe^(;YwK_u;TotmC}E;%{oKx1rp&9Q-S{wzO~ z7%w4jcaNe#aU<(?JzFc@)qW^mGC-LRe4Y2{B!n`hZEtw- z?gSEZA&%-l`%3XAtZzU`Nmyz#c@FW*p-Lq(u!oqN?>+qL^AE5-VtiZHOm=$a{8JW` zmewSf2crvf-Cy?iPD4E^BNTsx9Zm7AJrWCm76N!&LSV`6Yol>-s3e!_s`b zd1=OeB*TLprfh;{tNM~u04uD2=BaB!>j&~*1f~tGpFRSAPeu$G>%5M z&LAn+jkVZPS%-zdV1^$}iAdK#h7cPJAdR&*uEoHiY(^YHb@<-h>QqzSHVo=i^Igc` zo~`o+;xNC{U;j9aDI50sn(Th@0D``4-^EnP0t||UL!sCpEC3oB*wIdF5)F-u>@y1= z^Ll(WBbl?ERfT+BPx1XM=m{7{b)ZC>glt`1(e5xe=r+E@%v+VX;Wm{D>Dn)I?TZBD zSNWXpmuclNsnf!{NPw85%3HW=FH^aM&yp*&FG&iYx7XbnU`X3cuf&i$cy7|#FsU{` z$55_gSgr%f3(80x!;AS=JXp!#I0tw_4?~leCzTG0=_*o+Zm3(kRxZxDx23ZhO@WA0 zC#iqN5{%~(<*#X+PxrM+ZJTQns$FGo*V-x}(%Z(_V*gt^sVA|YuX#P+0B*vP;bUmc zsv*Z2V74xEZyk%wulFee_v(9@e8rTx$yOl%1>+3L^wU6h1LQv&1s@-3zMx==;e!Av zqobb;6_vnms$6Ym*z8R7gkOC^%NQAoos_K?aGWa`TO}|`|0l3a4{39gBZmFnaQE5R zSu~{mZRLng*8;n56J2a?esjwk`7BqV;|ba^)*&H3Z}#58Ii!2ML+3#=o1qjmT|`zjNn4^oVXYZK06dDR^QA22zyDct-4}(&{})vQ#CU~|pjS;zWIXHw^^F~^p5Cxw vL{ox={lm3%;4x++2LLVgQ$P!d-u6WYD|8$`u-sY%=vHWOL{NR;@r!>00xIcq diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/moderated_message_actions_modal_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/moderated_message_actions_modal_light.png index be72c13d3f2e7201585926eea2fc317a49c12d69..6dbe44ccbd9e8932f7d0c2ca0ddeb29c0b1bb254 100644 GIT binary patch delta 1533 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 IG5>G>1wW-`3jhEB literal 4796 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 LD5vU^LI3sN_k~mg diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_light.png index 58a918211c66d5a633a75367bb88b5b7ad0d244d..d16e2d5884eb3d606366b44909efeacc815e1439 100644 GIT binary patch literal 5218 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 DTYJ%z diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_dark.png index 37bb042033b935748c7daa8d56e9fcc99bdb2a8e..e3bb2bc834fce0633a1fe9e587617c848c9b5132 100644 GIT binary patch literal 4837 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 diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_light.png index 101f86692c5c9bf2eb712d7eebec49efdb75afc2..e93d9d44f1028f3af9c7ed833ac5aa76a5665195 100644 GIT binary patch literal 5191 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 L-!aP3@bCTuGbk{I delta 3443 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 diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_dark.png index 464a49572d82ec1ea24b4f182c586fa37b6c1a50..2827d1b37ca96f860026a65e1337c0efb5a0d188 100644 GIT binary patch literal 8230 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 literal 8213 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 diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_light.png index fc1c9c1a1d958253f6ce07e55f8d28c7408a5727..09fda075ae3cc4d9d0db3681cf541a9a2530e129 100644 GIT binary patch literal 8867 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 literal 8796 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 diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_with_reactions_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_with_reactions_dark.png index d9ed843efc723d330be491531aebc5cbbe6143cd..32ad0abe98b8aee04a7a06963286371998fa9d48 100644 GIT binary patch literal 8174 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 diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_with_reactions_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_with_reactions_light.png index db59b1021d661153c149a87b34747d6bd565bed6..892636f48be9ccda753951a7e070629693d54c89 100644 GIT binary patch literal 8808 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 UgL6o?0l)*Hp?|kR?ZM0c0U>bXf&c&j literal 8735 zcmeHtX;4$!x@`afw}^^>ptJ|Jq5`6V(vxh(4Wfe5_b4DpXTZ?+=tgOT*daX#C>Ssx zB28!#6$mJu2!u9?B=n&R7!nf_?!^7xdADwzSFc{x{d22M{v>nNS~GL5^?lvJ{z=K-IeR*7#w{KAvMF_zU@zy(n!CMdf-A-RMh+TEWcNgM^sd(tCOuk ztkp?rdHM9}>S{-%aa#B*ADFtjdOwjUm!F^CPaqt)a^>vr*-n=-Pvo-^uPl>GOpy4G z_k=zA3JmX_M(#8f@bU9Y4WSV(n3$NLO%jg1vOG36Hdc&At4BsgQaT?iR|kyONgq6j zV%)OMj@OD$6p_NS8MMTE_rHS4WwdWUR5nR(BcPo;f`i`^l&>A$h#ADRr%H9y4!f$# zYvqam!vPx|fi@{J^G_J8&QP_(FAP=q84A73FneW>i;a0-U*FN2ukUBLN3cfFvW)P? zL0R5eD>5pIy4Iyp#gLY`3|sj62dqd>U!Uvh=DFi#ubOki$;#%nFOEn) zn`*C~oQA0lr_bP{4@5AON}0(%E_um7mvrgcM+E8-8K3pmMCLm z<1l85eZ5zRnj7|w7b3rlS0q5+TgZ0aEh_ggZe1nLerQ&YzSZ-c6mu#xSnYY0{Q*0d zporjLwMbGxA+cIDmOera8p_D*W3ALwRcQzb2^Bk^kkTf2!m<1NM3KhuzAtk$T5_c%A}pP^H}<_QHgIKF zNr_?@w;EN=erJQ&n8+|YjOB0@+LfW)2>&>*4qdl~N^)?(<_r;@N+1w)bC=FxBeue* z$tv}Ej0??2m6c0^Rx(#od!P}&gW$5@j8f9m!)Ntl)hn?JDX5j0)z-qKLf)d3m>3<& zjML^(`nLT4Y;wKy%JPj%)7iS)M91%2yJm9Y2a^{^kF~Zc(;p+r;uZ#&>GvyfOIQw7 z9Ugmr;?6)CxSl0$(cS_5IjZwx0#Qc1&A6F~Zz6|;sksq!Q|HTO6MFVu;YM&Y`+9v~ z*sWG!N=@O`njoCnX%Qz60+cte9~Wajqc_mmdCP$H)T9_4aHDyuUEA2o%IcxTWn<&E zwzet3mBQ`ZV0CSANF&hT?{A$S9PtE=n7InLeK1Sa2Zi zB5VN$rckqgdmYcr&B{t*Vpq;rSHv;bV?%@e)r=zgX5E8KOy1Y3KV))=CcW%R&ZwL| zwTi?EqgU}(nZ+L07^F#ggYcsT5gSv9p5E9lbe(YATIYK>XH^-Acfqk6=N9wEr5eO07>-%Em~Iv*z`NsnkshqcgqQMh`uj8JCuk_o7>Jj`SaI>|{ zNI|)zPS~ovSlzz3-vio7BTxC={<5$om1J%{c(3YpiiAO zik<9;WO<_wmXzaAj~f)A^mJ`Cd{7z3*x^O1MmH zE<18D)1s|#i@7D|#$M1i=kT6d$m!n?+1|6dHd%+kB{nf7j9RnJ4Rv^{$}05|3UQQq zRxdL>Ap5!5tMD$DDx?mTS=rKJXxmNcu8q`lNXSO)28)31EANbp23T!_iKox2k5ewW z>2llG+Nz&_{`@&Sw4*XBFYjSpU1HFD`GSkg+gsP7Wc7YbXx)zZ#?q>r@LHf2Zu;*= zK5U!o{Cv2#YkF<)k9oJ58 z_CAcvV|^#bQ6ikqa+`!HNFF0DnzUrcVBi&6W|<;|%1Z7%H#2)##Ld;!z)3`&PRYXH z)15-X%=R|=iiYE0?3m2GfmrSGAMQ;rYR@b;a0w064JQwOe>EN(4wXPc>A}Xv6L;2A zsXg#!%`x-jF&_84RopVIM`7y)eKgTV_#GdHeqmVhY-=g4Qhl+S!qT6`#gZSmdkQHW zc@MI5P{gO7bZ406-CQw^Dyx^oI<2&2TR$z6m4-%tNm#eEI+l}T&NEW8Z&0MLGF7nS zxF|)VpuX9HF!ys}v6~;xThSY6X<>LGJZ{~Go!)wv8+8~zW64nt#W)=+;6NiVz<2DQ8#Pq`$ z9gmewl$FXuFVFawSrzJa&CN|pL#MmTDl4b0I$%eR9HF4fkS-V^i6pP>y!GkRtbS1A zI=-S0tMmh$IuMFOFD~Z{t=rJrL}%{D4<>*JwuUJwxw7u2{Xz$3>?(DTtL++db0T*9 zk`>diaB}kTL2_Blo&!&s*HT33jl<1K*hsQ=8J0PL=tvK-URUI0n+4s`9zr=?h+;0K z&XjuKMC9aB-EW53JhkjP$vj@dxs6$iVN{21WK?5`_3;aUMG2pUO0>3iq|4-w?6OCU zzN+^8OljE2sqkoJ*Ur~$l$Lnk3|k~agL}~*0c&#~eb*~}NN=C2ZFlVCXR9!Be>i}l zMKTVmr@!6=&&kQ5)RP%9L4Dp>Y3OGEj1SR!AVedH9N95m5!zi;TYK0%q9*c>Fzk#Z zVuPPFI4GfkTYAp=;k}a2CVeRQs4LouI-q91FSBwXRZc&wT|*)6I3@flIdHZ@S&5Q9 z4$rS~AaN_3s9%d_dn5AAqoSNPd!ZaOpmk}7FL~j{*FPlGRN*do0CYa7rB!pU$J^?7 z24y&Isd5ek4VLw)DxXfuSApfqAa7@lNZYpg-bO4Z=r_=ByWDw&3jc zbz?Ezg-+&mxS6Y)vDPw)`$blwL!N;LP%1^f+S$a(Zd?JyS1*(S7>q-M@doxc)`zyKR2;W8=-kD5Ld66eG{p zE!3x(8vxI#IXno*&f|6N{J0g(OLa>F`C3y@j=BEvZurlx+v&qtw%v?psbB&9F<)Cevk1YP?QC zzqPU|EL7wlM$CzBTcH=cXJ_H0MT17YpjJ`smwkA>OqGIYJ-o^h%9vl}i624Ul(`Xm z|Iv4b1;n5=aY$5TqX*T-u9vM=FnTnzD`=}8CnZCu%5^L)8RPvRb2CxutypY9_~<2g ziW5BIqKn!*DDV^xG+Ocg0}~3HR+&bs*+E&IRHhSGwvOh2()Tl<{$+g*fG&g0)z;om zzJdIfCXJ1a?n`46-l|`vjW+wEimR$r&blz~&J|FJp$t!;N$86;nmF4ewvu9q*FfnN z+oRLPwkI;FMGxe2@am|r=ciGc7KV&x6$s4hfYEn157ifo?KI6lC@06*`!JP|E~iiV z{_);G!{Ia-kPQg}!tTSBF17LkyK3XMc+%3+uYsWG^l1($$Kjv=*4%q?ForD>IH?) zfIU}22@=4i#N$rAhp~4dLK%!OO?0T+q`st9Ck178*7`~%} zO7BoWf*7!`^|c8O4Q=%LtMMcta>9G%AM$vylUXLtrX$}S9UY4+Dpa?>YHQDcFIf#t ze0qBN`U$zy1_oT4zh+DBU}VL`#kV=2hlj^0EiDNXq&%8oKNzq6NJtyd#HO&7N`|!P zO)5UEzrR0-P96K+@o zaN0!$-yJpAr4Fp)QNw-Ccb;2}3wZqr%hEPCT%NqH+fB+_`s53!D)^xMBvW{Ewjkh6 z85zk_snku?yr zTX$k(NHvK5VueGNj!sSH?fDo0}6Ne{VP{5xFns<6Xh(AnKp=7bikO zL-F`DnE=W2f9w+N+}?3ZOH1d2zs}WyVh{9iUsIOSNy9`zPsYMfFS_g>YOc*ACW7br zcKy}~#H;)Cmy8Pr65Yv_m6dagi*FdxU%&49%@hbwvAqI_^7{IEf->xUF2lhd$Wu^D zp8Vi0rFzx9E1L-gn{aZjH&Jl^=|I|Fglv?wW+gG;qt@D7oqcu(J z#f=_4MRj!}%fIl`(y~)M#}%(!xx(Z7wwjX@UC!s(z2MfHENNc!I6{1fE-8}lk&+_h z<<6auWBmNElz7NLhc$aTS*-9=Phq0g$0Z9N@3{yLw6z&;9|#xN5dlVXbGNofEN)`6 zgYXn)wm{Wu|LcVF5B3a2?zm+SmKM7D{i7oiBI-xrJ18TQT2xfzUYejAJomLoT1KV< z?EUSopC^%W`k`$=kXv=RdqevUc>#6ZR?}zXL_77ONb-(c=g5ft;smB9>A_vy>L}*2 z&YdY!bVvMs+mXb-qBa(*q1pFO8W_kng)RY+|8;d&%l;Uk;YmZ+hPTDRd|4p~{J?8R zwG^K|eM%qkK(_O^oYZLhqiw+Zpi1FbiK%WyiixtuP#cVMI@+loUv&O~`%t+}HNfQJ zLVhpL=x75rhvN|#_?A%e;liuc_`J7pGIpdM?iL&t*0DC-6RlyoWI$5b_fE5P*W*!7Lg- z5YkfN^8s^9s@k@_yV>R5Uwi>Ue+Ih;05J{Rcd*>Ty=>ooHln5o1OjA&v4w?&Z_$P8 zJp1>zuBkb`Kgwpa-LGF)1U^LGMZ4MZ{f~fRa&d8~_8ls}<&(YM;t3P*^Yu-^VzG0} z%jqsnq3xuWDD;?1S^3%m*1bK&z9q8OLl)+E=~CAqt+T)|%GYp@_qAKw`S`}6<_N(1 zlxX|w1;;bYsv{W-+xP+Oq|yB-%|!Vudu%i&9Fn*`W?8sP#1y0|uo3EBKM>IA0TvM& zW#pbk&jYN+~N6iZMr&e@{^MMuJ3sVPB=?M604~CXrBa^MeNZb~~&t4MkGFba(41g3NBed}pE_$GL|J z|FKc|@Ns{C!`3(%Nh-(yHDkYOQ?5;+@Ew_Zzr%nS|im@jpvXK##1ZXOa)WBL-+- z`|BOZ2YFusogVgYK22H|#>v3r#&bdJwtGXjro+E~eBh_0{$bNP%t=vYB8Y?T@2{TA zQ}7wxCbd!XSE@bf6xIiFoPiNHTLXVT#LvBb7i|jCo!B_RuRGbU?W&+n4yH!H5k!%y zghdc){MX zGB)1atnL7Q(JYdd~d0#1T9aEu56<9>#_z_I=vQ{QZ2#Kfw-`$&#R{*xNcT6~x( z*4h!}k_Z3$4A03=iTz;3pkmd^3to49Jx+spm4iL%lj(kmRXsO{9O7%5Zt?FIqZiYv zfU^i$$6u%s5sAkf{@1AcUqRSa#E+w?pOU>J{=U1&j`(K)``@@A96-2se;|EKa73P5 z8Rh@~Xc;_n&?vQk`9L8;OK4+v@4~6bC|f)uw^H5z2&A{Nl7Fq*A{Nl0BVG(?P(1-^ z0@m}k<8HCd@eD`g2LoF>zK9MYJk2m_WBc03Mv+|Mctk}-eDfTe=4;3g8JJSk`&CKI zT$VoNEIZiuzUP14(zvTLxbM?(_GMLw7xzGH z@xO+rX{!Ad^OGj>X$gk+G#f1d4{wMasI|%cE#6~6Pf>w4VhA{~S8(CXj29nYmOzNy zBXN2-(a-f1dOU84#su5)%qZhmTrdQo|8vaFoEGZFVRLN?1;O>l6=(Hk=tCTA1c`G+ z`{Y6J1bmUAHD?A~nyQ!78X@7Hs?qa?4XJHrPH7_@I7cLthF6o>*F#*zxkdjvjU zhHPNl63AJ>s89BVn9i)^zgo+G>T^AFa7)JiH$9kT8q>soJa(fH?IuNEaLcA8LvAI- zZ$5w`kJ|O}Y6u7T_+CF|i*#l?$XUeY`pL@F|3tcT$UU@Uy&Mj`Ia?w^G~$Oi}XP^m@DUtM^m-g zt9kg9yasN?^6QOYh^$>4Uous=H#gTpXsY`~-cLq$z3_{L1x>|iGvf%#)UdY@rMs%H z*$`r5SIDP3ke#q^cYF@w@ZUM~pV=6QJTs3x{P;xl;-`;@7|{=TxgpyMq6E;+ET7(K zD0keWUuSsuL-T1MM}HL!pa|N9U%oo*?oJtrb978cBSXrqaPrXDKW_MW_+RF$(Ka%2 z1!ww-yaW_B%g4N7AtlLe^E3szb)ju+XcjmN0UbO}PFj0!;N2mUqUD{YFis&2dT1CrrSY&-*W znAl$7RRiFT)xpW~pwC1o33R@3Df?l1I(}nmJT+1$e!zdcPW}D^0O&$xM}yVha5>D5 zWnxomv}ycEOH|lyQBgneEzj{?d-q$i1P$6Mq>Rf+G z^^Mm1x9YqkA8l*6@wxy+kBMBx7{BF%J#s09Tbr}}0|VkjBC&ta6O_MCV3CW&CT+ki z)g3Hn%NzM80o`64sa6IQ4Dcz*ZB?>O&dwC>zjd_)*!@7(ahNN}U;VvSt!-6rP$sMj zG)yrHbqvs~XVIm?-F~3i17?2Kbu*Js>xHhH08wMthxU&Nzx}(N{C{n(@16=81@8s4 mY`+We@4oy0$kz^dP|-1^m_btkJb1$ca`A$>apk$|cmD%1EV=*y diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png index b5e37925d1aad939b2d88c18a43272c7b5e0ab23..0065e1d2e7c05a3b6664b1727d0a1a9298bfe9f9 100644 GIT binary patch literal 10510 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 bBViYw_rulr&BnYP2>3wsjBi!zIK21|c|U%E literal 10996 zcmeHtcTiK?+ioZd%0UzaJaiF3siJfVAfgndL*P(EL8VLYgn$YNC{;vS5b4AaP(n{o zI5a_e482GRBtU=w0Ybvvp5J$8?#%t}{o{UfXYQT(WirW5_S$Q|Ypr*^@AEwGPB1gk z=R7TN8UzAy8XD+XfIv*CAP}=H`$^!R`)k#&fG^enT|+B&;1$XK`1fz701JIxP*uOs zDhMP7GSs_e6`Hp`9g^xufEH|#)$6J2SM=DJm|5AGy_|WkniuCFoNw0Mxm@>2Ez!lb zGvmIHtHom}lcz#b8DoE{FW6q{GW^ug{qAxa_f?BEUNI(aRzZVPm^JyjSqwezD5k@o z>Ejw>qtTms(C7BjpE{F8mhTTZ^piMX20e_Kf^dOAgW2rtpsz2Z0gL~A@b5|Zf727% zkRqTp2fVdrnw1)N8Yym3Em^3`eYd)rbswuROI-?sd*BiiJDz*sJP`4*k^vG2NDh zw*FjxE>(rlP&K^zT71YB5zj1Zi?$mY{kuVPz;oAZ5X4&i<4JSVI8jHRqG`(Ga!K#; zIdL)X??H}BO%aP~c~|252{f!^m;QJOy1P2i!}4#Iq=W^v?~6@K4M+DCBqLQP&FT=j zp$~`rg!CJ$HXvLqOwLzvPtz-ux-_fZ8lM?td)Xl3W1`Qds`ziT={!_S(kn{T`-l`d z$*iL@VW5%gxqEi7^yE7^ys~|j_wF_33?o-NFe_`ceRgev*_D=+5XUD0>h!UckP@SJ+{CfO}8P>+nEB`1q&3(UcKO4i5x zUbM|uL#iA~aGpPjY-C}t?*t1R4!-HmfgN{`lk{8tMz64LNk$Do56uJeUbnU7-To(ywtU{zj zE=JK0j%k)Sz(HSoZ@7}Df->qJG&?T&|7EJRResX6Ajh#>l{n*SgS0Ek)efYskvT?2 z1PU%#%JSKSgrrEgcO)We1MtS-OG{Z2hS}=P_NGGGPY)gNoH%fBEgzFR%cF%i=bD?E zw9lqc7}#cgQ4DoKI;)q^v7p=htl_WQw_j)seU(e&l|fkq_OIJ|;Fjg{vgDi;$@sf2 zipUm^%w!SusX;}@m5qpvk%=JEWy3TBE7hscBrI)E)fJ}VP_M|;xY3mVrBy5Hs9c0L z6Y{iPX#+#fu)~BSC02>X_?65oCksuunbINS*5(b@xV{46Bq=LtKB9oOUHpp`Z?UJE z6yk(8-f>G=2J%3;*{mtgdouWa=QSrm6)z1k)y1WVlitGd!*T9M*LuhlW)4-+)wC;! z4xSFTM&QP$lY6sJR!V%inEn3hEVl=r8>#h@d#!cU7W?Es;9ANwQmCe4fLzx>*?jWr zJVjWrET1Y&&MqaomWhO2k9k6ILSWMwF)?8+-zBq1!<`7#0mckPlan+5*%-mGPnX>O z)<6P%kTW+B?k}XDtKxm|m2YSQ?Ru%zhb)5GJR11|o0%Np-@hQQq^zz^Bi+}yNh6tE zDMpi2`WTGG1ea~7Q{xnkpXCsX8q#3qK3yY-$N<93!|8hAq!S_WGj2*hQ=w~xW(J~I zf(rpd&A!oYtc^9RkUcs$55{~`XdyQrk2Eb*MbtiPkuUnWs&3wu_vMFJ37m&Aczg4O=&N{%%NZ5pPASRUsU@%FQdQ|y-tQ{;dsOaEEmT7iX;LAfeNUy$8-9yyZo2F&oKuIrs zo{6C5e+?lyuF*6@RIRT>GPHXvR_Tb{OKIg=2Nj|Pbj(sie`elIHyGd`OB73fVNXp4 zBioW^!6hbzDgOnqF+6o7+^P2lJUhNy8+2NZ?N&JA(SZkwZ;53CF9kdF)>Z7>&a;^o4BPvNxOLmgD`{wa z{Jm1fx%CD&>`%~HSeEVmM?L-hb(j1NhurSt$t~t-fC)aBpkS@N_30vhr_j2qsv?nF zv97h@yA$`|oT}>Th0_Y)G9a_%holu+e=#N8tsY}`XX$7QoF*%3DwX@dNT|$$!?`FD zSLB15@_3P>ySG!+-x_h)kFC$P)<0q!R9MDvJX?JKLx~DG48Tv(4iAGCH!r#`|DpWD z%UdXtAm?uKFsBfK(2v!fryHkn=oKkG`K*rddk7P2rW=(!>=(c7S$$&oBmkovyKePo zUl7IDV-JF+LypUylHZ0GZfz+7?pjaa%?Zy_i?SLI)0!=IkXMGT!!~ye!{AhgJ9>VZ zVd?8=ldIx0gq{p8Z*JCC(4^L?cuh&nQiJAp@wGnWjnZWObRd7a@9hxR@#~}b_NHtLCo_cvbP#(@l64v~pji&x;|IPAX-Lrjtz>;+Ze zu;U6BeJ)aklxQvnZ*d88&`GkZk6Pd*N82MIyGLJ4m7z9qgfNY$qm{Kc%t^)%QoY9- zR$IsegM;q$)q*2)%P!x-Ds?G7J@IqE{^MdTZY~SyCy^qsjk^uP6Z!_jS$1G{1Duh1 zb=P<}ysSMQR&H5lawWI7!d8~p=n5aiLKRfKZ#hcy?k)?m0jSg@U!%}hEpld;Wx=H= zoQ7208Rbdw2yMV3vXPygchT-6eEAw-4ycWYJ8=AdV`CKI4Z+)enGy!3FS=;pqB+$0 zJ)9#QJn`OUYC=E1z3^Ghuh$OEH!r+~#3AtCsZh-3lx2`3+jX$=H^tO51f|tA!@k>+ zlTS1vPUSDev{FwP4yRp;4_t{SuZ|718-w>o zXV=}V$4birqJri~ce<1%Y7A%M){lVmJ383LADfXoAdKscFuR9km807W_2jz-} zan!O5P?WS(#0!wbJMqzh(T_C(IyknYgRLswD)-3E^2;#~Uu>IyWs!Z~QPq((r-R~= z1JCi80JjVZuokz!YJz+Y`u&B1ZcLW0{`Guui{=-pyD1W{@G9ugK~YQg&MI?(tM_Z#W# z`@Md`yxUneCR1OT+0iU_hL;&*(usQ z4LKhHKONhW7%NUl0x@|S=zOec;O=>=o7FhM?NUED`m6b-jymakF zC(DcsX`XXpXJbxE2qaz2?>Vs>taE#@OtNuH^lbFkGbX>Mb$I;4%o(@$RymXNEVtNY z#a8dXn})1hSXI(S>0p>o8OXxt48vBgJiAhSA?4Ibq6O7r)=%hi##IWw;7<)l^4aRY zclQ)-Kpn-iEgG%f62f9qPT8I&&M+r*I{P>Dvj}(l3Egqo0yxf!ZIHr2dz#y|OH6jr zFIJ-+r^52@2I)?wQ3`};bO2;VSrz*@YgCvwAvAi9>HHIek6>k=rD_Mw7?R5 zIpGHzDEbr=6X^Zfv;WzFA)RT91y-y9`oJQkJSwfe z1H@~vM#wNo^w#UUE=H~a-(Iu3r5}yHH0p3h#4`nc(y3y(nsv`B_Crx9T3`!AJUOTw|3?SI~Gz?AHT;BvTf{;FY*a z>CANLCsh=XK*?2LGG(>a?3w?EDSerH@CZm(L$>z|&1{oN-F|BC&&XO6M3$E#;6NDp zzQi4(zj2LI`_+b8GT?yDTdXx8-@mYAgq3tN3~JwUprI1coDfG1KTTOCFwWs7U^FGBcOKmGc%FlO5joL9A^EoRYd? zslWir=VlB%cBw&r3v~7hz$PW}m*efl(fK;9m$GG5%~;QB;yJHo^!RC_f3WhUhKg7C zs&S7_GJOpLNGf-a6~Lj@nNPi1Xo_@UQgV=mr}jQbt~+O-lb%<}@!=F;OnN4+&OGF| zS+XKrPnAHvCx&eEcz>z@|Mfp0eN*Mpq3C_Afp9U!uu6RIcn-=*S~7_!zbRJeYf0{6 z2jy*zOA^np58PxM^b%rm=h|ia`=S^qr#DDA8B-T?NY$>9^fh#iGpP?}U-8Azd+oq4 zRhGK_BsTVE?7X!8uM15QQ$!}&r~llS736lIP6q^%WMu_C)crp{(Bvv^pZ4p8O#3fL z?(ROu<=>OZx+k4wd^O8hI@i$6`SXXGtrkhiQe_o5eoXH{`z4<6)uk8VdnKy&;G=gg z4A4#ebf*EqogGeARRM=IlE&d6Cw_&8x-kq6@v`wTg>2dCRgu1PHlA**a=>TP(S)!{ zoMsptl|$%3gjF73jXFN}zrMe+WU?E? z+oC_)wC)E`i7p{zE0~kXR;b>Vt<}75_YoZDVccQySBJ%&+qW0OcY2_QL_8MdA{ECX z9B~uqMU;6s91wVR16_Ay3XY4Tw)LM`JB2`MY^|zDtPFKH0dRCu-%2r-u};|0#vVH6 z|C#;AQ9#a{1N#L}2`Xyc z+;}O;n6~!e8Z1ea$sNo*99K2j1ZkyCww5DQKUqoUyN#Xvk z1Vg+=_mG}DeH=cOw$k<|+Em)_rxP6aLUq-iG^B{3t#H=UDG; zDg7rtNufTtRf|$zdoK0o0z^`-*1K1LKFlIIQke-&9Q6b1-iU3#=!pJkqkWv+Tni=T zyi1NfV6jVzt~Ky6z1w;$dK~jkfs{rH{#pMmNEm55Q*^v1iv56q?9@H}qfc}&Jbzcg zC+*eC+lSlaD032C)>3w2rrA;cvcMZR`oz-VHtg`@uPrxA?c4*F36?G$ySZ8VvTG1| z8ZVP;SQmaJDB*6~_hO!j>fmfFWu@1c=CI85>w>?x z+ItE1wP6d^-ms=s-HwmvyvJLN3J%gH1rF5#^B=GqWW3a^irVEU*x0Ao1D_Re*q;13GSaw%Dp>ZO{YgvsjiIZz1uoDVth#}S=u43 zpR+rlopvf2qXfnVI0kj+223ZyE)Nmlyx13353Y#=5jOjic$KePoAiGEYFwJ^|6Wa4jF@$qOjFU)#(WI`3X%2 zO`;RKCPJ8ODJ49YO}p-4?@D76;a1y`nc%BLBSpMPx3bZy^#pHP_cj5 z`Ow;D5ge;~5jpAm<}eFsRn=^h=4O?=(8n;n!SU@&Yw-DmyO|`S`SE97yH>v>K-*-@ zrC!WT(>|u|+lJqnyxn{B8)UE6Pnv(OU;%irlnTfSM8(Ftcm~wI2C_7ScBlY(M zRefw^;bv+r)gyP?=U)N%P{nJa6wBZh5E2(HT%b)jQJ73nBKmxup!>hIf!YW_K2i;hUV?JlUbf`x+JHbVe6~FGu*dQ&h zjE-e&0AL&eLE-oH9vtr%FtCT=UxF~A6r0&2rZ;zW1jt}(!#c;j|l3InVxi1LM7^n3`7|!H{&NH=hO&sS+J~p+q+DC^dDUs zuyAskc2n#Ew2O*5zHrPq*pORVB{NMBWn}~ww#3_osZGrhZ%tg~577MIgMtjxzb_59 zv@YxBtH1(~c4#|5z}0$ky>8*cEg(d=O?@wpiR-^#4)OdUuv7T4%;0fUJ?K=dZd34N zF!HZ2kn}|wc5fYeN6JJ9|MlzjY2O9o)3Lhg#h3KYklwJM5DzaF@As=FgSxYR!Qg>nDkC51P%C z1gupe_C7QR9sM(qBP*4`{l9C!XL^kI^yZ`T=`Ew0!RoQ?^ORqT_}DEq*Mn_dN|T6q zng;Cv9&&&L;_b_W=J(2;_#bhukyBBU)))$#k^r#5S$ssy(LK$NN?rM)his1$QE&9T zsP;Jn_&Y_eKF?QhTeGtpl#oI%6@+oF$^ZjvcBQ<&-Ufq#eqZ7Xmi9dhbi)mFL!h=G zoeLY;bSXDvJ`IJfwJY!?Nh@sLFYs^t%;4^Xut zcWU7gTR}m)I|?h}wMHc$i}X`~3f1Lbe!?0%w=qpVK!HCDIqD_;~4ETY&Ac)&+=O!yLxY9;;ScxN#D2m#ys%W!ybmB-lLk^*(Q zU1n;)Ifm&0@JlR!Utq@zY|84mJoL)I(Mo<3X^*6&qA^uV?O5w7z($eT>5? zApdhQ4?i9thuOm@b4db%PGibU5GTgK@o(&{e@3|FSCGi=wx(%3Y`;$8YNkh@T=(y^ z>B|S<(4-$RL039|2&@cT74Tlu+F;D&)pS)o1TVpHY$e#uGH*9&XOp( zp?>G%o~!%soBHuV?8U_7=EAV>$c^ZYX4f+R@waXBop3_ylM#;|zn#S|C_n=KaD7}p zR`;3Mz`-8W6ri5kYN0wg2Vt)i=Hg=b`b70~CYYJr(^hp2BdEtZ)EV^c?E4?*sW$=O zBYZmccKEMV8)X%ZPqwmEz7iJri9jU8I?BUBp9>@N<49JCM2InAv$b;eCNtN=OSr+0V-uus=d}y^VXd&}? z&o(`9q8%wYX(*t6BeX}R3ObAL-gH+_zCkO;1-BnD{pjE`$lZcE4YX{= z!Ds1phqTY%pK$vNl|91WJnEfgBt|XFGHL)kWU=~b+j7B3Vg#t*B`FJ*|Qg&;}3ps#Rvzu>c`#5cDC{oETn?8@Ruc)pX{MGZGF+4r^^5VCK(%8^3;2X@|@MvunB*KmkRo z3Q>k@_l)RsA^}wPlGwdGGA*ayw8HKx07H5>W6vk(W!^6Vo}*ZTUjA2`g$v!>0L!&- zhl3w=96h?~!h7mJQ!(29vK3zJ8(fWp-Ss*C416L076H^x{+EK`|3*vvpHC%R>Om1W z!^3HpyG}jR16|{~)cyh~(w1&R<$Fbl`1YC=wA@3n-Kc$W$kL|Dt&?#e8#19g%~WBH z_~%@OolE1$89-<(aX9l-0(8w3IDMPWRPpw5Yt_Ogs2YToUa~;2#fUNgqG63noUauz zMax{tGTQ#j-J2uEfz9H5KZWzepf8r}8Llx)eDv_H7}J)fX^Hd2SM|)%_oR6_wmr~* z%6fyjnHzI0pYU21#A%7Jwth$|LbCFmouw+PD|QB51ThoB;F4WkW7acInKAtD2w^#S zbGd(rsR8}^=VgurTTwC9@`t6#i2EvKz7pI!_AIu#m6?e^X>2K=x43RJH9C(cN$RaZ z>0;B=4*%Dq18iB_PVGPW?L^klV6?&SASPj%*umr9;HePZy9sqFarRpt2GwayIJUD~ zw~-J49$Lr{JPoL1>dE)4qAvp#jRUOIEKtt1wwBMblJ|=ZlXCX}G^!*vHmU!*Wc6QE oy#9Upzn9>@qL}`F?LWt&@5?T^^%BZkLBPxKj)`8C?!)K*4StvKaR2}S diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png index 8b9e2194653b898568913772561a342dcbbd20bb..8be55f6a6e3396eda82066d6f332d08b59b47ada 100644 GIT binary patch literal 11106 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 literal 11095 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 Yw$4`Y3>lKU4+1{wPqbC49=~||Uu%1=*Z=?k diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_dark.png index d73843ad3664dbdb11846d05d08e63d35d742064..10b912349654d9e3a223026c72de10e9ce83be87 100644 GIT binary patch literal 10586 zcmeHtXH-+q`fg~#PZan`^UzfU0STaVq=||YK{`@JL^?>9gb+{xK?MZ?kuEhr2ndNl z2%t!jCM|@H)X-ZJAOXUEd(OJ&-VgVFxNF`2TKB`rhh+B7%eP`b1d1lAoH_<

LkaoQI4;IZf7~`lG))6#LUF77^53Cq8Z=pj*GH zt7Q<9GGKTg7(5>TeYO<4Lynxm;#p68G#mO_mOPPjZhtM6h)nhy^GKSuMe!P$ zMz11ga81#z&Am0u8Ri8gx*Hznh;qGsqP^x-$>1E|J2Jq)j)tCx#YeJ6>V9`6e?hm{ zLK_4MXZkf3pDmWXENP7IzncWnKI=KMFz?`KexO3jGN5dG#`OF8r}^=duZ~f=MDNQh zQF}l)Bqmd$DbFB9mr)t`vz^{=(W!`^EW=-)&aO%6v+bS1dJ)4b!_vJdGH`EAdN59l z8ld^*9%=PMkm<|S0wsVRkG5yWH3!JTirrD~G)zuymUzMm52s(rw3(4he;Y8vz*sL3 z*)=(oAA^c0Z2wtr-siO`0Y!f3zjpiD-T$jD!NH&VErB=rj3Lq21bkXT|e6u37EzC3~WH} zXtQtAf^p>foAjahz(}~{3yHLHOY;KKgttoJtbpw>@D{T()w)M)YdbO&ytOp8z8-I! zRw;`*Ce^a!M#%5`>gX;*u%7k#mMEYo-*rB*;rh@{w$=I?zSMAqpC`&*8Mf?teic1< zns{j93_LJfyv)ZB%JaEecqT=FYt24Ycle7UT+792VD}x8&8hVg@4VTET{+6$j`Cti zhC7^lIz8TcPP>|<=;}4FfzhMZb33|_P(1C*mV(?`$RaqmG3GKf!`xzZ5U;oWw2BEHb3Ij>|mhFUhU=b0OhWQR%)nn)B`g( zzJI7B+(BCwE}5O#P2)vasj9;3(5w<4tv!d!yZD4lb&sM4c`2gF{oeyF9_GqC9qKT` z{w2AS?ERDNEP@Zd*z@#>i-ORRl4X-nZVPLvLOf+L3ukRf(A>HZMU<_bk+gE1x^lv} zQE_7%pGI%eFoRC8XWB)SrgIQgRhBXg5!w2d|? zXR;*wN=cQksLVeKAjonH=-lkO^bXuHRH%b;^Hs0i%;`JaE@m)V??!^|>-6+jgfehaC$h4EDJOo|!3pwS=9rMi8U~w}|pM)e-uPI$g@}Sl?l?4;TQ}(Bp-!c_L z*J_mRzW`?j0dbt=5*y_i%ebkA>azDHKZUlr(j@6tlZa_E)~So@$_%+z89=HDbnc zg~T3h*3X);g$dujY>#%?RXP0$&04$$+bI6op~y-!rRi)5M>nQM-Yqu>c$d4bLn{r5 zGGGk;aQ3SXk4?YdJI3H z{w=iZ;h{H{OipC~H)(KcpCsN;LSaNnwyvkzF-H@-WcMF5o7DQ20~?F~Tr4t`wV+bB zK0h!52Wd#HzZFxiQI#aYpkw5;fY*`W-78e9^06gGE*A2@IZaw@zgpYYQ literal 4719 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 CR^w^_ diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_dark.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_dark.png index d976fab03b303666cd79c6c84e3546ad25bf3997..4bd5f0d9cc387c8dc5df5e908f4d4e3042179cec 100644 GIT binary patch literal 6313 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 literal 6755 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 diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_light.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_light.png index 78f6a931bcd0090be60f50e4cd28dead2d261f51..53f35b1ba942d4fb564dd4337bf5c5f81c12d55c 100644 GIT binary patch literal 5919 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 H>|g#Htzxb4 literal 6584 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 diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_dark.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_dark.png index 2df009ca562878b010d9079f3baef4d8481ca949..60900dc5c2acd38338dd5f7df8875a89dbed2d6c 100644 GIT binary patch literal 5747 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 Co2-8T literal 5927 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 SG;M)SH%LPT0WXD_2mKcjZVu!C diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_light.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_light.png index 9d2b0f23e6900ec81469d6a11c38476a8d1b6803..fba3d2d9a90cfc6af85a9bb72d519ff5788ee2d4 100644 GIT binary patch literal 5339 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 VlDBgOzD|O!T)KuVHn{!ZKL7w~Dyjeg literal 5795 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 diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart index b644f5ae1b..8ce4bb198b 100644 --- a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart @@ -3,7 +3,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat_flutter/src/misc/audio_waveform.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import '../../utils/finders.dart'; @@ -692,6 +691,7 @@ Widget _wrapWithStreamChatApp( Brightness? brightness, }) { return MaterialApp( + theme: ThemeData(brightness: brightness), debugShowCheckedModeBanner: false, home: Portal( child: StreamChatTheme( diff --git a/packages/stream_chat_flutter/test/src/misc/audio_waveform_test.dart b/packages/stream_chat_flutter/test/src/misc/audio_waveform_test.dart index d4eca37902..f9663694f9 100644 --- a/packages/stream_chat_flutter/test/src/misc/audio_waveform_test.dart +++ b/packages/stream_chat_flutter/test/src/misc/audio_waveform_test.dart @@ -2,7 +2,6 @@ import 'package:alchemist/alchemist.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat_flutter/src/misc/audio_waveform.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import '../mocks.dart'; @@ -215,6 +214,7 @@ Widget _wrapWithMaterialApp( Brightness? brightness, }) { return MaterialApp( + theme: ThemeData(brightness: brightness), debugShowCheckedModeBanner: false, home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_custom_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_custom_dark.png index d79ed091b9f4b34fe273ff41f1380117d20f0707..2cf5a7f5e10ef81cc412a434de0f7bbafb64468c 100644 GIT binary patch delta 3696 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 delta 3458 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 diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_custom_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_custom_light.png index 2a80d9629c367b85dfc40f3c28d74ecc0947f478..6468cbef90ad348cafd894d3441b981fd37e9744 100644 GIT binary patch delta 3601 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 diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_dark.png index bb548e48e189e1a8c5eed706f5fbd3f01bd12d2e..9e7ff7e561178d416bf08d867df203e3363a0cc7 100644 GIT binary patch delta 2883 zcmai0`#;nBA0L+7lUs|@EVLH7Fi8lxhvTvsjZL{;)i^ykf|zgh2zF6y^KZ2u7qX~mc*O3=r~a%!3n3-}s> zuklD3)49R)nIKSmL7GKzT0fUSCC|2K*YkoZIV7RlVLu^ygLC!5+SmRgena~3u#5|YHjcAEccn1nr4a0nKtw)kiSWIS)VG#B0Ob{+O*bepe=g!9dUWAMP$0WY9=xBXVRM&}6 zQu}cbRDd_5S21e$S9Eae#8wDA$qc6CUmC7p~8ylNJ1lmK8t}wpyr>%`3Rgi1DtE(sFvCFy!Gy<&Y+dF)=QU@N*I_Ioz z{rC~%G3xB3nQM%A;(O<9p;9pMyq1=?kXIwa^YyD&AhF}x;FTo93(p3Lmv}+0wT>4+ z^xMzB?w^Zy`!GF?Z48WvAj$Q*+YN6?W_c(bJPBO0*|C<&g=CjtZv>h{3~1!0lre|) zmE}`!;qf&}J56s^9(}VfwazasEv@k<5Tt;=wV7CP$1=wHt&8ha$m(9q#6J6Ssefkb z)HK~FKZY#2{4W*+rh7O|bVMAHl$4|j?QbngPIX3-4<3?-=8_l|dWo}IA|fJ#aZcIU z+2dc5L)#}axN6z`QZXDAJWUQyYag0P>V}2q1?FBUKe`gyZdO&%GKrsL>*UZE2{E%R z&IzhdEYLS@_>HSr)^s$e2qzVYQ0B%526!l}Q_ce_s;VHyrm}D!-+s3 zKqVe}dzxC+)nL0G0UhN76%4??%+LFs^gvojiSUy)mN;iH9e1VAVmqsJjqqD_xIo3K_%09h( z3?L4T!JxKuD69m1DD>OBG3@YUVW+RcungDEPWJY~2ZiL6l>FM-+8?@en|I#atdATg zHHm_@x3|~ldL=>nfNuaKEbn{Yy2U3aCkM~&7l<=_nwR%>WCYDn81^=@({>)v5bc`A z1{N{+yQWjQT#v~YZ47<321d;>U~6J@)Bu*QUyicddT!#>Z=s&E8hTmmgdS$tHb7JE zrR&*$dl+)Py3HojWX-|3R)&0lR9IMubQB0ZC`B7jp;D2GndZiaw+mcK@#MSe_uq8oQ_U8pTaIJ zEyR`9CLM>f_8(z1wgq#A?nXrXWR#bEaeL2X`rgXtU)MR?r&^&VDEJnd%YezISf~je z`e&u$xKW{PuTuWAapDV{sHmtLkw~4nf(kjmMSxaX0m4un)krtPxw{*o^7^@T9UY|V zzRT|chkA_h^}2Ah8)jdQs%vQ})8f9eL?NUBHJD%VJ`>e;Pp{VJ(PNnr(5H>ro|ce# zvG{}pTe#FZxBp0bSNF~OPjkJQ>pOez+%JT0E!f@=@tu1-H8$qkf0?65Va@LC{LIL| zr#m_|WzvetvPAhidfrveJn!yO)=OeC#lQw>gwm#uu3e-*_;*eG$n@n|2Dr!{Bu?&L zXI=U8X^~wD%)xrN&adywN5b&tLkM>QI@&IaD3(FgF$HaS0Nq~?1A*j5tH z&?-ZyLahaVJid>qnfvhZAI8Uz5?NVUs0QZKBKBfipeY<$ zS4S)oWP#<9aq!pS98-IG4h49IwMv)ze@DO@UH8RPdz4bmG|91TD1@7bhjE~~(VLs+ zdrxMhq=}{Xb49`&(0~)EWoPQZMiXyVl;f(as-Wp+-l2lVg*Kg2rzM1ig+JYy3L<6O zykn##4-L!#3*`0ob{itw#y=py{Oc;aeYK47{(UlVp|KR`%Xo>zN@Q6mF+7c9pimjI zx$EUY3k|aX0K|-Eq7G}A**6_Q%@W;lFGdd+93#2INl|8q7<#)cH4r{{IZRekGV4E%6dD R{uu-ub7O0x8be&n{{ZBYhMxcc delta 3135 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 Z;eT8XRQ7`19?}2+002ovPDHLkV1o3>>j3}& diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_dark.png index ae3ce8928f009093096b56d9c393600b077a7494..dfecb4229c12697748df5c200fe1f98860591cc5 100644 GIT binary patch literal 1123 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!jX2nVymGdX6biivo3NKEu>admZf>?pawd&RXx z)-n82V9Rq^7A4i%2_CLtlSIGxPMlgSn=|?8rmeR&L~cF)>gPdjzslu&?Y|6W_N`>S z(a|As;NwkZ0l}Z!MIw}vcvz=(NH{mDDjf?*=-&-DG={VUqRybLH>wqMdVW4Gk0j@96hh8dMt+xm5Z{ zlHs&z(->~ty{kI=Y*BeRd&A|IOIEMu-hZFp!9+?`uD?F*MAr%N6vndPU}0-(>x#W` zmtLAgM@Q$IwL2*?H8QBHtFtsa=H=zt)f`azRBFkPmY)8xYVVVxo$+m=2aX+Mb9Z;& zvVFU;vGM1Lk3|H3s&8;}b7Q!1`*wACdyX0Rffp}c%$J+p;n5n^@bF@id#-C<(nb%fxzFCb;c0mGEUl`#diAqM z|NhNOG4k9P!LnfccJcn>!KUKBfB%+|m34f1C(%M?*|u$;j^A-w=wLK+Nq`2!gYUmz zzIgE<{rK_YZ{EFQ3W$$C|KNeb*RNkiZO@-Sza(hor=O?Jo%7qZdpFSJKY!-DeVfam zb6QEL^TLfA5vJ>b@fQ;l!*Jo!B`1IX^RtU5dZ?VbU0YjQ^ZVP|_4B%~T?+$-0-yU+ zuSL6eTTeQfQfV#2_qYsbmeS;t1w}&|al^Xbma zcl-B=-QKp^Zq2e^v+soqM>NRxAD=mAPD*NO>*dRr@4vfn?b@*$HzZDD zj*jL_hXTCX54^8@8yO$}|8lah;K~HXZRejS7Z(dJzwD_tIkC8S^DBevn{iTsLaUd`1h~w$HPa@TU%Mz yty^cO9pU<70f${hIk-ZEW(+v7IwZdmdr^I@f5%48oqIb!mclA8xIA9M{IfFD!6f*gy_^Si&SPshTM29vovS!(`}#M zl$3wSe6A$e_xJht;_o(p^!`0RZ+ZXo^B5a-#wQb&FsPjW#-#Fcf4xz}q^86TDy)YS zJh|EqP7>j1R@IbHWzC;9B`CO@QS_x2gPV~v58od*GwZlFUp<534ho0m)-wD08ozvX z$0hu^nv`^UQ1tzPKUY03X>!fmpmizK=KCAtEnDtP+5i26a=%z{>?9LUpeC)UT}QKS zZd$q|i1oIbr|aYXcOZ-F(QIb(4l>}Gwj@D9$Mc{8Hm5EB%F%XQ?ma_7Qc}~t^76t5 zt7{ozDguR9zmN0IjEu~wtElM6)wex=F_(}np(S)jCaQL?VVq9;<)@*W`krm z)9mZE2exEBKl;pXUj3ihZ|}~wEPQ0aa6R_^p-I{K3KKn!JmZtQaem%iYX%K%?Zvf+ z-L$kW3Q)4B>ILyyn~P7-h!qxBX;leg7S!!c3n_`x2F}UVVN%ZPVk& zZ)%pe%kTTo*Ko7=*`=!6ubQeirWU8y|Eszhn?1vHccs^R*Ux)?uF2RtIYdizwzb&@ z>-9O+hV^&1rOEdCiXTrqclCU<;=B!AsuLBa&zAZhIom_!0Z*K_i0kBgYZh8v-h1Jt zr^v78&v$+nFZ}Uf-;KSM++v^4F8q5mX12;vpWgMBp3hl8NeeB4>oF4_vI-4}H5#Wb z-M&42|GT$umAf__jhU|E8}xy(PI+eHycZQ+E=HbavzI`?t^M|!G zHJ2`1*ZAd&SCOT}NghLs585}^EZ6Z&_dl56dFlT|`(57fq^^wOpdaiVp%z`5hl-dO OfWXt$&t;ucLK6TvHNq$W diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_light.png index 8397663d6ddb2f30eeebfeeb795a56b914eeb822..9da0382e309d5f5ee7291714002ab7439a178af2 100644 GIT binary patch literal 944 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!jX2nVV2<*1aSW-L^Y-q2@91=y;~&qT>=aZ@*buzs1&?2hf`TKT=9{F}wbA7p zPbS#kkl%3nuj%eP_8Ynna;2s&wGU(4tD)!-VALFuZ7@5jCzyj}@y9nw(GOmF#x68@ zdwp+wx%i(0qtFx=dNwh6wIM8robsPhOjeZmB0Uj#go{IJQc{zEn+h>=D=F50Z zI;OYu%hToZuhxDE*?*m#A@<&7d8fkX2bJ&c`09QB;d#5t`0MN9US7Y&aNyw{)taEc ze`ju-v_!ss*ZSkpTkFz#)aHU8GTe|S1hnBmKln8T~CmHq$Bw$=Wd zisxKE_kHiZC-wF9ZP~PGk>B#iXZZN|wC0~Lf37vPtE~L){D+nE($dnV_$^oUTpQ+n z`t)flOUuG%Y;0^>cI;TOaG|2-rEAxwt-88u-oJ>Wot>PiMl;_@akn=0_4m(}y#Iac zh7AiQP89UKbnaZ=wd>cF?`u!>(wu&}w7>i4qFHB`84GtGopLh8#LiC7)5^jkU}sF< zo1-5-Y-myl(E0ZLeMiyGHC21%Zg$_i8R_fq?>z~q#cSy#KY#zZJ6)6(DsW8kP*L%e zmX=<1by51h;Naj{vu96M*?K!ywA&R(rmM;IYfnF|In&2$(zfl}x9;32d9!=tM#I$2 zk)A;kZCWB+SzDtf)}Bq9Db zFJA3DJWsv0=I`Ch~{ty;p{B_F||A+raRk^vHi9CWGb#MylY)`o?iy VPt+I7o(5()22WQ%mvv4FO#nq5w7dWS delta 925 zcmdnMet~_0V||sUi(^Q|oVR!LGp>}%9QgP>|9g7*@ng@nIq@D@v*N%Z4IQqD9f4ok zdpfQ*2MVQb<+^;T-B#SFGc2;>*aj(4$-fIF1eIJp1Fr=9PFGr@^!xO&yxq6ouTwYr z{j`@W@pbn5>xUn2{`i#p`?K=nKXg`wtqxVuWGMYy%i#HIInT5u^$8L>o(BygCN(8) zP+>is;K}8->soiAjIn{mA(uj-1}8iB=xpPaAzACrt9u$RimGof{d0sNWMQhKFoW~q z#R3Np72kgLxMvc}=2{gUKc7ptF8P?vR-AqI!lnhCk4`Sv^_-<|+AJEbm14BqPB(3{ z;kgzrFv%t8a44yp|rRDJ~ZF&wsIK-#)p9d)v!4zki)CY#bnIUe12G zhJyzcx9$7DB`kdONX)_s>3{1N^Iy7ki|K%R?fi@1U&{U4buRm9X!wP~>rxEMmMy!n zXX)&T6E7a|sWm&z{Jp&B-z8Osnyv4p@7?^XSSX|ZOfO>F6!&XuSrJIxo)aX;IRJ)`}#BY&hPiR<0|61X4gBZ zy!~(07>*q~URn0qyQjBTy)ImD`r_9Mx9Y@g5ooZj-v6Mv|JRC;Nw+QaqU$ujm)_%5 z7q`i~cj@eb0|`lLvriV}r?XFwsS#xO`#y7Ev+o^UrY^7CrNpr9{hg!#nC<6Y zE&q7;``qntcG-v53lDeB{QvlOu9;op*@>Iq?!W4m z{$caWl3!amTHg4)bNcUhws-M<8LLCBr>FgSn?1>7dd(qxaas>daad!0h2)x$ReeD( zukbAnTIr*edRyh}`dOl(Z4&kFp z0)ZM&GO{!0-JGWB`FUa529=llPv|d$ByJ3=#Qre0F7`{`wAyML0}yz+`njxgN@xNA DX${TI diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_inverted_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_inverted_dark.png index 273cc75f7adb0f677080d048804805ce44749ab4..72b4eb830e44b2cc1b2956c7acf29206b0733ace 100644 GIT binary patch delta 2889 zcma)8`9IWa8y;I^$s`HcawIWH(O45A%b8@I46xYWXXt-5o0-yaG2~_Gsa{~ z)7ba*FpZ^NWS|7!X5o4Ruh}MC=au*|svweZTwY$h{e^sxFExL+J z_P-vr-YU8L0s^IHp{}kR83vsuI5uC<^pHJo>5jZSQyl$lp}?}-0gYzKLUfr-B~XX< zqwmSdMcTwN z;pAjaTk#W=k&(fUJHDF4tjNh`);-ImD&;%?Y|Ld7#cf)G>|Z|)=y+GM?4=|pFCPPF zh(St*&O@X!$riS@y5gyFg^BfcDCBkIy=c;3MfLPB^$v$SRWJ^S5id)c#5Iu=E9nnW z8XC3JOWXb*4T*Cped=^ybaTRc=<#W@m*-qZa@(7ESUNQcA`LI?5aKx^kDk&&hFXydB@pP&tP2 zQkov!%`tQ$5MIW#AykwfW@Tp&x?zl@hO1SqV(s4_@?=q!I5{~#`3nekM3 z=vUhMV+IC{zf&mp5+eVW3ff+LQzS1yP+#y3v9zR1S>b1%c-x??SB75M$z>g7|JSMByANuBdvVnP?D99C#vO8;)t1+~;9^tIIU z6hw_huz`8qPAY;fh%%`s2tN1USPLP>tam+^6n_LK@+V)C!CO6uv! zQDfk5k%+UC{d#H4qe5w2>27aGWP|5iy+6gn1oNi~qD1H*CMG8P+PMg#C14s1Y~d`|tgSShT724S!N|_z^tfXiX8) z_VcIoEFtu1Wh5GvqwIUm)YLR+>&JN!wV+5*;P_&ZNskda#EPocH>Fi&83n=+yWJgu z(-x0<^M>o;*dVSzj+2QvH}|wQ3i&=S4@rS5!r`3z!o;~sRp;Qm?!&|8n|qV7@=m>C zya4;ils^E7g0;ir@o^Q@-4OrGWVJrr4GWvfD-Vu0m>_l_mw-9jwx_6v9Yg}Bbk-u1 z^Fu>JK?fVxP7kQuEn)OQFrL&w!p5WRAhnGU!fvuPj4zc;s)Zg@t-Fo()Hyy3!5Y)hToCVEg`$?^Inaup`}vnK21WD zijahijLQfFLIHMuo8IE$jR+{wtdf+JtZHhS*mjx?vq z!()B~&*val#!lOcPc^rUC$ed|p-pkIz;{ugR9iN@;>U(hJws_wnQ7SQLZ#8QX zjyK-nc_NcxFej7|9V1Nj@Sy!t2DUEiRJT0K#A4m9j`qWhn@#sbom!f_py-JG^=HRh zUfsocwvs@AsQSnw!LSi1!PkE$AS4R~lqR3l z)}5T3R7eu|*jYZ9+@!P_W#m4vL_9r|@tA2ge?l3U zWSO3x?s#e&9^0KF+JOEbp`UH4DRU{FQfyhCU2i=)RcFpiOW#rLqkS@t>G8~K)6r&;iUDr$-0Z2dHY^^{?T>#cXsIvc5G%Qcq`8R+szsV;dt-4*!ZqdXO{ z23TOxl=rgucE&Bb8Xq5@+}*n-a}jg1v-QUxIAye)w2eFRfzyfpZb#I5NJQby=4UFp{vAjzTnANuZPO(?9o6jWePodJ`ppPuxGP%K{-&Y-08+>}K99}jH$wHhfGSS~Q-PJzQOMBJX zsY7`(a;cmO>3=Xk)^Q_chB%<75YLQRR?RUiaaR;eSp1);9PTk=*3^RDXa7H>r-TKw c=6Kv0gQk|_l!~k7O9*)MbquvDwCtk)14VwLQvd(} delta 3204 zcmV-~414qJ7Nr@GKz|HRNklSx{To8OQ&UKwyc*2D2j}fdH`?@De+Y7ZQ_X zlGO3ksnaHtwwXR;rZau$^r26kbehg|I!&jOnIvw;9k)*HHf_?xabg@75D58N17KK6)4N*j6R5Vi3GN4c<005kh zS=en>xZTTq`$wTrD1L}Dtkm>OsMR@m@7=fX-n(x>rOH8SdM5wgQ79D3eGq3@3S|O* z|GVF!s_L-c_J7l#{hUv~D3p+imdlZrmH~-G0-w(Zo6U-4mrH23C>%f(ESXFeAXe4Y zMn9LMD^>Z61#YU(}q3v8PK=-zXbr)2mQy>=qOh7C{*o4**zR zcA?kUgS&U9g?24s$;?tgrPe?yy|-gvAGg8MXM)G$5r5jX2U1bFCqS&Q@&GO^s}x+Y zQq$6*(dPLd{xf%{VKVj5*&howyrNXkU%DWOPi#;qltg=MY%K4-+>c7F!HFOIh+)@T%=qM^e+c4}7~w>)q*5uK zdggiL7k?BXIyxEv;FzCBYfA%~Zq;+qMM$KirZdE9mHfgYG&bDi-KSMF+B_Vrszypm z8UVoO^I>Svf~&Qc1Qk+UzMg2;Xmw%hGdC|Ej~#k~aV@S~zKEHbyJ72>PYU%VM7v6@ zfm|+!i^VJ}*!|YrRF9UX2F`qrL^#RAQs?BNxPN4a|Kb6FGA<7L4?GUJTpm)JDJf}C zsWnikHU5Fj8ei~26dsQ^v^F{7k$ndOzL!KIfjTD_r8_D^YA;$YN46>lDzygLsvJaz zc-ox#@$Kyu7#f)(qJ3;1}(y~h8Msr2w9>E1GDS12c zbOnftx;N10W@lhBc4KL2Nl?GmL)nf>#!OWrkzhypE>SGKq*5s~+B~GKyY@2d zb+t8Uzum&BEu}J!Xjg3EcZ~5WSbqr#iKy852+}hG75|%-Ml38i`E-Rfa=9Gp94+y) zLVZct1go&91p1;<#Khc-aygw2bau4C(q{@we_Wz3B5yQjWMo09jDy25&$~~nXmj&X zQdaJdDlRQ8qNn=~4BefA`NTEB0w}tCJ!`>QFDkVLW#v2llM9#21(UHGoqrv-Icc9) z!P z-A!wzYlf)y@tlH7$f5pP#C!Rt{X@x&mZEFPqg%mEaYf%{R4SwY69k7BizgEDpY8+I!4Y-e0%~996ExIwpLcz z;tS5;%jCs{1y+7>O-xJ-@(YR(pO6RuaJ!db?lod+aw07KWn^Y!NBPbG+ZKfadmlZB zjj$OO=1=wO^W3g-ZK6JFTvic5AXYk*^F(3F@ zsP|jQOTukQ!iJo=M5EQ=_=%SpcC*QV3+MmMs;yw@3Uav|@d=5zyGgR^kH7c|GBUCP zYzGfl`^+6_+At7HLy z$zkz7*!^bc=|s(EAFuhp5f>#~FL814*!$?g0D+jDok3&64b~S~mtRPbY0moHtwu08t#mW0DY1DxeCi5<}j*8Kb&u3ovsNn1ukH}TN1*<{#Mvl64B3r54n zkLoz)=V7&u5cjbRA~?IqCDYSWu#ek_`&cGpH|zGSqrJ{eoHS5 zJ)OkF(QswN3&GOT67KN0OtFp*qy2UZ^u?u&#D7tzgMIE4m+*ZpxMXT_0xiuA#Kh5X z3T~+P8!N&3(;wbo-NF5g3=JSLaVx_v1I~wKb@# zt=Vup*3lt&JRU}c3#@U?H#1*-S<5=Boy6pX9S+Al@u2k3(7=Y<2j6>G^*64;>2xv< zKYz15vhuLtaLl9T^N)##pEpZxm=L&JE_`BOiRIH=JICJ_Wv49^X{9OOv6%6+G z5exWV@3k$H@$zg4=GC5&Kwkn5UXJ3!o*wY0|U$1E2c5JuXh&f$C3*E@@ zAWpvhCekyq0^ZTMxF~F;j0rpcsWX_q zZ!x3aViwc~5++kO3iQPcyKx;^$f0638DKUE`+I~Nld&5WJNGc`{T4IpnSXGk-fw&m zaeG)?^TD~l(pS~9z|h@^x!GCbtLiDCGc?$bbMK!bzN((Gem1>eEiNvgy{%PfH$=tg z$Ph+HhJ4!?T+H+_&urEt65c>ULJ!0DLd-#ZE=1jHPGpsbAq0Hc0NR8%xl z(lVipi-*q#I32UF+eYDb(^(dULJMKn;s qqEINJfeIFdLJwc|Gsv`FuXF_w#wbU#s*U%CRKLMmpG9 zxe)K)(o;bnZD+Hl%vEPH55 zZXOE#rO7My(UjYNd{5;cYITOpnaW)wsQN3b%Gv1qxvc&TPLY$d84&2PW{OjpzwX5i z#FWOQgc=`}2TMyN$v><^Bv@*Yz%9wr%88aVd0QH=i_=I#YkRDr z!}I5NEH!+paCfqU{@J&@upn88ksJ*@1OgCULKF-Jt3}{Q!HgF#65##8r4xQfB-L1n z)YLOcIT#qXM-~J@TbY$KU#2-$D18y#>hDNPlUYTbYda%;ug#~OuqHMi&8S;hT?L;j zMIED5Bx*UlpPs%%miogAuB+?em#0{yKy4ifZA#>Rv1o+*2L+wII1r^aHZ}%t=*#*` z6Psx#E+s{hdL8)e$-89`sGvWY!T9wqCD`tuxvLJ@&2LmIuMZpQWl?q(1wC8kCI)hW z?8^^O%(T-zG!%}g@hBbSe;W(bD`0Xsxz+kmi(7Zs*48?jezVtd7=jlBsB925`3wE|36Xi5=41 z9QVx|hz~ej=7Z(=?e?^;>eD-?4qi8#ZrNR3RBO)S*)o1Z- zyel0yY|bornNI%f?@p+r60k(?dlc;#EiGb9gOnxi)RcP~glTa1m7jV5-(XERc!s2^ zBe=s8XA>2)+M`Ug_4M>;t*u;`7z91D`~6!!(U9o>*t>pid;4f!ULKWRILL3H(<_%U zf{oM1$32F*!~P%ru&?}FV?YItCmPA(=_xh{fxkMsJHT zFx|to(TscH2yWxxryDk42@KleAAw7ix)rffPGZCHX=7brSDp!Zl*n0LgEjr3rp%xe129} zuMw+igo}d~_$0nmh~v4BJQ%MY6yM}IiaGsyKKlK{gr?{>^YiuZ*1pU;jS33cfiD|t zE389d#JQ%cbc*Rw`4xenrmf+!o>Yo2My4cKcI?5H%I-F=GpDcp;K7%JvkIMi@=(2w zgXBk#j^D99_JS8-levh?bh>|S|H~yww5zArfPjUwbtTy%)evq4rKuEc*=WY3eVjON zb$cU>OjkLODxY0)S+DZ9kP{Bwlas}#zD^{Y;-@r9oax)6i`O-e$oWM@aa_!kt*ve@ zYCD`KNi3)G&&=RSdku9f#<{&^yE}((eV;eFwL5Mn|04du{mk;A3H}UNQ^yaId zKmK$1gu6iaX>+f)o#BDPf_2Lb0`Cvk66GN#}0|QxaI)G@|Y@)w1I&EHz$cnXV{~lXX~>n>6G`gU3gPt@vWPkVdXAe zBO`xSh;TQkd2ctBCgVvPOJp5i_x$-WSGp_pg5!m}=5gf04(%joN3qso!rI_~h%J4i zlFgJIlyqrBEo~**6C_`-s~U$kvTZe8Am|tedzZOr2Cc2_o}13-MEhfH+4#;f!X=xY zp&=z8l!^R17G;CaQLxrw820M-&qh0frVbxh$m*+Sx*rfJq-zyE3|@roBi9Q{N)lGW zx2Fe%$LGn?Ngyk*MGLf`e_#E(8Wq;|$a!5rFuDxx1*+8)bbKYYW3OB>>1&zM0!s%& z!v%pW&t=Bij&z$|y`6>c+;fdZWKu(Zun)eYndDg)C_1fTG=n^#s;XMVt+nI)Mp%kO z+AUN=KqRb}m+D9Njwxm?MZ5nS&<%LaA@U^$95?%SvkURnxYVG|iJpli@txJ3lPl_K z-7}FNW@gQIzP{_c({)Z9q?FcUsen2lR{t*S&CuOBq=eW{>>iHmm$71Jz9lQ+-7;Dm zGK5iUGRwxlz3mHwW=qAy#1y4H`VN`&=08}lAXR-R>s9X*S#R332Ua|T*DT6<#znpl z4=&pE(Q}|<&YcU(zW~{A=q;geo}BUpl?_T2QB4Gtp=F_KAn^Yp-CLD`OZ5tzV(5dl zzgoQQ$r6d;fJPPqjfXvnVqC_0cyLfVC>@5cJD|I?mX_+ct?^Jid%#$I%rmu@7d z;6?)&*+8QiyoUYGoQX`BL$ZZAff9`yG@-m0YH)6S-R zzF?702=HjG|Aor2FZmfg9WIINXOw^c0GnX*Il_^h(&a zfH=)G$XB?Y%1kUQRB^(+C21<91$*b-5=@yped@+tWW71fusy3Q1*4WOu>S-$U8R z$f&4Zv=lpZugH>zhM-46O-5_#h&<>`q*^l%cL=Qr=yi3+k(8B)B5G=c%)iQRe^vcF z)1Vil2nYO5TU)r@MQXQ!915Dfz`aB$rT0)_fTfv30*NN^A|7yNAUd~BR86!N(BK(ZIQ7SL(Yj6|S703UpwI#oV-?IF`?}u#54`PhL!PtgE zG6^IBhY*sX876I#CZ$Z%%+PO4+E4w^cG^xKnKaF$G^I4jq?r(s03iub3^vB{`~ARg zY-1a2Y)kf9{a~Xa_O8*&E3MYKzi&Po-+Oem``>%-Ip;28wSQWzpin3j1)!4K0Sbje zaTF?86beN#P{E>5D2jm!7KK7l3{WF6oVJZ$Z;G-Z%$x(W)=Y8=j(&8 z&>*lZ>u&!j6bi)#L(0P%8Xg6#KuB;P0AOtV7Qg^{U6>p`QYaJ(AT-08nVv;Vv>x8x ziywys2cm!QhJU+#qfnf}Y_XuPZ@~6I{DV#vz1-h05lar(&wD z$3Oo0AfRMZ^YJ;B3b+bfR@&F ze0u6M2AB2*RVo#dlj2eK)J|E2w7#L4Xm4(5cUhlJ&8_(Ci?h73*t_QmgoFgUtY3Fh zZK@;M+dH~oHk+Z*XdG@!=zc3HE<|xrp=3ULB7c(PVYRh)q3T+VZSVl#_T(f!|MDEn zX0uam4i4TxM`t%WI=gKbvRG6MBm9|4-V}|mzP@1hy%rW0(Aw6CE0x!rYR_!3*v6}) zvm54nehX0uoZT_&~8`tS+=Vodu75P@;zg`?e}s% z`}{0kefc1g660m_OK6!gb%(qG?JoKUhVYx;{h4Pkuc*S|*M2CAPht=nKWCSP?&i}| zr*YxpWuCok_f8aU%oo*`(7ks@Ab-f=D%>hqY6%Ko9Zw-ySO(I5a5seymd%7q`0I%DKXWMH=28U`!P8=1+6x~ z?LOV5sksf6Rn@krqK}UT>vFTOE+;^s&8n?~^Q8Zka`3s@@O%FHl87w(z@`JUc> zT)uME&Ng-D4$huCkAF=C>qT8de!jwE7Z4yk=0do8ETm+3WEAHwl-t>6W@d2yLOC`S ztQYkiv|8b@Tcg$5{q5}i=Jb+qc;u$D|LSoxx3uH!za8V*jTr_Ud||(+wg!iUL(bz9 zw-B~UN`l^c=NNi=`s{2cPo2T*hkqo=lk|QD2Fa-p9LM42*nhaJ54}JBUL3*B$IIYu0KZtt@H>v2&+N4b4_5@){p7B`0fp92;g9E7bU zn-QT47u8mMLle>7)Y2w83)z#Yscl#cPcMb)RVo#-jOi%cm@g{KJvlXn-lb6a{YW+S zR|>-;qk>{u-+#~~dKJ=ry%*;%mfMvW#l^;8TWOIZQb8O+C>Ybx(fz>xRjgp0`}QI( zU9RNWCnu*w*JcTALUJ4jZ;?wvPi1QA4&MLQN4&k8w)RfML`R9rZ?jshf*ZQ4C9&60 zmaC>%SP&mvIPt-McnPx|o!!uC16KXs+HxsPccDile}6}iJ2FS1NP%K!SX+xXgPZ4w zOF{}Ctu_G9KU;>1tJiGDo2XPOWTvMfc5SrW?opQ5xA!UDk}xqLPO>~ErK4EETC+xr zvfVo+X)8M`lUQ_Y%rFQRttye6>`dfjXRf%NHCioVV`F%m?u&hRe(j=^goTA7DpF6} z$I8yi5P#iXeB4^Z$E}sfJI^FHCyQ9vo}2UFYsYE9(uJ>{v!o1Z$vFJumxy6ju?Q8O zYBU-I2L-O!S5M*P7Z12KlphsI-pFTJ7NMc?9zpZkD+d*u)}Ytx1V0OR(LKJB`%Sc9 z-Ax?LTbJXUw;oFD+Gt6ZMuxOxAr50><9xe}g~L+g<>mFj zToOk~NlpaAFuVL}}SlzW1JG1Gu^-(29?V`F1vFULwZq<<*l#EX@XWlSd?FXPJVH7POPr7I@IMhWHM zg#-IM@&G?iB`!7wN8UI@Jiw3QdUidw18FHq!~^`4ipL37W_lW;qauk%BpWluNA09U zqgEsDAa=VAz7#zHT>7}TJpRX?> zBc(s5T2Ay4I&^gMYo{WY2{Q|kn{7n8Ayu(&JC2Id6LY%cP76qgkHs&J9zjoUzb)_) zsgHo4pRcTb=_7vX6x%g2l2dp4+zv*e6A6(h(UM0bO_)ub7l!7}=MXX@9B7NJ~wY z)dvzrqXDMcdY(O3f(&`VJ~Iq3I+3KSByzGd@!#?ao;@u!S@cZUQ|~u-;Bk9cQgi6l zAJA9Tiy$}Kh@ikg;;ZT@&XbfFkJk^sLVQ&{#c@`>VEOs^Vq?L2x!q6<48tHHJ{Ae_ zv2wfO6g#$;5Cag!TjYN&*MH)<<}{+b5{QDuvMN|Cb5JiGX>75~fn`@u5in)-qVxa+ zWqBAY;h?0U;ZeW}goFeFR$y%W7Qg^{o#z*GQYe(w3sJBZUok(ilvcpN&(8;8AwjFE zc$Y$Nf6 zi7OK8vhD~+phzSV{hX~u-La6>%;;?PidE6^_q#aG`bJ90-#TkLBL}%W84zPQ=9jVz zR}k=Sw(XJ=AmYmiQKoRroDom5OkztkFVYzPfbRq?X%* zQCJTM^v@klhga-{$_f9=x_WvHCev-ZQ&H(ryR=pjK`1pfHPmtd>Wjr#(;1BD@^V>F zRL|$GzVu%+El8NNdYVS(5Q6onWQ?5b;PiJ-amey;35&0ewiGk6Ts^aJ1{zC=()1f% zA6e)mq+?M^)fH~Qd2jaIX((@Pm3)JRL7~u3+O?nF+-WEvymqVxKA)1BDwLFcM^j%v zg)lkk7%Y|0+^q9~6=K}EFgp4yN3n3|jve2m-L|OQrU58wXD94+ZS7NyFmG>p2n13# z#reGXy}qdl%g{zEDhLwCR?2K0!jjZru^kai$KXXNVYs#<|nOkcl2Ug&TQvm#2IurHWdz3D084ofw=F^QPHyS}CdP>A9ly1{2JlN87*rQp(xhFC29eD6z@gb^~Uifma83j!#IS z`3rtwXPS7(#>dD1Wc0pn>i6#t!`tU$`o6NqIoqPX^lY!w33Sf3nnWrA1R~j=OzySc z3Xk`qPfkLrCseN)yH**#sdBtg*3i)4Z_NX2X!rw3&SUZUj{jw(YY0tJ2kPM@}prcjJKw3c=cxSPw@?Wn%a zS^4=0en%l=`&hvo7tBOtZ+d=z)1iJKXchsZXUdNWdhpKO#l-+f+u&&m)%Lfc!vEik^ z4)@-JJdM9~rB2}*U4XT@v~=* zot&JUssH$Aj3jx$2sMt$fIOwHJZ}3k{%)oU^r<@z3W@?8`>ZoqZKN}FN${daM+)Gf zD}*0it6@EXTHZLEfM6VN5{4IGaCUaKjRFJ7WeQYOR%Y5%s_W_wj*K}uI@Z_Mg8*S> zDPK2v=^&S5L0Jl=tnK#T$Dk1lL&Z*xmN<>n&WB6e$E?LRFyGTSXH z)*~tPnN`!59l15%1wGeYisMnHQq*IP*9NY z+CpcZgGUYEK_n6hBuzYEAtM=r3|V170L)9rq1&#%&TV#q5kp=u;QzJCJ#_U%Yr93WkcXp->4q7<(@$>R@Q0CAgF*>`9QKv*chuxw!Kb!Dk2Kfi0N z^18a38YsCphS1wE;@daoAh8hBeZ1 zW9o{pMC`z(YTa3RUw!_gAKo=HBY~f9OA@!Q=)6%`f1KMo(Jq@;k(CYP?wwJN)=##Wu`BpeUB9J-AJ1_rKArU6MkqifQ<$7S&Y zT_AvcPCM+ah3ea1D8 zQ$EVqVW-$8x+S%X6>(DI7B`GgxIVi@I6+cU^032iz~`+`tYV3cyg00zv+cM&i&LFY zM)Du%B^_C7;K@b8QA>z_;)3aj^@Af*u~?|$y*L0X_g{mupMU7(k<#+9mHO6JR|X_7 zA|iq~4S7YXgEu=0onw36suPPg0;%S_dJ&@MlwSNmH;)zv2vb2M>Dh0pvV`}^w{7E1 z5k7u?m5ZLio|4nEvpqm3>3tvVzj7V}Pyx(qk*f_5pjfo1B$D(t43T5$AxmW7oShq| zI7{T$p{;YG{HVeD#8u-$Km{6X2o)1A`TVi53LGqu6gns~=IAiq|;1OPsiQzL)+;O{R2Abw4Lc>rZbtesna-qzCV5Dd+Q(p5}5nVmZ3Yc~>F;PJa+WNEi?YI{-om@fRg5 zLP*%4ghdDm8#hd3}!<*+{gHo9bMS5o7qa%cnH9U^6Wb$~t@`vB!=+UQ`=YIan zUj;HQLUFaZH^O$6XmDk2+!rE+nY}AECF4y@5YP4#ac{K8w`U zbab?9aJ%;UhWeo?Az_uAk_w4LvZ1dPi^Vunc^q+Z@kmS0L|OR(uk9TU*U^;I>H83o z&BVt>nX?_ci%Jk9l_FoIhDu$?xeuq);d1S8IDc5%u8NM1M*OyffW*hf=@zF%?E7aM zzbygL(Y~kHdrHcXlbZ*rOosgj4nZCpyP+6qghf31-q{}^D?0~^HXA3+dB1tKCNp)M?fy08clk&(Rh8x<9W!lDvj<@dj5?>^r8_J&4S zWF5KO^&c`eHqLAN!{2xs6;B)n02G&&t4=?Mgv2CZWeRrv+Evbd#7G~Hd}MNdr?Q?TC8yw2 z)fr&rcQ89MgC2c1Z~ark+Tt=YvwuB4_CzX`;bhgfkd>8#zCJy^xcct^MCd>FZTSL~ z8ueZh*4na*+MCySTe#;#MrJmXtN>tpUV+#4Ys&`OTAOj__RRo9x!&sZlesP|@|v(z zI}357@)!W%?wwoEb$5F0pBCM>5KoK7Yu6zneJskV)P=||P$Nbv#qOdKpMMFC4_p?F z=jZ2Ku5C6OYg_lSi`8kdknwS0HoF#V+LxBRwqIK|aNyt(pVM-oD)HUpwwTRc+aEks ziNvI2Bqk-}@R6r|_D>_MErO3l<8@opY3Udp8R9igcS0>1$Hm3FlwBwk=^+&#SMKEv zsj2BOvy=^lQYHS{xD%6-eSfy6gtbM4N+r2SBtlvF0p#WH#K^D#_4n^$X_5V$wO}k7 zbHOFPh{#ABc;YZJvy|xT)uE-S-md~#zHzO@PfSWi#eqYR%4Eh$xFNF=aYt$+CF{dX}jKE@Q7 z0l>6z3K!pb6HccSKm6%0khn5WZ*IJg+M6}V%Fe-ezIzstQBknl?Wn%`DRkYPD6cq( z%A?OfBoe`Fp2wwkFTi3pjE})E*e`gD?zsLfR6cbA`6@L4K$)`xe|`Org2v}f;McH3A~>B+h(sbF2=6XF zQGA5BSh7D}KNI;Zt%|A=hrcUwdWU^Gyx+(A02`HKw&fJ-)fQSzI{(YCz))6+?yr)ZVONri5Cp_g$g?Ct7VM zwk0GoQ^Fy`K(IsdOWmDXEZS_$l+d4hBZWe1SX)G2uMT$IKBqJOq71e2Ry*X4J7ON)!ByHgvW$k+`J4ZvozGPeNvDkYdUPT|@Yp9d&9 z?vw*B&gGwWqpwA% z__#7oO(MTQ&0L#IM%K1=yB&Xj>n~8L3o$W1=JnVgp??{tCXuRGzA=8XfvAI`w+of>S7fM)L-0;vKb8F7vz(cQX7Zxn|?9&ee z6d8BJhJQhn??1>~`+7Z^<_2h*8v+m+ce67lnCIu20#ILX4%5>{eEiWr0u&uP_jNg6 zaqD$m(CfPV?w=CY7I*W@YL}az!`Z_=JyY7z*Q*c6lvPNde|8xqrRB`c&*8v0HHn() zE6B@NVPwdFhkbfp`=^Ap#Z69*Z|J7lwR-g3%zyDRqoc!^nK5Bo5I)4fZntw<(C0$0 z>q1_^PGDuz!q@1H4QyFVUbO{``=p_9vXy9Cg(J~ z4<{Y%4`4D)BPCSVIy-`t^d(H!7oX68U&sb}{W^7n2Q*un4QE z$%zdogVnNuxw%=4JsR0?*?>^-u`vwxqqCzGUoR}6w?_-THZW6MtJUf=Sp_pbHnTG( zzed&Vtt}Yn@56${g2wtfpJf+pXoN)s!=-GXuUC(}{Kt#hhC$&Mwb^X;V8S7TYkwuV zp63dEp$<;eX}eHf@g#Gt)$+GU&sW(6B`gvW_^8AW6`@o~&c*1+5H7v@7P7LH=Or!ts-*na};3;c-!$O{i3ob2Tyk2ovLvf*l4cj|t0eU&8h4 zH*n&_aR9*e>o;Jxdq1L;kWkgu+J6F_w$ragZ4%l%CM=724!?f+w-AZNSXx@b%fI;@ zEao{+O$iALR;x82B^N|kB2S-J6cG`L)U-^g-0v7WuCdNjvhVG%;Y#{U4?uS+%Kc@A;W00000 LNkvXXu0mjfq^rZ0 diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_light.png index dac6d91b585f5ef0d2f625026f00ab8084fb3890..488eca938e9bd2e22bc56b2e15eff23967cb7eee 100644 GIT binary patch delta 2626 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 P00000NkvXXu0mjfhlkGF diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_light.png index fd1176a5914e165b367f1045627acccab84f94b7..ed2f3349496df262e619cc22e0781865a5c22cf3 100644 GIT binary patch delta 2702 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 CG6Tf` delta 3154 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 PP*K9d1Z-Sw2#x<2cQ6rz delta 3202 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 od // Sets callback for the token refresh event. firebaseSubscriptions.add(FirebaseMessaging.instance.onTokenRefresh.listen(_onFirebaseTokenRefresh(client))); - final token = await FirebaseMessaging.instance.getToken(); - debugPrint('[onTokenInit] #firebase; token: $token'); - if (token != null) { - // replace with your push provider, e.g., 'PushProvider.xiaomi' - const pushProvider = PushProvider.firebase; - - // add Token to Stream - await client.addDevice(token, pushProvider); + try { + final token = await FirebaseMessaging.instance.getToken(); + debugPrint('[onTokenInit] #firebase; token: $token'); + if (token != null) { + // replace with your push provider, e.g., 'PushProvider.xiaomi' + const pushProvider = PushProvider.firebase; + + // add Token to Stream + await client.addDevice(token, pushProvider); + } + } catch (e) { + debugPrint('[onTokenInit] #firebase; failed to get token: $e'); } } // User logged out From be8648be4b7f1369e90c157a2d8dc5f6480570cd Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 3 Mar 2026 23:02:34 +0530 Subject: [PATCH 11/33] feat(ui)!: redesign reaction components and introduce `ReactionIconResolver` (#2521) Co-authored-by: xsahil03x <25670178+xsahil03x@users.noreply.github.com> --- melos.yaml | 2 +- migrations/redesign/README.md | 1 + migrations/redesign/reaction_picker.md | 323 ++++++++++++++ .../lib/src/channel/channel_header.dart | 145 +++--- .../lib/src/channel/channel_list_header.dart | 181 ++++---- .../message_input/stream_message_input.dart | 23 +- .../message_list_view/message_list_view.dart | 69 +-- .../message_modal/message_actions_modal.dart | 6 +- .../src/message_widget/message_widget.dart | 35 +- .../message_widget_content.dart | 5 - .../lib/src/misc/reaction_icon.dart | 172 ------- .../lib/src/misc/reaction_icon_resolver.dart | 80 ++++ .../src/misc/staggered_scale_transition.dart | 148 ++++++ .../lib/src/misc/stream_modal.dart | 5 +- .../reactions/desktop_reactions_builder.dart | 35 +- .../detail/reaction_detail_sheet.dart | 183 ++++++++ .../indicator/reaction_indicator.dart | 126 ++---- .../reaction_indicator_bubble_overlay.dart | 14 - .../reaction_indicator_icon_list.dart | 92 ---- .../src/reactions/picker/reaction_picker.dart | 142 +++--- .../reaction_picker_bubble_overlay.dart | 18 +- .../picker/reaction_picker_icon_list.dart | 266 ----------- .../lib/src/reactions/reaction_bubble.dart | 309 ------------- .../reactions/reaction_bubble_overlay.dart | 298 +------------ .../lib/src/reactions/user_reactions.dart | 51 +-- .../lib/src/stream_chat.dart | 55 ++- .../lib/src/stream_chat_configuration.dart | 17 +- .../lib/stream_chat_flutter.dart | 16 +- packages/stream_chat_flutter/pubspec.yaml | 2 +- .../ci/stream_message_actions_modal_dark.png | Bin 4823 -> 4802 bytes .../ci/stream_message_actions_modal_light.png | Bin 5218 -> 5575 bytes ...am_message_actions_modal_reversed_dark.png | Bin 4837 -> 4812 bytes ...m_message_actions_modal_reversed_light.png | Bin 5191 -> 5522 bytes ...ons_modal_reversed_with_reactions_dark.png | Bin 8230 -> 7291 bytes ...ns_modal_reversed_with_reactions_light.png | Bin 8867 -> 9814 bytes ...sage_actions_modal_with_reactions_dark.png | Bin 8174 -> 7296 bytes ...age_actions_modal_with_reactions_light.png | Bin 8808 -> 9908 bytes .../stream_message_reactions_modal_dark.png | Bin 10510 -> 9208 bytes .../stream_message_reactions_modal_light.png | Bin 11106 -> 11490 bytes ..._message_reactions_modal_reversed_dark.png | Bin 10586 -> 9259 bytes ...message_reactions_modal_reversed_light.png | Bin 11117 -> 11397 bytes .../message_actions_modal_test.dart | 62 ++- .../message_reactions_modal_test.dart | 95 ++-- .../test/src/misc/reaction_bubble_test.dart | 234 ---------- .../goldens/ci/reaction_detail_sheet_dark.png | Bin 0 -> 6150 bytes .../reaction_detail_sheet_filtered_dark.png | Bin 0 -> 5743 bytes .../reaction_detail_sheet_filtered_light.png | Bin 0 -> 6028 bytes .../ci/reaction_detail_sheet_light.png | Bin 0 -> 6328 bytes .../detail/reaction_detail_sheet_test.dart | 371 +++++++++++++++ .../ci/stream_reaction_indicator_dark.png | Bin 1458 -> 1660 bytes ...tream_reaction_indicator_fallback_dark.png | Bin 0 -> 1523 bytes ...ream_reaction_indicator_fallback_light.png | Bin 0 -> 1744 bytes .../ci/stream_reaction_indicator_light.png | Bin 1605 -> 1938 bytes .../ci/stream_reaction_indicator_own_dark.png | Bin 1259 -> 1523 bytes .../stream_reaction_indicator_own_light.png | Bin 1358 -> 1744 bytes .../reaction_indicator_icon_list_test.dart | 252 ----------- .../indicator/reaction_indicator_test.dart | 380 ++++++++++++---- .../ci/stream_reaction_picker_dark.png | Bin 2324 -> 4053 bytes .../ci/stream_reaction_picker_light.png | Bin 2580 -> 4657 bytes .../stream_reaction_picker_selected_dark.png | Bin 2495 -> 4706 bytes .../stream_reaction_picker_selected_light.png | Bin 2645 -> 5317 bytes .../ci/stream_reaction_picker_subset_dark.png | Bin 0 -> 3785 bytes .../stream_reaction_picker_subset_light.png | Bin 0 -> 4286 bytes .../reaction_picker_icon_list_test.dart | 389 ---------------- .../picker/reaction_picker_test.dart | 422 +++++++++++++++--- .../reaction_bubble_overlay_test.dart | 219 +++++++++ 66 files changed, 2515 insertions(+), 2728 deletions(-) create mode 100644 migrations/redesign/reaction_picker.md delete mode 100644 packages/stream_chat_flutter/lib/src/misc/reaction_icon.dart create mode 100644 packages/stream_chat_flutter/lib/src/misc/reaction_icon_resolver.dart create mode 100644 packages/stream_chat_flutter/lib/src/misc/staggered_scale_transition.dart create mode 100644 packages/stream_chat_flutter/lib/src/reactions/detail/reaction_detail_sheet.dart delete mode 100644 packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_icon_list.dart delete mode 100644 packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_icon_list.dart delete mode 100644 packages/stream_chat_flutter/lib/src/reactions/reaction_bubble.dart delete mode 100644 packages/stream_chat_flutter/test/src/misc/reaction_bubble_test.dart create mode 100644 packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_dark.png create mode 100644 packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_filtered_dark.png create mode 100644 packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_filtered_light.png create mode 100644 packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_light.png create mode 100644 packages/stream_chat_flutter/test/src/reactions/detail/reaction_detail_sheet_test.dart create mode 100644 packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_fallback_dark.png create mode 100644 packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_fallback_light.png delete mode 100644 packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_icon_list_test.dart create mode 100644 packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_subset_dark.png create mode 100644 packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_subset_light.png delete mode 100644 packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_icon_list_test.dart create mode 100644 packages/stream_chat_flutter/test/src/reactions/reaction_bubble_overlay_test.dart diff --git a/melos.yaml b/melos.yaml index 551fb4f1a3..65aa486691 100644 --- a/melos.yaml +++ b/melos.yaml @@ -95,7 +95,7 @@ command: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 4e7e22b4b61e9b9569e2933407d49fff370e6bec + ref: 490feb000a1b4a2f6baa56621dc8941363195742 path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 diff --git a/migrations/redesign/README.md b/migrations/redesign/README.md index 78184f48dd..3f84bfa4ef 100644 --- a/migrations/redesign/README.md +++ b/migrations/redesign/README.md @@ -45,6 +45,7 @@ You can also use the convenience factories `StreamTheme.light()` or `StreamTheme |-----------|-----------------| | Stream Avatar | [stream_avatar.md](stream_avatar.md) | | Message Actions | [message_actions.md](message_actions.md) | +| Reaction Picker / Reactions | [reaction_picker.md](reaction_picker.md) | ## Need Help? diff --git a/migrations/redesign/reaction_picker.md b/migrations/redesign/reaction_picker.md new file mode 100644 index 0000000000..c7ad88bd04 --- /dev/null +++ b/migrations/redesign/reaction_picker.md @@ -0,0 +1,323 @@ +# Reaction Picker Migration Guide + +This guide covers the migration for the redesigned reaction picker and reaction indicator components in Stream Chat Flutter SDK. + +--- + +## Table of Contents + +- [Quick Reference](#quick-reference) +- [StreamChatConfigurationData](#streamchatconfigurationdata) +- [Removed Icon-List APIs](#removed-icon-list-apis) +- [ReactionIconResolver and DefaultReactionIconResolver](#reactioniconresolver-and-defaultreactioniconresolver) +- [StreamReactionPicker](#streamreactionpicker) +- [StreamReactionIndicator](#streamreactionindicator) +- [New Components](#new-components) +- [Migration Checklist](#migration-checklist) + +--- + +## Quick Reference + +| Symbol | Change | +|--------|--------| +| `StreamChatConfigurationData.reactionIcons` | **Removed** — replaced by `reactionIconResolver` | +| `StreamChatConfigurationData.reactionIconResolver` | **New** — optional (default: `DefaultReactionIconResolver()`). Replaces `reactionIcons` | +| `ReactionIconResolver` | **New** — abstract contract for mapping reaction type → widget/emoji | +| `DefaultReactionIconResolver` | **New** — ready-to-use default; extend to customize `defaultReactions`, `emojiCode`, or rendering hooks | +| `ReactionPickerIconList` / `ReactionIndicatorIconList` | **Removed** — list rendering now lives inside picker/indicator widgets | +| `ReactionPickerIcon` / `ReactionIndicatorIcon` | **Removed** — use resolver-based reaction mapping instead | +| `StreamReactionPicker` | **Changed** — reaction set from `config.reactionIconResolver.defaultReactions` only | +| `StreamReactionIndicator` | **Changed** — uses `config.reactionIconResolver.resolve(context, type)` only | +| `ReactionDetailSheet` | **New** — `ReactionDetailSheet.show()` for reaction details bottom sheet | + +> **Note:** If you were using default reactions only, behavior stays the same (`like`, `haha`, `love`, `wow`, `sad`). Migration is required only for custom reaction icon/type setups. + +--- + +## StreamChatConfigurationData + +### Breaking Changes: + +- `reactionIcons` **removed** — was a list of reaction type + builder pairs for picker/indicator +- `reactionIconResolver` **new** — optional; defaults to `DefaultReactionIconResolver()`. All reaction UI uses it + +### Migration + +**Before:** +```dart +StreamChat( + client: client, + streamChatConfigData: StreamChatConfigurationData( + reactionIcons: [ /* type + builder per reaction */ ], + ), + child: MyApp(), +) +``` + +**After:** +```dart +StreamChat( + client: client, + streamChatConfigData: StreamChatConfigurationData( + reactionIconResolver: const MyReactionIconResolver(), + ), + child: MyApp(), +) +``` + +Extend `DefaultReactionIconResolver` (see below), pass as `reactionIconResolver`. Omit to keep defaults. + +> **Important:** +> - Resolver replaces the old list: use `defaultReactions` + `resolve(context, type)` (which uses `emojiCode(type)`) + +--- + +## Removed Icon-List APIs + +### Breaking Changes: + +- `ReactionPickerIconList` and `ReactionIndicatorIconList` were removed +- `ReactionPickerIcon` and `ReactionIndicatorIcon` were removed +- Per-widget icon list injection moved to a single global resolver (`StreamChatConfigurationData.reactionIconResolver`) + +### Migration + +**Before:** +```dart +StreamChat( + client: client, + streamChatConfigData: StreamChatConfigurationData( + reactionIcons: [ + // old reaction icon entries + ], + ), + child: MyApp(), +) +``` + +**After:** +```dart +class MyReactionIconResolver extends DefaultReactionIconResolver { + const MyReactionIconResolver(); + + @override + Set get defaultReactions => const {'like', 'love', 'celebrate'}; + + @override + String? emojiCode(String type) { + if (type == 'celebrate') return '🎉'; + return super.emojiCode(type); + } +} + +StreamChat( + client: client, + streamChatConfigData: StreamChatConfigurationData( + reactionIconResolver: const MyReactionIconResolver(), + ), + child: MyApp(), +) +``` + +--- + +## ReactionIconResolver and DefaultReactionIconResolver + +Picker uses `defaultReactions`; picker and indicator call `resolve(context, type)` → uses `emojiCode(type)` then `buildEmojiReaction` or `buildFallbackReaction`. Extend `DefaultReactionIconResolver` and override only what you need. + +### Contract + +- **`defaultReactions`** — types in quick-pick bar. Every type here must be resolvable by `emojiCode(type)` (return non-null emoji) or fallback is shown. +- **`emojiCode(type)`** — return Unicode emoji (e.g. `'👍'`) or `null`. Used by `resolve`. +- **`supportedReactions`** — full resolver-supported type set. Keep this in sync with your resolver implementation. +- **`resolve(context, type)`** — widget for display. Default: `emojiCode(type)` → `buildEmojiReaction` else `buildFallbackReaction`. + +Override points on `DefaultReactionIconResolver`: `defaultReactions`, `emojiCode`, `buildEmojiReaction`, `buildFallbackReaction`, `supportedReactions`. + +### Migration (custom quick-pick set) + +Restrict `defaultReactions` to keys in `streamSupportedEmojis` so inherited `emojiCode` returns the emoji. + +**Before:** Custom list of reaction types on config or picker. + +**After:** +```dart +class MyReactionIconResolver extends DefaultReactionIconResolver { + const MyReactionIconResolver(); + static const _defaults = {'like', 'haha', 'love', 'wow', 'sad'}; + + @override + Set get defaultReactions => _defaults; +} +// StreamChatConfigurationData(reactionIconResolver: const MyReactionIconResolver(), ...) +``` + +> **Important:** If you add a type not in `streamSupportedEmojis`, override `emojiCode` to return the Unicode emoji for it (see next section). + +### Migration (custom types not in streamSupportedEmojis) + +Override `defaultReactions` (and/or `supportedReactions`) and `emojiCode` so every type has an emoji. + +**After:** +```dart +class MyReactionIconResolver extends DefaultReactionIconResolver { + const MyReactionIconResolver(); + static const _defaults = {'like', 'love', 'custom_celebration'}; + static const _supported = {'like', 'love', 'custom_celebration'}; + static const _customEmojis = {'custom_celebration': '🎉'}; + + @override + Set get defaultReactions => _defaults; + + @override + Set get supportedReactions => _supported; + + @override + String? emojiCode(String type) => _customEmojis[type] ?? streamSupportedEmojis[type]?.emoji; +} +``` + +### Migration (custom rendering, e.g. Twemoji) + +For type-based custom rendering (e.g. Twemoji assets keyed by reaction type), +override `resolve(context, type)` and branch by `type`. + +**After:** +```dart +class MyReactionIconResolver extends DefaultReactionIconResolver { + const MyReactionIconResolver(); + + @override + Widget resolve(BuildContext context, String type) { + switch (type) { + case 'love': + return MyTwemojiWidget(assetName: 'heart'); + case 'haha': + return MyTwemojiWidget(assetName: 'joy'); + default: + return super.resolve(context, type); + } + } +} +``` + +--- + +## StreamReactionPicker + +### Breaking Changes: + +- Picker icons are no longer configured with per-widget icon models +- Quick-pick entries now come from `config.reactionIconResolver.defaultReactions` + +### Migration + +**Before:** +```dart +StreamChat( + client: client, + streamChatConfigData: StreamChatConfigurationData( + reactionIcons: [ /* old icon list */ ], + ), + child: MyApp(), +) +``` + +**After:** +```dart +StreamChat( + client: client, + streamChatConfigData: StreamChatConfigurationData( + reactionIconResolver: const MyReactionIconResolver(), + ), + child: MyApp(), +) +``` + +Then keep picker usage unchanged: + +```dart +StreamReactionPicker( + message: message, + onReactionPicked: onReactionPicked, +) +``` + +Set `reactionIconResolver` on `StreamChatConfigurationData` to customize. + +--- + +## StreamReactionIndicator + +### Breaking Changes: + +- Indicator icons are resolved only through `config.reactionIconResolver.resolve(context, type)` +- Old icon-list based customization paths were removed + +### Migration + +**Before:** +```dart +StreamChat( + client: client, + streamChatConfigData: StreamChatConfigurationData( + reactionIcons: [ /* old icon list */ ], + ), + child: MyApp(), +) +``` + +**After:** +```dart +StreamChat( + client: client, + streamChatConfigData: StreamChatConfigurationData( + reactionIconResolver: const MyReactionIconResolver(), + ), + child: MyApp(), +) +``` + +Then keep indicator usage unchanged: + +```dart +StreamReactionIndicator( + message: message, + onTap: onTap, +) +``` + +Customize via `reactionIconResolver` in config. + +--- + +## New Components + +### ReactionDetailSheet + +Bottom sheet: reaction counts, filter chips per type, list of users. Returns `Future` (e.g. `SelectReaction`). + +```dart +final action = await ReactionDetailSheet.show( + context: context, + message: message, + initialReactionType: selectedType, // optional +); +if (action is SelectReaction) handleSelectReaction(action); +``` + +### ReactionIconResolver / DefaultReactionIconResolver + +Exported for `StreamChatConfigurationData`. See [ReactionIconResolver and DefaultReactionIconResolver](#reactioniconresolver-and-defaultreactioniconresolver). + +--- + +## Migration Checklist + +- [ ] Remove `reactionIcons` from `StreamChatConfigurationData` +- [ ] Custom quick-pick: extend `DefaultReactionIconResolver`, override `defaultReactions` with types from `streamSupportedEmojis` (so `emojiCode` returns emoji); set `reactionIconResolver` +- [ ] Custom types not in `streamSupportedEmojis`: also override `emojiCode` to return Unicode emoji for each; optionally `supportedReactions` +- [ ] Custom rendering (e.g. Twemoji): extend `DefaultReactionIconResolver`, override `resolve(context, type)` and branch by type, set `reactionIconResolver` +- [ ] Remove old icon-list based customization and configure reactions via `reactionIconResolver` only +- [ ] Optionally use `ReactionDetailSheet.show()` diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_header.dart b/packages/stream_chat_flutter/lib/src/channel/channel_header.dart index 7ada230edc..c0a6bab4b0 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_header.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_portal/flutter_portal.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@template streamChannelHeader} @@ -149,84 +150,86 @@ class StreamChannelHeader extends StatelessWidget implements PreferredSizeWidget ) : const SizedBox()); - return StreamConnectionStatusBuilder( - statusBuilder: (context, status) { - var statusString = ''; - var showStatus = true; - - switch (status) { - case ConnectionStatus.connected: - statusString = context.translations.connectedLabel; - showStatus = false; - break; - case ConnectionStatus.connecting: - statusString = context.translations.reconnectingLabel; - break; - case ConnectionStatus.disconnected: - statusString = context.translations.disconnectedLabel; - break; - } - - final theme = Theme.of(context); - - return StreamInfoTile( - showMessage: showConnectionStateTile && showStatus, - message: statusString, - child: AppBar( - toolbarTextStyle: theme.textTheme.bodyMedium, - titleTextStyle: theme.textTheme.titleLarge, - systemOverlayStyle: theme.brightness == Brightness.dark - ? SystemUiOverlayStyle.light - : SystemUiOverlayStyle.dark, - elevation: elevation, - leading: leadingWidget, - bottom: bottom, - bottomOpacity: bottomOpacity, - backgroundColor: backgroundColor ?? channelHeaderTheme.color, - actions: - actions ?? - [ - Padding( - padding: const EdgeInsets.only(right: 10), - child: Center( - child: GestureDetector( - onTap: onImageTap, - child: StreamChannelAvatar( - size: .lg, - channel: channel, + return Portal( + child: StreamConnectionStatusBuilder( + statusBuilder: (context, status) { + var statusString = ''; + var showStatus = true; + + switch (status) { + case ConnectionStatus.connected: + statusString = context.translations.connectedLabel; + showStatus = false; + break; + case ConnectionStatus.connecting: + statusString = context.translations.reconnectingLabel; + break; + case ConnectionStatus.disconnected: + statusString = context.translations.disconnectedLabel; + break; + } + + final theme = Theme.of(context); + + return StreamInfoTile( + showMessage: showConnectionStateTile && showStatus, + message: statusString, + child: AppBar( + toolbarTextStyle: theme.textTheme.bodyMedium, + titleTextStyle: theme.textTheme.titleLarge, + systemOverlayStyle: theme.brightness == Brightness.dark + ? SystemUiOverlayStyle.light + : SystemUiOverlayStyle.dark, + elevation: elevation, + leading: leadingWidget, + bottom: bottom, + bottomOpacity: bottomOpacity, + backgroundColor: backgroundColor ?? channelHeaderTheme.color, + actions: + actions ?? + [ + Padding( + padding: const EdgeInsets.only(right: 10), + child: Center( + child: GestureDetector( + onTap: onImageTap, + child: StreamChannelAvatar( + size: .lg, + channel: channel, + ), ), ), ), - ), - ], - centerTitle: centerTitle, - title: InkWell( - onTap: onTitleTap, - child: SizedBox( - height: preferredSize.height, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: effectiveCenterTitle ? CrossAxisAlignment.center : CrossAxisAlignment.stretch, - children: [ - title ?? - StreamChannelName( - channel: channel, - textStyle: channelHeaderTheme.titleStyle, - ), - const SizedBox(height: 2), - subtitle ?? - StreamChannelInfo( - showTypingIndicator: showTypingIndicator, - channel: channel, - textStyle: channelHeaderTheme.subtitleStyle, - ), ], + centerTitle: centerTitle, + title: InkWell( + onTap: onTitleTap, + child: SizedBox( + height: preferredSize.height, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: effectiveCenterTitle ? CrossAxisAlignment.center : CrossAxisAlignment.stretch, + children: [ + title ?? + StreamChannelName( + channel: channel, + textStyle: channelHeaderTheme.titleStyle, + ), + const SizedBox(height: 2), + subtitle ?? + StreamChannelInfo( + showTypingIndicator: showTypingIndicator, + channel: channel, + textStyle: channelHeaderTheme.subtitleStyle, + ), + ], + ), ), ), ), - ), - ); - }, + ); + }, + ), ); } } diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart b/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart index a375162297..19d0e06f7e 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_portal/flutter_portal.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -104,106 +105,108 @@ class StreamChannelListHeader extends StatelessWidget implements PreferredSizeWi Widget build(BuildContext context) { final _client = client ?? StreamChat.of(context).client; final user = _client.state.currentUser; - return StreamConnectionStatusBuilder( - statusBuilder: (context, status) { - var statusString = ''; - var showStatus = true; + return Portal( + child: StreamConnectionStatusBuilder( + statusBuilder: (context, status) { + var statusString = ''; + var showStatus = true; - switch (status) { - case ConnectionStatus.connected: - statusString = context.translations.connectedLabel; - showStatus = false; - break; - case ConnectionStatus.connecting: - statusString = context.translations.reconnectingLabel; - break; - case ConnectionStatus.disconnected: - statusString = context.translations.disconnectedLabel; - break; - } + switch (status) { + case ConnectionStatus.connected: + statusString = context.translations.connectedLabel; + showStatus = false; + break; + case ConnectionStatus.connecting: + statusString = context.translations.reconnectingLabel; + break; + case ConnectionStatus.disconnected: + statusString = context.translations.disconnectedLabel; + break; + } - final chatThemeData = StreamChatTheme.of(context); - final channelListHeaderThemeData = StreamChannelListHeaderTheme.of(context); - final theme = Theme.of(context); - return StreamInfoTile( - showMessage: showConnectionStateTile && showStatus, - message: statusString, - child: AppBar( - toolbarTextStyle: theme.textTheme.bodyMedium, - titleTextStyle: theme.textTheme.titleLarge, - systemOverlayStyle: theme.brightness == Brightness.dark - ? SystemUiOverlayStyle.light - : SystemUiOverlayStyle.dark, - elevation: elevation, - backgroundColor: backgroundColor ?? channelListHeaderThemeData.color, - centerTitle: centerTitle, - leading: switch ((leading, user)) { - (final leading?, _) => leading, - (_, final user?) => Center( - child: GestureDetector( - onTap: switch (onUserAvatarTap) { - final onTap? => () => onTap(user), - _ => () { - preNavigationCallback?.call(); - Scaffold.of(context).openDrawer(); + final chatThemeData = StreamChatTheme.of(context); + final channelListHeaderThemeData = StreamChannelListHeaderTheme.of(context); + final theme = Theme.of(context); + return StreamInfoTile( + showMessage: showConnectionStateTile && showStatus, + message: statusString, + child: AppBar( + toolbarTextStyle: theme.textTheme.bodyMedium, + titleTextStyle: theme.textTheme.titleLarge, + systemOverlayStyle: theme.brightness == Brightness.dark + ? SystemUiOverlayStyle.light + : SystemUiOverlayStyle.dark, + elevation: elevation, + backgroundColor: backgroundColor ?? channelListHeaderThemeData.color, + centerTitle: centerTitle, + leading: switch ((leading, user)) { + (final leading?, _) => leading, + (_, final user?) => Center( + child: GestureDetector( + onTap: switch (onUserAvatarTap) { + final onTap? => () => onTap(user), + _ => () { + preNavigationCallback?.call(); + Scaffold.of(context).openDrawer(); + }, }, - }, - child: StreamUserAvatar( - size: .lg, - user: user, - showOnlineIndicator: false, + child: StreamUserAvatar( + size: .lg, + user: user, + showOnlineIndicator: false, + ), ), ), - ), - _ => const Empty(), - }, - actions: - actions ?? - [ - StreamNeumorphicButton( - child: IconButton( - icon: StreamConnectionStatusBuilder( - statusBuilder: (context, status) { - final color = switch (status) { - ConnectionStatus.connected => chatThemeData.colorTheme.accentPrimary, - ConnectionStatus.connecting => Colors.grey, - ConnectionStatus.disconnected => Colors.grey, - }; + _ => const Empty(), + }, + actions: + actions ?? + [ + StreamNeumorphicButton( + child: IconButton( + icon: StreamConnectionStatusBuilder( + statusBuilder: (context, status) { + final color = switch (status) { + ConnectionStatus.connected => chatThemeData.colorTheme.accentPrimary, + ConnectionStatus.connecting => Colors.grey, + ConnectionStatus.disconnected => Colors.grey, + }; - return StreamSvgIcon( - size: 24, - color: color, - icon: StreamSvgIcons.penWrite, - ); - }, + return StreamSvgIcon( + size: 24, + color: color, + icon: StreamSvgIcons.penWrite, + ); + }, + ), + onPressed: onNewChatButtonTap, ), - onPressed: onNewChatButtonTap, ), + ], + title: Column( + children: [ + Builder( + builder: (context) { + if (titleBuilder != null) { + return titleBuilder!(context, status, _client); + } + switch (status) { + case ConnectionStatus.connected: + return _ConnectedTitleState(); + case ConnectionStatus.connecting: + return _ConnectingTitleState(); + case ConnectionStatus.disconnected: + return _DisconnectedTitleState(client: _client); + } + }, ), + subtitle ?? const Empty(), ], - title: Column( - children: [ - Builder( - builder: (context) { - if (titleBuilder != null) { - return titleBuilder!(context, status, _client); - } - switch (status) { - case ConnectionStatus.connected: - return _ConnectedTitleState(); - case ConnectionStatus.connecting: - return _ConnectingTitleState(); - case ConnectionStatus.disconnected: - return _DisconnectedTitleState(client: _client); - } - }, - ), - subtitle ?? const Empty(), - ], + ), ), - ), - ); - }, + ); + }, + ), ); } } diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart index 27caf99f8b..8a4b1f169c 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart @@ -7,6 +7,7 @@ import 'dart:math'; import 'package:desktop_drop/desktop_drop.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_portal/flutter_portal.dart'; import 'package:stream_chat_flutter/platform_widget_builder/src/platform_widget_builder.dart'; import 'package:stream_chat_flutter/src/message_input/attachment_button.dart'; import 'package:stream_chat_flutter/src/message_input/command_button.dart'; @@ -684,16 +685,18 @@ class StreamMessageInputState extends State with Restoration final shadow = widget.shadow ?? _messageInputTheme.shadow; final elevation = widget.elevation ?? _messageInputTheme.elevation; - return Material( - elevation: elevation ?? 8, - child: DecoratedBox( - decoration: BoxDecoration( - color: _messageInputTheme.inputBackgroundColor, - boxShadow: [if (shadow != null) shadow], - ), - child: SimpleSafeArea( - enabled: widget.enableSafeArea ?? _messageInputTheme.enableSafeArea, - child: Center(heightFactor: 1, child: messageInput), + return Portal( + child: Material( + elevation: elevation ?? 8, + child: DecoratedBox( + decoration: BoxDecoration( + color: _messageInputTheme.inputBackgroundColor, + boxShadow: [if (shadow != null) shadow], + ), + child: SimpleSafeArea( + enabled: widget.enableSafeArea ?? _messageInputTheme.enableSafeArea, + child: Center(heightFactor: 1, child: messageInput), + ), ), ), ); diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart index c4be763db1..59b10cf44d 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart @@ -481,44 +481,49 @@ class _StreamMessageListViewState extends State { @override Widget build(BuildContext context) { + // TODO: Revisit this nested Portal setup during desktop reactions refactor + // and remove the extra layer if a dedicated message-list portal label is + // no longer required. return Portal( labels: const [kPortalMessageListViewLabel], - child: ScaffoldMessenger( - child: MessageListCore( - paginationLimit: widget.paginationLimit, - messageFilter: widget.messageFilter, - loadingBuilder: - widget.loadingBuilder ?? - (context) => const Center( - child: CircularProgressIndicator.adaptive(), - ), - emptyBuilder: - widget.emptyBuilder ?? - (context) => Center( - child: Text( - context.translations.emptyChatMessagesText, - style: _streamTheme.textTheme.footnote.copyWith( - color: _streamTheme.colorTheme.textHighEmphasis - // ignore: deprecated_member_use - .withOpacity(0.5), + child: Portal( + child: ScaffoldMessenger( + child: MessageListCore( + paginationLimit: widget.paginationLimit, + messageFilter: widget.messageFilter, + loadingBuilder: + widget.loadingBuilder ?? + (context) => const Center( + child: CircularProgressIndicator.adaptive(), + ), + emptyBuilder: + widget.emptyBuilder ?? + (context) => Center( + child: Text( + context.translations.emptyChatMessagesText, + style: _streamTheme.textTheme.footnote.copyWith( + color: _streamTheme.colorTheme.textHighEmphasis + // ignore: deprecated_member_use + .withOpacity(0.5), + ), ), ), - ), - messageListBuilder: widget.messageListBuilder ?? (context, list) => _buildListView(list), - messageListController: _messageListController, - parentMessage: widget.parentMessage, - errorBuilder: - widget.errorBuilder ?? - (BuildContext context, Object error) => Center( - child: Text( - context.translations.genericErrorText, - style: _streamTheme.textTheme.footnote.copyWith( - color: _streamTheme.colorTheme.textHighEmphasis - // ignore: deprecated_member_use - .withOpacity(0.5), + messageListBuilder: widget.messageListBuilder ?? (context, list) => _buildListView(list), + messageListController: _messageListController, + parentMessage: widget.parentMessage, + errorBuilder: + widget.errorBuilder ?? + (BuildContext context, Object error) => Center( + child: Text( + context.translations.genericErrorText, + style: _streamTheme.textTheme.footnote.copyWith( + color: _streamTheme.colorTheme.textHighEmphasis + // ignore: deprecated_member_use + .withOpacity(0.5), + ), ), ), - ), + ), ), ), ); diff --git a/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart index 0046ca0d0d..5677a5f696 100644 --- a/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart @@ -64,6 +64,8 @@ class StreamMessageActionsModal extends StatelessWidget { @override Widget build(BuildContext context) { + final spacing = context.streamSpacing; + final alignment = switch (reverse) { true => AlignmentDirectional.centerEnd, false => AlignmentDirectional.centerStart, @@ -75,7 +77,7 @@ class StreamMessageActionsModal extends StatelessWidget { } return StreamMessageDialog( - spacing: 4, + spacing: spacing.xs, alignment: alignment, headerBuilder: (context) { final safeArea = MediaQuery.paddingOf(context); @@ -86,7 +88,7 @@ class StreamMessageActionsModal extends StatelessWidget { message: message, reverse: reverse, visible: showReactionPicker, - anchorOffset: const Offset(0, -8), + anchorOffset: Offset(0, -spacing.xs), onReactionPicked: onReactionPicked, reactionPickerBuilder: reactionPickerBuilder, child: IgnorePointer(child: messageWidget), diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart index 0298759cb5..c45941b2a3 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart @@ -854,41 +854,10 @@ class _StreamMessageWidgetState extends State Message message, ) async { final channel = StreamChannel.of(context).channel; - final showPicker = widget.showReactionPicker && channel.canSendReaction; - final action = await showStreamDialog( + final action = await ReactionDetailSheet.show( context: context, - useRootNavigator: false, - builder: (_) => StreamChatConfiguration( - // This is needed to provide the nearest reaction icons to the - // StreamMessageReactionsModal. - data: StreamChatConfiguration.of(context), - child: StreamMessageReactionsModal( - message: message, - reverse: widget.reverse, - onUserAvatarTap: widget.onUserAvatarTap, - showReactionPicker: showPicker, - reactionPickerBuilder: widget.reactionPickerBuilder, - messageWidget: StreamChannel( - channel: channel, - child: widget.copyWith( - key: const Key('MessageWidget'), - message: message.trimmed, - showReactions: false, - showUsername: false, - showTimestamp: false, - translateUserAvatar: false, - showSendingIndicator: false, - padding: EdgeInsets.zero, - showPinHighlight: false, - showUserAvatar: switch (widget.reverse) { - true => DisplayWidget.gone, - false => DisplayWidget.show, - }, - ), - ), - ), - ), + message: message, ); if (action is! MessageAction) return; diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart index be20f0eca1..edf7f194d6 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart @@ -275,11 +275,6 @@ class MessageWidgetContent extends StatelessWidget { visible: isMobileDevice && showReactions, anchorOffset: const Offset(0, 36), reactionIndicatorBuilder: reactionIndicatorBuilder, - childSizeDelta: switch (showUserAvatar) { - DisplayWidget.gone => Offset.zero, - // Size adjustment for the user avatar - _ => const Offset(40, 0), - }, child: Padding( padding: switch (showReactions) { true => const EdgeInsets.only(top: 28), diff --git a/packages/stream_chat_flutter/lib/src/misc/reaction_icon.dart b/packages/stream_chat_flutter/lib/src/misc/reaction_icon.dart deleted file mode 100644 index c3c6cd3d34..0000000000 --- a/packages/stream_chat_flutter/lib/src/misc/reaction_icon.dart +++ /dev/null @@ -1,172 +0,0 @@ -// ignore_for_file: avoid_positional_boolean_parameters - -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; - -/// {@template reactionIconBuilder} -/// Signature for a function that builds a reaction icon. -/// {@endtemplate} -typedef ReactionIconBuilder = - Widget Function( - BuildContext context, - bool isHighlighted, - double iconSize, - ); - -/// {@template streamReactionIcon} -/// Reaction icon data -/// {@endtemplate} -class StreamReactionIcon { - /// {@macro streamReactionIcon} - const StreamReactionIcon({ - required this.type, - this.emojiCode, - required this.builder, - }); - - /// Creates a reaction icon with a default unknown icon. - const StreamReactionIcon.unknown() : type = 'unknown', emojiCode = null, builder = _unknownBuilder; - - /// Converts this [StreamReactionIcon] to a [Reaction] object. - Reaction toReaction() => Reaction(type: type, emojiCode: emojiCode); - - /// Type of reaction - final String type; - - /// Optional emoji code for the reaction. - /// - /// Used to display a custom emoji in the notification. - final String? emojiCode; - - /// {@macro reactionIconBuilder} - final ReactionIconBuilder builder; - - /// The default list of reaction icons provided by Stream Chat. - /// - /// This includes five reactions: - /// - love: Represented by a heart icon - /// - like: Represented by a thumbs up icon - /// - sad: Represented by a thumbs down icon - /// - haha: Represented by a laughing face icon - /// - wow: Represented by a surprised face icon - /// - /// These default reactions can be used directly or as a starting point for - /// custom reaction configurations. - static const List defaultReactions = [ - StreamReactionIcon(type: 'love', emojiCode: '❤️', builder: _loveBuilder), - StreamReactionIcon(type: 'like', emojiCode: '👍', builder: _likeBuilder), - StreamReactionIcon(type: 'sad', emojiCode: '👎', builder: _sadBuilder), - StreamReactionIcon(type: 'haha', emojiCode: '😂', builder: _hahaBuilder), - StreamReactionIcon(type: 'wow', emojiCode: '😮', builder: _wowBuilder), - ]; - - static Widget _loveBuilder( - BuildContext context, - bool highlighted, - double size, - ) { - final theme = StreamChatTheme.of(context); - final iconColor = switch (highlighted) { - true => theme.colorTheme.accentPrimary, - false => theme.primaryIconTheme.color, - }; - - return StreamSvgIcon( - icon: StreamSvgIcons.loveReaction, - color: iconColor, - size: size, - ); - } - - static Widget _likeBuilder( - BuildContext context, - bool highlighted, - double size, - ) { - final theme = StreamChatTheme.of(context); - final iconColor = switch (highlighted) { - true => theme.colorTheme.accentPrimary, - false => theme.primaryIconTheme.color, - }; - - return StreamSvgIcon( - icon: StreamSvgIcons.thumbsUpReaction, - color: iconColor, - size: size, - ); - } - - static Widget _sadBuilder( - BuildContext context, - bool highlighted, - double size, - ) { - final theme = StreamChatTheme.of(context); - final iconColor = switch (highlighted) { - true => theme.colorTheme.accentPrimary, - false => theme.primaryIconTheme.color, - }; - - return StreamSvgIcon( - icon: StreamSvgIcons.thumbsDownReaction, - color: iconColor, - size: size, - ); - } - - static Widget _hahaBuilder( - BuildContext context, - bool highlighted, - double size, - ) { - final theme = StreamChatTheme.of(context); - final iconColor = switch (highlighted) { - true => theme.colorTheme.accentPrimary, - false => theme.primaryIconTheme.color, - }; - - return StreamSvgIcon( - icon: StreamSvgIcons.lolReaction, - color: iconColor, - size: size, - ); - } - - static Widget _wowBuilder( - BuildContext context, - bool highlighted, - double size, - ) { - final theme = StreamChatTheme.of(context); - final iconColor = switch (highlighted) { - true => theme.colorTheme.accentPrimary, - false => theme.primaryIconTheme.color, - }; - - return StreamSvgIcon( - icon: StreamSvgIcons.wutReaction, - color: iconColor, - size: size, - ); - } - - static Widget _unknownBuilder( - BuildContext context, - bool highlighted, - double size, - ) { - final theme = StreamChatTheme.of(context); - final iconColor = switch (highlighted) { - true => theme.colorTheme.accentPrimary, - false => theme.primaryIconTheme.color, - }; - - return Icon( - Icons.help_outline_rounded, - color: iconColor, - size: size, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/misc/reaction_icon_resolver.dart b/packages/stream_chat_flutter/lib/src/misc/reaction_icon_resolver.dart new file mode 100644 index 0000000000..9ead8547bc --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/misc/reaction_icon_resolver.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// {@template reactionIconResolver} +/// Resolves reaction types to emoji codes and provides the list of +/// available reactions. +/// +/// Consumers can query supported reactions, default quick-pick reactions, +/// and map reaction types to emoji characters — all from a single source +/// of truth. +/// {@endtemplate} +abstract class ReactionIconResolver { + /// {@macro reactionIconResolver} + const ReactionIconResolver(); + + /// A small set of commonly used reaction types shown for quick + /// access in the reaction picker bar. + Set get defaultReactions; + + /// All supported reaction types, in display order. + /// + /// Iteration order of this set is used as reaction display order in + /// default components. Implementations should use a + /// [LinkedHashSet] or equivalent to preserve insertion order. + Set get supportedReactions; + + /// Returns the emoji code for the given reaction [type], or null + /// if the type is not supported. + String? emojiCode(String type); + + /// Resolves the given reaction [type] into a widget for display. + Widget resolve(BuildContext context, String type); +} + +/// {@template defaultReactionIconResolver} +/// Default implementation of [ReactionIconResolver]. +/// +/// Uses [streamSupportedEmojis] from `stream_core_flutter` to resolve +/// reaction types to emoji characters. +/// +/// Resolution chain for [resolve]: +/// 1. [emojiCode] lookup by type -> render as emoji text +/// 2. Fallback -> render type string as text +/// +/// Override [buildEmojiReaction] to customize emoji rendering +/// (e.g., Twemoji images). +/// Override [buildFallbackReaction] to customize the fallback. +/// {@endtemplate} +class DefaultReactionIconResolver extends ReactionIconResolver { + /// {@macro defaultReactionIconResolver} + const DefaultReactionIconResolver(); + + static const _defaultQuickReactions = {'like', 'haha', 'love', 'wow', 'sad'}; + + @override + Set get defaultReactions => _defaultQuickReactions; + + @override + Set get supportedReactions => streamSupportedEmojis.keys.toSet(); + + @override + String? emojiCode(String type) => streamSupportedEmojis[type]?.emoji; + + @override + Widget resolve(BuildContext context, String type) { + if (emojiCode(type) case final emoji?) { + return buildEmojiReaction(context, emoji); + } + + return buildFallbackReaction(context, type); + } + + /// Builds a widget for a resolved emoji character. + @protected + Widget buildEmojiReaction(BuildContext context, String emoji) => Text(emoji); + + /// Builds a fallback widget when no emoji could be resolved. + @protected + Widget buildFallbackReaction(BuildContext context, String type) => const Text('❓'); +} diff --git a/packages/stream_chat_flutter/lib/src/misc/staggered_scale_transition.dart b/packages/stream_chat_flutter/lib/src/misc/staggered_scale_transition.dart new file mode 100644 index 0000000000..8dc1dddd40 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/misc/staggered_scale_transition.dart @@ -0,0 +1,148 @@ +import 'package:ezanimation/ezanimation.dart'; +import 'package:flutter/material.dart'; + +/// {@template staggeredScaleTransition} +/// A widget that scales in its [children] with a staggered animation. +/// +/// Each child pops in sequentially with a configurable [staggerDelay]. +/// +/// By default, children animate from last to first. Set [animateReversed] +/// to `false` to animate from first to last. +/// +/// {@tool snippet} +/// +/// ```dart +/// StaggeredScaleTransition( +/// children: [ +/// Icon(Icons.star), +/// Icon(Icons.favorite), +/// Icon(Icons.thumb_up), +/// ], +/// ) +/// ``` +/// {@end-tool} +/// {@endtemplate} +class StaggeredScaleTransition extends StatefulWidget { + /// {@macro staggeredScaleTransition} + const StaggeredScaleTransition({ + super.key, + required this.children, + this.staggerDelay = const Duration(milliseconds: 30), + this.animateReversed = true, + }); + + /// The widgets to display with staggered scale-in animation. + final List children; + + /// The delay between the start of each child's animation. + /// + /// Defaults to 30 milliseconds. + final Duration staggerDelay; + + /// Whether to animate children in reversed list order. + /// + /// When `true`, children animate from last to first in list order. + /// When `false`, children animate from first to last in list order. + /// + /// Defaults to `true`. + final bool animateReversed; + + @override + State createState() => _StaggeredScaleTransitionState(); +} + +class _StaggeredScaleTransitionState extends State { + List _animations = []; + + void _initAnimations() { + _animations = List.generate( + widget.children.length, + (index) => EzAnimation.tween( + Tween(begin: 0.0, end: 1.0), + const Duration(milliseconds: 120), + curve: Curves.bounceOut, + ), + ); + } + + void _triggerAnimations() async { + final iterable = switch (widget.animateReversed) { + true => _animations.reversed, + false => _animations, + }; + + for (final animation in iterable) { + if (!mounted) return; + animation.start(); + await Future.delayed(widget.staggerDelay); + } + } + + void _dismissAnimations() { + for (final animation in _animations) { + animation.stop(); + } + } + + void _disposeAnimations() { + for (final animation in _animations) { + animation.dispose(); + } + } + + @override + void initState() { + super.initState(); + _initAnimations(); + + // Trigger animations at the end of the frame to avoid jank. + WidgetsBinding.instance.endOfFrame.then((_) => _triggerAnimations()); + } + + @override + void didUpdateWidget(covariant StaggeredScaleTransition oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.children.length != widget.children.length) { + // Dismiss and dispose old animations. + _dismissAnimations(); + _disposeAnimations(); + + // Initialize new animations. + _initAnimations(); + + // Trigger animations at the end of the frame to avoid jank. + WidgetsBinding.instance.endOfFrame.then((_) => _triggerAnimations()); + } + } + + @override + void dispose() { + _dismissAnimations(); + _disposeAnimations(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + for (var i = 0; i < widget.children.length; i++) + AnimatedBuilder( + animation: _animations[i], + builder: (context, child) { + final value = _animations[i].value; + // Width grows at 2x the scale rate so the row reaches full + // width before the bounce oscillation starts. + return Align( + widthFactor: (value * 2.0).clamp(0.0, 1.0), + heightFactor: 1, + child: Transform.scale(scale: value, child: child), + ); + }, + child: widget.children[i], + ), + ], + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/misc/stream_modal.dart b/packages/stream_chat_flutter/lib/src/misc/stream_modal.dart index d25ee5ad6c..03665115a0 100644 --- a/packages/stream_chat_flutter/lib/src/misc/stream_modal.dart +++ b/packages/stream_chat_flutter/lib/src/misc/stream_modal.dart @@ -1,6 +1,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:flutter_portal/flutter_portal.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; /// Shows a modal dialog with customized transitions and backdrop effects. @@ -54,8 +55,8 @@ Future showStreamDialog({ ); }, pageBuilder: (context, animation, secondaryAnimation) { - final pageChild = Builder(builder: builder); - return capturedThemes.wrap(StreamChatTheme(data: theme, child: pageChild)); + final pageChild = Portal(child: Builder(builder: builder)); + return StreamChatTheme(data: theme, child: capturedThemes.wrap(pageChild)); }, ); } diff --git a/packages/stream_chat_flutter/lib/src/reactions/desktop_reactions_builder.dart b/packages/stream_chat_flutter/lib/src/reactions/desktop_reactions_builder.dart index 86a41a4583..719cb31d09 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/desktop_reactions_builder.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/desktop_reactions_builder.dart @@ -1,6 +1,5 @@ // ignore_for_file: cascade_invocations -import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; @@ -69,7 +68,8 @@ class _DesktopReactionsBuilderState extends State { Widget build(BuildContext context) { final streamChat = StreamChat.of(context); final currentUser = streamChat.currentUser!; - final reactionIcons = StreamChatConfiguration.of(context).reactionIcons; + final config = StreamChatConfiguration.of(context); + final resolver = config.reactionIconResolver; final streamChatTheme = StreamChatTheme.of(context); final reactionsMap = {}; @@ -114,17 +114,13 @@ class _DesktopReactionsBuilderState extends State { runSpacing: 4, children: [ ...reactionsList.map((reaction) { - final reactionIcon = reactionIcons.firstWhereOrNull( - (r) => r.type == reaction.type, - ); - return _BottomReaction( currentUser: currentUser, reaction: reaction, message: widget.message, borderSide: widget.borderSide, messageTheme: widget.messageTheme, - reactionIcon: reactionIcon, + resolver: resolver, streamChatTheme: streamChatTheme, ); }).toList(), @@ -151,7 +147,7 @@ class _BottomReaction extends StatelessWidget { required this.message, required this.borderSide, required this.messageTheme, - required this.reactionIcon, + required this.resolver, required this.streamChatTheme, }); @@ -160,7 +156,7 @@ class _BottomReaction extends StatelessWidget { final Message message; final BorderSide? borderSide; final StreamMessageThemeData? messageTheme; - final StreamReactionIcon? reactionIcon; + final ReactionIconResolver resolver; final StreamChatThemeData streamChatTheme; @override @@ -177,10 +173,13 @@ class _BottomReaction extends StatelessWidget { message, reaction, ); - } else if (reactionIcon != null) { + } else { StreamChannel.of(context).channel.sendReaction( message, - reactionIcon!.toReaction(), + Reaction( + type: reaction.type, + emojiCode: resolver.emojiCode(reaction.type), + ), enforceUnique: StreamChatConfiguration.of(context).enforceUniqueReactions, ); } @@ -208,19 +207,7 @@ class _BottomReaction extends StatelessWidget { constraints: BoxConstraints.tight( const Size.square(14), ), - child: - reactionIcon?.builder( - context, - reaction.user?.id == userId, - 14, - ) ?? - Icon( - Icons.help_outline_rounded, - size: 14, - color: reaction.user?.id == userId - ? streamChatTheme.colorTheme.accentPrimary - : streamChatTheme.colorTheme.textLowEmphasis, - ), + child: resolver.resolve(context, reaction.type), ), const SizedBox(width: 4), Text( diff --git a/packages/stream_chat_flutter/lib/src/reactions/detail/reaction_detail_sheet.dart b/packages/stream_chat_flutter/lib/src/reactions/detail/reaction_detail_sheet.dart new file mode 100644 index 0000000000..5d99d0d60c --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/reactions/detail/reaction_detail_sheet.dart @@ -0,0 +1,183 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar.dart'; +import 'package:stream_chat_flutter/src/message_action/message_action.dart'; +import 'package:stream_chat_flutter/src/stream_chat_configuration.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// A bottom sheet that displays detailed reaction information for a message. +/// +/// Shows the total reaction count, emoji filter chips for each reaction type, +/// and a scrollable list of users who reacted. +/// +/// Use [ReactionDetailSheet.show] to display the sheet. +class ReactionDetailSheet extends StatefulWidget { + /// Creates a reaction detail sheet. + /// + /// This constructor is private. Use [ReactionDetailSheet.show] to display + /// the sheet as a modal bottom sheet. + const ReactionDetailSheet._({ + required this.scrollController, + required this.message, + this.initialReactionType, + }); + + /// The message whose reactions are displayed. + final Message message; + + /// Scroll controller provided by [DraggableScrollableSheet]. + final ScrollController scrollController; + + /// The reaction type to pre-select when the sheet opens. + /// + /// When non-null, the sheet opens with this reaction type already filtered + /// and the corresponding chip scrolled into view. + final String? initialReactionType; + + /// Shows the reaction detail sheet as a modal bottom sheet. + /// + /// Returns a [SelectReaction] if the user selects a reaction, or `null` + /// if the sheet is dismissed without any selection. + static Future show({ + required BuildContext context, + required Message message, + String? initialReactionType, + }) { + final radius = context.streamRadius; + final colorScheme = context.streamColorScheme; + + return showModalBottomSheet( + context: context, + useSafeArea: true, + isScrollControlled: true, + showDragHandle: true, + backgroundColor: colorScheme.backgroundElevation1, + shape: RoundedRectangleBorder( + borderRadius: BorderRadiusDirectional.only( + topStart: radius.xl, + topEnd: radius.xl, + ), + ), + builder: (context) => DraggableScrollableSheet( + snap: true, + expand: false, + minChildSize: 0.5, + snapSizes: const [0.5, 1], + builder: (_, scrollController) => ReactionDetailSheet._( + scrollController: scrollController, + message: message, + initialReactionType: initialReactionType, + ), + ), + ); + } + + @override + State createState() => _ReactionDetailSheetState(); +} + +class _ReactionDetailSheetState extends State { + late String? _selectedReactionType = widget.initialReactionType; + + @override + Widget build(BuildContext context) { + final spacing = context.streamSpacing; + final textTheme = context.streamTextTheme; + + final config = StreamChatConfiguration.of(context); + final resolver = config.reactionIconResolver; + + final allReactions = widget.message.latestReactions ?? []; + final ownReactions = [...?widget.message.ownReactions]; + final ownReactionsMap = {for (final it in ownReactions) it.type: it}; + final reactionGroups = widget.message.reactionGroups ?? {}; + + final currentUserId = StreamChatCore.of(context).currentUser?.id; + + final filteredReactions = switch (_selectedReactionType) { + final type? => allReactions.where((r) => r.type == type).toList(), + _ => allReactions, + }; + + final visibleCount = switch (_selectedReactionType) { + final type? => reactionGroups[type]?.count ?? 0, + _ => reactionGroups.values.fold(0, (sum, g) => sum + g.count), + }; + + return Column( + mainAxisSize: .min, + crossAxisAlignment: .stretch, + children: [ + Padding( + //visibleCount == 1 ? '1 Reaction' : '$visibleCount Reactions', + padding: .symmetric(horizontal: spacing.sm), + child: Text( + switch (visibleCount) { + 1 => '1 Reaction', + _ => '$visibleCount Reactions', + }, + textAlign: .center, + style: textTheme.headingSm, + ), + ), + SizedBox(height: spacing.sm), + StreamEmojiChipBar( + selected: _selectedReactionType, + onSelected: (type) => setState(() => _selectedReactionType = type), + items: [ + for (final MapEntry(:key, :value) in reactionGroups.entries) + StreamEmojiChipItem( + value: key, + emoji: resolver.resolve(context, key), + count: value.count, + ), + ], + leading: StreamEmojiChip.addEmoji( + onPressed: () async { + final selectedReactions = ownReactionsMap.keys.toSet(); + final emoji = await StreamEmojiPickerSheet.show( + context: context, + selectedReactions: selectedReactions, + ); + + if (!context.mounted) return; + if (emoji == null) return Navigator.of(context).pop(); + + final reaction = Reaction(type: emoji.shortName, emojiCode: emoji.emoji); + return Navigator.of(context).pop(SelectReaction(message: widget.message, reaction: reaction)); + }, + ), + ), + SizedBox(height: spacing.md), + Expanded( + child: ListView.builder( + controller: widget.scrollController, + padding: .symmetric(horizontal: spacing.xxs), + itemCount: filteredReactions.length, + itemBuilder: (context, index) { + final reaction = filteredReactions[index]; + final user = reaction.user; + if (user == null) return const SizedBox.shrink(); + + final isOwnReaction = currentUserId != null && reaction.userId == currentUserId; + + return StreamListTile( + leading: StreamUserAvatar(size: .md, user: user, showOnlineIndicator: false), + title: Text(user.name), + subtitle: isOwnReaction ? const Text('Tap to remove') : null, + trailing: StreamEmoji(size: .md, emoji: resolver.resolve(context, reaction.type)), + onTap: switch (isOwnReaction) { + true => () { + final action = SelectReaction(message: widget.message, reaction: reaction); + return Navigator.of(context).pop(action); + }, + _ => null, + }, + ); + }, + ), + ), + ], + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator.dart b/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator.dart index 975f548bbe..fa442cf2ab 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator.dart @@ -14,12 +14,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// - [onTap]: An optional callback triggered when the reaction indicator /// is tapped. /// {@endtemplate} -typedef ReactionIndicatorBuilder = - Widget Function( - BuildContext context, - Message message, - VoidCallback? onTap, - ); +typedef ReactionIndicatorBuilder = Widget Function(BuildContext context, Message message, VoidCallback? onTap); /// {@template streamReactionIndicator} /// A widget that displays a horizontal list of reaction icons that users have @@ -35,43 +30,22 @@ class StreamReactionIndicator extends StatelessWidget { super.key, this.onTap, required this.message, - required this.reactionIcons, - this.reactionIconBuilder, this.backgroundColor, - this.padding = const EdgeInsets.all(8), - this.scrollable = true, - this.borderRadius = const BorderRadius.all(Radius.circular(26)), - this.reactionSorting = ReactionSorting.byFirstReactionAt, + this.padding, + this.borderRadius, + this.reactionSorting, }); - /// Creates a [StreamReactionIndicator] using the default reaction icons - /// provided by the [StreamChatConfiguration]. + /// Creates a [StreamReactionIndicator] using the default configuration. /// /// This is the recommended way to create a reaction indicator /// as it ensures that the icons are consistent with the rest of the app. - /// - /// The [onTap] callback is optional and can be used to handle - /// when the reaction indicator is tapped. factory StreamReactionIndicator.builder( - BuildContext context, + BuildContext _, Message message, VoidCallback? onTap, ) { - final config = StreamChatConfiguration.of(context); - final reactionIcons = config.reactionIcons; - - final currentUser = StreamChat.maybeOf(context)?.currentUser; - final isMyMessage = message.user?.id == currentUser?.id; - - final theme = StreamChatTheme.of(context); - final messageTheme = theme.getMessageTheme(reverse: isMyMessage); - - return StreamReactionIndicator( - onTap: onTap, - message: message, - reactionIcons: reactionIcons, - backgroundColor: messageTheme.reactionsBackgroundColor, - ); + return StreamReactionIndicator(onTap: onTap, message: message); } /// Callback triggered when the reaction indicator is tapped. @@ -80,86 +54,68 @@ class StreamReactionIndicator extends StatelessWidget { /// Message to attach the reaction to. final Message message; - /// The list of available reaction icons. - final List reactionIcons; - - /// Optional custom builder for reaction indicator icons. - final ReactionIndicatorIconBuilder? reactionIconBuilder; - /// Background color for the reaction indicator. final Color? backgroundColor; /// Padding around the reaction indicator. /// /// Defaults to `EdgeInsets.all(8)`. - final EdgeInsets padding; - - /// Whether the reaction indicator should be scrollable. - /// - /// Defaults to `true`. - final bool scrollable; + final EdgeInsetsGeometry? padding; /// Border radius for the reaction indicator. /// /// Defaults to a circular border with a radius of 26. - final BorderRadius? borderRadius; + final BorderRadiusGeometry? borderRadius; /// Sorting strategy for the reaction. /// /// Defaults to sorting by the first reaction at. - final Comparator reactionSorting; + final Comparator? reactionSorting; @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); + final radius = context.streamRadius; + final spacing = context.streamSpacing; + final colorScheme = context.streamColorScheme; + + final effectivePadding = padding ?? .symmetric(horizontal: spacing.xs, vertical: spacing.xxs); + final effectiveBorderRadius = borderRadius ?? BorderRadius.all(radius.max); + final effectiveBackgroundColor = backgroundColor ?? colorScheme.backgroundElevation3; - final ownReactions = {...?message.ownReactions?.map((it) => it.type)}; - final reactionIcons = {for (final it in this.reactionIcons) it.type: it}; + final side = BorderSide(color: colorScheme.borderDefault); + final shape = RoundedSuperellipseBorder(borderRadius: effectiveBorderRadius, side: side); - final sortedReactionGroups = message.reactionGroups?.entries.sortedByCompare((it) => it.value, reactionSorting); + final config = StreamChatConfiguration.of(context); + final resolver = config.reactionIconResolver; + + final reactionGroups = message.reactionGroups?.entries; + final effectiveReactionSorting = reactionSorting ?? ReactionSorting.byFirstReactionAt; + final sortedReactionGroups = reactionGroups?.sortedByCompare((it) => it.value, effectiveReactionSorting); final indicatorIcons = sortedReactionGroups?.map( - (group) { - final reactionType = group.key; - final reactionIcon = switch (reactionIcons[reactionType]) { - final icon? => icon, - _ => const StreamReactionIcon.unknown(), - }; - - return ReactionIndicatorIcon( - type: reactionType, - builder: reactionIcon.builder, - isSelected: ownReactions.contains(reactionType), - ); - }, + (group) => StreamEmoji( + size: StreamEmojiSize.sm, + emoji: resolver.resolve(context, group.key), + ), ); - final reactionIndicator = ReactionIndicatorIconList( - iconBuilder: reactionIconBuilder, - indicatorIcons: [...?indicatorIcons], + final indicatorContent = Row( + mainAxisSize: .min, + spacing: spacing.xxs, + children: [...?indicatorIcons], ); - final isSingleIndicatorIcon = indicatorIcons?.length == 1; - final extraPadding = switch (isSingleIndicatorIcon) { - true => EdgeInsets.zero, - false => const EdgeInsets.symmetric(horizontal: 4), - }; - return Material( - borderRadius: borderRadius, - clipBehavior: Clip.antiAlias, - color: backgroundColor ?? theme.colorTheme.barsBg, + shape: shape, + elevation: 3, + clipBehavior: .antiAlias, + color: effectiveBackgroundColor, child: InkWell( onTap: onTap, - child: Padding( - padding: padding.add(extraPadding), - child: switch (scrollable) { - false => reactionIndicator, - true => SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: reactionIndicator, - ), - }, + child: SingleChildScrollView( + padding: effectivePadding, + scrollDirection: .horizontal, + child: indicatorContent, ), ), ); diff --git a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_bubble_overlay.dart b/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_bubble_overlay.dart index 5e7e02fb21..7ef2202d63 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_bubble_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_bubble_overlay.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/reactions/indicator/reaction_indicator.dart'; import 'package:stream_chat_flutter/src/reactions/reaction_bubble_overlay.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// {@template reactionIndicatorBubbleOverlay} @@ -21,7 +20,6 @@ class ReactionIndicatorBubbleOverlay extends StatelessWidget { this.visible = true, this.reverse = false, this.anchorOffset = Offset.zero, - this.childSizeDelta = Offset.zero, this.reactionIndicatorBuilder = StreamReactionIndicator.builder, }); @@ -43,25 +41,13 @@ class ReactionIndicatorBubbleOverlay extends StatelessWidget { /// The offset to apply to the anchor position. final Offset anchorOffset; - /// The additional size delta to apply to the child widget for positioning. - final Offset childSizeDelta; - /// Builder for the reaction indicator widget. final ReactionIndicatorBuilder reactionIndicatorBuilder; @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); - final messageTheme = theme.getMessageTheme(reverse: reverse); - return ReactionBubbleOverlay( visible: visible, - childSizeDelta: childSizeDelta, - config: ReactionBubbleConfig( - fillColor: messageTheme.reactionsBackgroundColor, - borderColor: messageTheme.reactionsBorderColor, - maskColor: messageTheme.reactionsMaskColor, - ), anchor: ReactionBubbleAnchor( offset: anchorOffset, follower: AlignmentDirectional.bottomCenter, diff --git a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_icon_list.dart b/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_icon_list.dart deleted file mode 100644 index 51884d3e21..0000000000 --- a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_icon_list.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/misc/reaction_icon.dart'; - -/// {@template reactionIndicatorIconBuilder} -/// Function signature for building a custom reaction icon widget. -/// -/// This is used to customize how each reaction icon is displayed in the -/// [ReactionIndicatorIconList]. -/// -/// Parameters: -/// - [context]: The build context. -/// - [icon]: The reaction icon data containing type and selection state. -/// {@endtemplate} -typedef ReactionIndicatorIconBuilder = - Widget Function( - BuildContext context, - ReactionIndicatorIcon icon, - ); - -/// {@template reactionIndicatorIconList} -/// A widget that displays a list of reactionIcons that users have reacted with -/// on a message. -/// -/// See also: -/// - [StreamReactionIndicator], which is a higher-level widget that uses this -/// widget to display a reaction indicator in a modal or inline. -/// {@endtemplate} -class ReactionIndicatorIconList extends StatelessWidget { - /// {@macro reactionIndicatorIconList} - const ReactionIndicatorIconList({ - super.key, - required this.indicatorIcons, - ReactionIndicatorIconBuilder? iconBuilder, - }) : iconBuilder = iconBuilder ?? _defaultIconBuilder; - - /// The list of available reaction indicator icons. - final List indicatorIcons; - - /// The builder used to create the reaction indicator icons. - final ReactionIndicatorIconBuilder iconBuilder; - - static Widget _defaultIconBuilder( - BuildContext context, - ReactionIndicatorIcon icon, - ) { - return icon.build(context); - } - - @override - Widget build(BuildContext context) { - return Wrap( - spacing: 8, - runSpacing: 4, - runAlignment: WrapAlignment.center, - alignment: WrapAlignment.spaceAround, - crossAxisAlignment: WrapCrossAlignment.center, - children: [...indicatorIcons.map((icon) => iconBuilder(context, icon))], - ); - } -} - -/// {@template reactionIndicatorIcon} -/// A data class that represents a reaction icon within the reaction indicator. -/// -/// This class holds information about a specific reaction, such as its type, -/// whether it's currently selected by the user, and a builder function -/// to construct its visual representation. -/// {@endtemplate} -class ReactionIndicatorIcon { - /// {@macro reactionIndicatorIcon} - const ReactionIndicatorIcon({ - required this.type, - this.isSelected = false, - this.iconSize = 16, - required ReactionIconBuilder builder, - }) : _builder = builder; - - /// The unique identifier for the reaction type (e.g., "like", "love"). - final String type; - - /// A boolean indicating whether this reaction is currently selected by the - /// user. - final bool isSelected; - - /// The size of the reaction icon. - final double iconSize; - - /// Builds the actual widget for this reaction icon using the provided - /// [context], selection state, and icon size. - Widget build(BuildContext context) => _builder(context, isSelected, iconSize); - final ReactionIconBuilder _builder; -} diff --git a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart b/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart index d078eb4cff..a7d1b8f361 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart @@ -32,7 +32,7 @@ typedef ReactionPickerBuilder = /// A widget that displays a horizontal list of reaction icons that users can /// select to react to a message. /// -/// The reaction picker can be configured with custom reaction icons, padding, +/// The reaction picker can be configured with custom reaction types, padding, /// border radius, and can be made scrollable or static depending on the /// specific needs. /// {@endtemplate} @@ -42,15 +42,12 @@ class StreamReactionPicker extends StatelessWidget { super.key, this.onReactionPicked, required this.message, - required this.reactionIcons, - this.reactionIconBuilder, this.backgroundColor, - this.padding = const EdgeInsets.all(4), - this.scrollable = true, - this.borderRadius = const BorderRadius.all(Radius.circular(24)), + this.padding, + this.borderRadius, }); - /// Creates a [StreamReactionPicker] using the default reaction icons + /// Creates a [StreamReactionPicker] using the default reaction types /// provided by the [StreamChatConfiguration]. /// /// This is the recommended way to create a reaction picker @@ -63,21 +60,15 @@ class StreamReactionPicker extends StatelessWidget { Message message, OnReactionPicked? onReactionPicked, ) { - final config = StreamChatConfiguration.of(context); - final reactionIcons = config.reactionIcons; - final platform = Theme.of(context).platform; return switch (platform) { TargetPlatform.iOS || TargetPlatform.android => StreamReactionPicker( message: message, - reactionIcons: reactionIcons, onReactionPicked: onReactionPicked, ), _ => StreamReactionPicker( message: message, - scrollable: false, borderRadius: BorderRadius.zero, - reactionIcons: reactionIcons, onReactionPicked: onReactionPicked, ), }; @@ -86,86 +77,99 @@ class StreamReactionPicker extends StatelessWidget { /// Message to attach the reaction to. final Message message; - /// List of reaction icons to display. - final List reactionIcons; - /// {@macro onReactionPressed} final OnReactionPicked? onReactionPicked; - /// Optional custom builder for reaction picker icons. - final ReactionPickerIconBuilder? reactionIconBuilder; - /// Background color for the reaction picker. final Color? backgroundColor; /// Padding around the reaction picker. /// /// Defaults to `EdgeInsets.all(4)`. - final EdgeInsets padding; - - /// Whether the reaction picker should be scrollable. - /// - /// Defaults to `true`. - final bool scrollable; + final EdgeInsetsGeometry? padding; /// Border radius for the reaction picker. /// /// Defaults to a circular border with a radius of 24. - final BorderRadius? borderRadius; + final BorderRadiusGeometry? borderRadius; @override Widget build(BuildContext context) { + final icons = context.streamIcons; + final radius = context.streamRadius; + final spacing = context.streamSpacing; + final colorScheme = context.streamColorScheme; + + final effectivePadding = padding ?? EdgeInsetsDirectional.only(start: spacing.xxs); + final effectiveBorderRadius = borderRadius ?? BorderRadius.all(radius.xxxxl); + final effectiveBackgroundColor = backgroundColor ?? colorScheme.backgroundElevation2; + + final side = BorderSide(color: colorScheme.borderDefault); + final shape = RoundedSuperellipseBorder(borderRadius: effectiveBorderRadius, side: side); + + final config = StreamChatConfiguration.of(context); + final resolver = config.reactionIconResolver; + final reactionTypes = resolver.defaultReactions; + final ownReactions = [...?message.ownReactions]; final ownReactionsMap = {for (final it in ownReactions) it.type: it}; - final indicatorIcons = reactionIcons.map( - (reactionIcon) { - final reactionType = reactionIcon.type; - - return ReactionPickerIcon( - type: reactionType, - builder: reactionIcon.builder, - emojiCode: reactionIcon.emojiCode, - // If the reaction is present in ownReactions, it is selected. - isSelected: ownReactionsMap[reactionType] != null, - ); - }, + final reactionButtons = reactionTypes.map( + (type) => StreamEmojiButton( + key: Key(type), + size: .lg, + emoji: resolver.resolve(context, type), + // If the reaction is present in ownReactions, it is selected. + isSelected: ownReactionsMap[type] != null, + onPressed: () { + final reactionEmojiCode = resolver.emojiCode(type); + final pickedReaction = switch (ownReactionsMap[type]) { + final reaction? => reaction, + _ => Reaction(type: type, emojiCode: reactionEmojiCode), + }; + + return onReactionPicked?.call(pickedReaction); + }, + ), ); - final reactionPicker = ReactionPickerIconList( - iconBuilder: reactionIconBuilder, - reactionIcons: [...indicatorIcons], - onIconPicked: (reactionIcon) { - final reactionType = reactionIcon.type; - final reactionEmojiCode = reactionIcon.emojiCode; - final pickedReaction = switch (ownReactionsMap[reactionType]) { - final reaction? => reaction, - _ => Reaction(type: reactionType, emojiCode: reactionEmojiCode), - }; - - return onReactionPicked?.call(pickedReaction); - }, + final pickerContent = Row( + mainAxisSize: .min, + spacing: spacing.none, + children: [ + // TODO: Re-enable staggered animation when MessageWidget redesign is finalized. + ...reactionButtons, + StreamButton.icon( + key: const Key('add_reaction'), + size: .small, + type: .outline, + style: .secondary, + icon: icons.plusLarge, + onTap: () async { + final selectedReactions = ownReactionsMap.keys.toSet(); + final emoji = await StreamEmojiPickerSheet.show( + context: context, + selectedReactions: selectedReactions, + ); + + if (!context.mounted || emoji == null) return; + + final reaction = Reaction(type: emoji.shortName, emojiCode: emoji.emoji); + return onReactionPicked?.call(reaction); + }, + ), + ], ); - final isSinglePickerIcon = reactionIcons.length == 1; - final extraPadding = switch (isSinglePickerIcon) { - true => EdgeInsets.zero, - false => const EdgeInsets.symmetric(horizontal: 4), - }; - return Material( - borderRadius: borderRadius, - clipBehavior: Clip.antiAlias, - color: backgroundColor ?? StreamColors.transparent, - child: Padding( - padding: padding.add(extraPadding), - child: switch (scrollable) { - false => reactionPicker, - true => SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: reactionPicker, - ), - }, + shape: shape, + elevation: 3, + clipBehavior: .antiAlias, + color: effectiveBackgroundColor, + child: SingleChildScrollView( + padding: effectivePadding, + scrollDirection: .horizontal, + child: pickerContent, ), ); } diff --git a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_bubble_overlay.dart b/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_bubble_overlay.dart index d49fee015d..5b7da48e6c 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_bubble_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_bubble_overlay.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/reactions/picker/reaction_picker.dart'; import 'package:stream_chat_flutter/src/reactions/reaction_bubble_overlay.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// {@template reactionPickerBubbleOverlay} @@ -21,7 +20,6 @@ class ReactionPickerBubbleOverlay extends StatelessWidget { this.visible = true, this.reverse = false, this.anchorOffset = Offset.zero, - this.childSizeDelta = Offset.zero, this.reactionPickerBuilder = StreamReactionPicker.builder, }); @@ -46,26 +44,14 @@ class ReactionPickerBubbleOverlay extends StatelessWidget { /// The offset to apply to the anchor position. final Offset anchorOffset; - /// The additional size delta to apply to the child widget for positioning. - final Offset childSizeDelta; - @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); - final colorTheme = theme.colorTheme; - return ReactionBubbleOverlay( visible: visible, - childSizeDelta: childSizeDelta, - config: ReactionBubbleConfig( - fillColor: colorTheme.barsBg, - maskColor: Colors.transparent, - borderColor: Colors.transparent, - ), anchor: ReactionBubbleAnchor( offset: anchorOffset, - follower: AlignmentDirectional.bottomCenter, - target: AlignmentDirectional(reverse ? -1 : 1, -1), + follower: AlignmentDirectional(reverse ? 1 : -1, 1), + target: AlignmentDirectional(reverse ? 1 : -1, -1), ), reaction: reactionPickerBuilder.call(context, message, onReactionPicked), child: child, diff --git a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_icon_list.dart b/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_icon_list.dart deleted file mode 100644 index d692798c5c..0000000000 --- a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_icon_list.dart +++ /dev/null @@ -1,266 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:ezanimation/ezanimation.dart'; -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/misc/reaction_icon.dart'; - -/// {@template onReactionIconPicked} -/// Callback called when a reaction icon is pressed. -/// {@endtemplate} -typedef OnReactionIconPicked = ValueSetter; - -/// {@template onReactionPickerIconPressed} -/// Callback called when a reaction picker icon is pressed. -/// {@endtemplate} -typedef OnReactionPickerIconPressed = ValueSetter; - -/// {@template reactionPickerIconBuilder} -/// Function signature for building a custom reaction icon widget. -/// -/// This is used to customize how each reaction icon is displayed in the -/// [ReactionPickerIconList]. -/// -/// Parameters: -/// - [context]: The build context. -/// - [icon]: The reaction icon data containing type and selection state. -/// - [onPressed]: Callback when the reaction icon is pressed. -/// {@endtemplate} -typedef ReactionPickerIconBuilder = - Widget Function( - BuildContext context, - ReactionPickerIcon icon, - VoidCallback? onPressed, - ); - -/// {@template reactionPickerIconList} -/// A widget that displays a list of reactionIcons that can be picked by a user. -/// -/// This widget shows a row of reaction icons with animated entry. When a user -/// taps on a reaction icon, the [onIconPicked] callback is invoked with the -/// selected reaction. -/// -/// The reactions displayed are configured via [reactionIcons]. -/// -/// See also: -/// - [StreamReactionPicker], which is a higher-level widget that uses this -/// widget to display a reaction picker in a modal or inline. -/// {@endtemplate} -class ReactionPickerIconList extends StatefulWidget { - /// {@macro reactionPickerIconList} - const ReactionPickerIconList({ - super.key, - this.onIconPicked, - required this.reactionIcons, - ReactionPickerIconBuilder? iconBuilder, - }) : iconBuilder = iconBuilder ?? _defaultIconBuilder; - - /// The list of available reaction picker icons. - final List reactionIcons; - - /// The builder used to create the reaction picker icons. - final ReactionPickerIconBuilder iconBuilder; - - /// {@macro onReactionIconPicked} - final OnReactionIconPicked? onIconPicked; - - static Widget _defaultIconBuilder( - BuildContext context, - ReactionPickerIcon icon, - VoidCallback? onPressed, - ) { - return ReactionIconButton( - icon: icon, - onPressed: onPressed, - ); - } - - @override - State createState() => _ReactionPickerIconListState(); -} - -class _ReactionPickerIconListState extends State { - List _iconAnimations = []; - - void _triggerAnimations() async { - for (final animation in _iconAnimations) { - if (mounted) animation.start(); - // Add a small delay between the start of each animation. - await Future.delayed(const Duration(milliseconds: 100)); - } - } - - void _dismissAnimations() { - for (final animation in _iconAnimations) { - animation.stop(); - } - } - - void _disposeAnimations() { - for (final animation in _iconAnimations) { - animation.dispose(); - } - } - - @override - void initState() { - super.initState(); - _iconAnimations = List.generate( - widget.reactionIcons.length, - (index) => EzAnimation.tween( - Tween(begin: 0.0, end: 1.0), - kThemeAnimationDuration, - curve: Curves.easeInOutBack, - ), - ); - - // Trigger animations at the end of the frame to avoid jank. - WidgetsBinding.instance.endOfFrame.then((_) => _triggerAnimations()); - } - - @override - void didUpdateWidget(covariant ReactionPickerIconList oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.reactionIcons.length != widget.reactionIcons.length) { - // Dismiss and dispose old animations. - _dismissAnimations(); - _disposeAnimations(); - - // Initialize new animations. - _iconAnimations = List.generate( - widget.reactionIcons.length, - (index) => EzAnimation.tween( - Tween(begin: 0.0, end: 1.0), - kThemeAnimationDuration, - curve: Curves.easeInOutBack, - ), - ); - - // Trigger animations at the end of the frame to avoid jank. - WidgetsBinding.instance.endOfFrame.then((_) => _triggerAnimations()); - } - } - - @override - void dispose() { - _dismissAnimations(); - _disposeAnimations(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final child = Wrap( - spacing: 4, - runSpacing: 4, - runAlignment: WrapAlignment.center, - alignment: WrapAlignment.spaceAround, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - ...widget.reactionIcons.mapIndexed((index, icon) { - final animation = _iconAnimations[index]; - return AnimatedBuilder( - animation: animation, - builder: (context, child) => Transform.scale( - scale: animation.value, - child: child, - ), - child: Builder( - builder: (context) { - final icon = widget.reactionIcons[index]; - final onPressed = switch (widget.onIconPicked) { - final onPicked? => () => onPicked(icon), - _ => null, - }; - - return widget.iconBuilder(context, icon, onPressed); - }, - ), - ); - }), - ], - ); - - return TweenAnimationBuilder( - tween: Tween(begin: 0, end: 1), - curve: Curves.easeOutBack, - duration: const Duration(milliseconds: 335), - builder: (context, scale, child) { - return Transform.scale(scale: scale, child: child); - }, - child: child, - ); - } -} - -/// {@template reactionPickerIcon} -/// A data class that represents a reaction icon within the reaction picker. -/// -/// This class holds information about a specific reaction, such as its type, -/// whether it's currently selected by the user, and a builder function -/// to construct its visual representation. -/// {@endtemplate} -class ReactionPickerIcon { - /// {@macro reactionPickerIcon} - const ReactionPickerIcon({ - required this.type, - this.emojiCode, - this.isSelected = false, - this.iconSize = 24, - required ReactionIconBuilder builder, - }) : _builder = builder; - - /// The unique identifier for the reaction type (e.g., "like", "love"). - final String type; - - /// Optional emoji code for the reaction. - /// - /// Used to display a custom emoji in the notification. - final String? emojiCode; - - /// A boolean indicating whether this reaction is currently selected by the - /// user. - final bool isSelected; - - /// The size of the reaction icon. - final double iconSize; - - /// Builds the actual widget for this reaction icon using the provided - /// [context], selection state, and icon size. - Widget build(BuildContext context) => _builder(context, isSelected, iconSize); - final ReactionIconBuilder _builder; -} - -/// {@template reactionIconButton} -/// A button that displays a reaction icon. -/// -/// This button is used in the reaction picker to display individual reaction -/// options. -/// {@endtemplate} -class ReactionIconButton extends StatelessWidget { - /// {@macro reactionIconButton} - const ReactionIconButton({ - super.key, - required this.icon, - this.onPressed, - }); - - /// The reaction icon to display. - final ReactionPickerIcon icon; - - /// Callback triggered when the reaction picker icon is pressed. - final VoidCallback? onPressed; - - @override - Widget build(BuildContext context) { - return IconButton( - key: Key(icon.type), - iconSize: icon.iconSize, - onPressed: onPressed, - icon: icon.build(context), - padding: const EdgeInsets.all(4), - style: IconButton.styleFrom( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - minimumSize: Size.square(icon.iconSize), - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble.dart deleted file mode 100644 index 43cb68814c..0000000000 --- a/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble.dart +++ /dev/null @@ -1,309 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template streamReactionBubble} -/// Creates a reaction bubble that displays over messages. -/// {@endtemplate} -class StreamReactionBubble extends StatelessWidget { - /// {@macro streamReactionBubble} - const StreamReactionBubble({ - super.key, - required this.reactions, - required this.borderColor, - required this.backgroundColor, - required this.maskColor, - this.reverse = false, - this.flipTail = false, - this.highlightOwnReactions = true, - this.tailCirclesSpacing = 0, - }); - - /// Reactions to show - final List reactions; - - /// Border color of bubble - final Color borderColor; - - /// Background color of bubble - final Color backgroundColor; - - /// Mask color - final Color maskColor; - - /// Reverse for other side - final bool reverse; - - /// Reverse tail for other side - final bool flipTail; - - /// Flag for highlighting own reactions - final bool highlightOwnReactions; - - /// Spacing for tail circles - final double tailCirclesSpacing; - - @override - Widget build(BuildContext context) { - final reactionIcons = StreamChatConfiguration.of(context).reactionIcons; - final totalReactions = reactions.length; - final offset = totalReactions > 1 ? 16.0.mirrorConditionally(flipTail) : 2.0; - return Stack( - alignment: Alignment.center, - children: [ - Transform.translate( - offset: Offset(-offset, 0), - child: Container( - padding: const EdgeInsets.all(2), - decoration: BoxDecoration( - color: maskColor, - borderRadius: const BorderRadius.all(Radius.circular(26)), - ), - child: Container( - padding: EdgeInsets.symmetric( - vertical: 8, - horizontal: totalReactions > 1 ? 12.0 : 8.0, - ), - decoration: BoxDecoration( - color: backgroundColor, - border: Border.all(color: borderColor), - borderRadius: const BorderRadius.all(Radius.circular(24)), - ), - child: Wrap( - spacing: 8, - runSpacing: 4, - runAlignment: WrapAlignment.center, - alignment: WrapAlignment.spaceAround, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - ...reactions.map( - (reaction) => _buildReaction( - context, - reaction, - reactionIcons, - ), - ), - ], - ), - ), - ), - ), - Positioned( - bottom: 2, - left: reverse ? null : 13, - right: reverse ? 13 : null, - child: _buildReactionsTail(context), - ), - ], - ); - } - - Widget _buildReaction( - BuildContext context, - Reaction reaction, - List reactionIcons, - ) { - final reactionIcon = reactionIcons.firstWhere( - (it) => it.type == reaction.type, - orElse: () => const StreamReactionIcon.unknown(), - ); - - final currentUser = StreamChat.of(context).currentUser; - final isMyReaction = reaction.userId == currentUser?.id; - final isHighlighted = highlightOwnReactions && isMyReaction; - - return reactionIcon.builder(context, isHighlighted, 16); - } - - Widget _buildReactionsTail(BuildContext context) { - final tail = CustomPaint( - painter: ReactionBubblePainter( - backgroundColor, - borderColor, - maskColor, - tailCirclesSpace: tailCirclesSpacing, - flipTail: !flipTail, - numberOfReactions: reactions.length, - ), - ); - return tail; - } -} - -/// Painter widget for a reaction bubble -class ReactionBubblePainter extends CustomPainter { - /// Constructor for creating a [ReactionBubblePainter] - ReactionBubblePainter( - this.color, - this.borderColor, - this.maskColor, { - this.tailCirclesSpace = 0, - this.flipTail = false, - this.numberOfReactions = 0, - }); - - /// Color of bubble - final Color color; - - /// Border color of bubble - final Color borderColor; - - /// Mask color - final Color maskColor; - - /// Tail circle space - final double tailCirclesSpace; - - /// Flip tail - final bool flipTail; - - /// Number of reactions on the page - final int numberOfReactions; - - @override - void paint(Canvas canvas, Size size) { - _drawOvalMask(size, canvas); - - _drawMask(size, canvas); - - _drawOval(size, canvas); - - _drawOvalBorder(size, canvas); - - _drawArc(size, canvas); - - _drawBorder(size, canvas); - } - - void _drawOvalMask(Size size, Canvas canvas) { - final paint = Paint() - ..color = maskColor - ..style = PaintingStyle.fill; - - final path = Path() - ..addOval( - Rect.fromCircle( - center: - const Offset(4, 3).mirrorConditionally(flipTail) + - Offset(tailCirclesSpace, tailCirclesSpace).mirrorConditionally(flipTail), - radius: 4, - ), - ); - canvas.drawPath(path, paint); - } - - void _drawOvalBorder(Size size, Canvas canvas) { - final paint = Paint() - ..color = borderColor - ..strokeWidth = 1 - ..style = PaintingStyle.stroke; - - final path = Path() - ..addOval( - Rect.fromCircle( - center: - const Offset(4, 3).mirrorConditionally(flipTail) + - Offset(tailCirclesSpace, tailCirclesSpace).mirrorConditionally(flipTail), - radius: 2, - ), - ); - canvas.drawPath(path, paint); - } - - void _drawOval(Size size, Canvas canvas) { - final paint = Paint() - ..color = color - ..strokeWidth = 1; - - final path = Path() - ..addOval( - Rect.fromCircle( - center: - const Offset(4, 3).mirrorConditionally(flipTail) + - Offset(tailCirclesSpace, tailCirclesSpace).mirrorConditionally(flipTail), - radius: 2, - ), - ); - canvas.drawPath(path, paint); - } - - void _drawBorder(Size size, Canvas canvas) { - final paint = Paint() - ..color = borderColor - ..strokeWidth = 1 - ..style = PaintingStyle.stroke; - - const dy = -2.2; - final startAngle = flipTail ? -0.1 : 1.1; - final sweepAngle = flipTail ? -1.2 : (numberOfReactions > 1 ? 1.2 : 0.9); - final path = Path() - ..addArc( - Rect.fromCircle( - center: const Offset(1, dy).mirrorConditionally(flipTail), - radius: 4, - ), - -pi * startAngle, - -pi / sweepAngle, - ); - canvas.drawPath(path, paint); - } - - void _drawArc(Size size, Canvas canvas) { - final paint = Paint() - ..color = color - ..strokeWidth = 1; - - const dy = -2.2; - final startAngle = flipTail ? -0.0 : 1.0; - final sweepAngle = flipTail ? -1.3 : 1.3; - final path = Path() - ..addArc( - Rect.fromCircle( - center: const Offset(1, dy).mirrorConditionally(flipTail), - radius: 4, - ), - -pi * startAngle, - -pi * sweepAngle, - ); - canvas.drawPath(path, paint); - } - - void _drawMask(Size size, Canvas canvas) { - final paint = Paint() - ..color = maskColor - ..strokeWidth = 1 - ..style = PaintingStyle.fill; - - const dy = -2.2; - final startAngle = flipTail ? -0.1 : 1.1; - final sweepAngle = flipTail ? -1.2 : 1.2; - final path = Path() - ..addArc( - Rect.fromCircle( - center: const Offset(1, dy).mirrorConditionally(flipTail), - radius: 6, - ), - -pi * startAngle, - -pi / sweepAngle, - ); - canvas.drawPath(path, paint); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) => true; -} - -/// Extension on [Offset] -extension YTransformer on Offset { - /// Flips x coordinate when flip is true - // ignore: avoid_positional_boolean_parameters - Offset mirrorConditionally(bool flip) => Offset(flip ? -dx : dx, dy); -} - -/// Extension on [Offset] -extension IntTransformer on double { - /// Flips x coordinate when flip is true - // ignore: avoid_positional_boolean_parameters - double mirrorConditionally(bool flip) => flip ? -this : this; -} diff --git a/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble_overlay.dart b/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble_overlay.dart index 94863fc78d..2a5087ecf2 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble_overlay.dart @@ -1,18 +1,5 @@ -// ignore_for_file: cascade_invocations - -import 'dart:math' as math; - import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; -import 'package:stream_chat_flutter/src/misc/size_change_listener.dart'; - -/// Signature for building a custom ReactionBubble widget. -typedef ReactionBubbleBuilder = - Widget Function( - BuildContext context, - ReactionBubbleConfig config, - Widget child, - ); /// Defines the anchor settings for positioning a ReactionBubble relative to a /// target widget. @@ -56,16 +43,13 @@ class ReactionBubbleAnchor { } /// An overlay widget that displays a reaction bubble near a child widget. -class ReactionBubbleOverlay extends StatefulWidget { +class ReactionBubbleOverlay extends StatelessWidget { /// Creates a new instance of [ReactionBubbleOverlay]. const ReactionBubbleOverlay({ super.key, this.visible = true, required this.child, required this.reaction, - this.childSizeDelta = Offset.zero, - this.builder = _defaultBuilder, - this.config = const ReactionBubbleConfig(), this.anchor = const ReactionBubbleAnchor.topEnd(), }); @@ -78,296 +62,28 @@ class ReactionBubbleOverlay extends StatefulWidget { /// Whether the reaction bubble is visible. final bool visible; - /// Optional adjustment to the child's reported size. - final Offset childSizeDelta; - - /// The configuration used for rendering the reaction bubble. - final ReactionBubbleConfig config; - /// The anchor configuration to control bubble positioning. final ReactionBubbleAnchor anchor; - /// The builder used to create the bubble appearance. - final ReactionBubbleBuilder builder; - - static Widget _defaultBuilder( - BuildContext context, - ReactionBubbleConfig config, - Widget child, - ) { - return RepaintBoundary( - child: CustomPaint( - painter: ReactionBubblePainter(config: config), - child: child, - ), - ); - } - - @override - State createState() => _ReactionBubbleOverlayState(); -} - -class _ReactionBubbleOverlayState extends State { - Size? _childSize; - - /// Calculates the alignment for the bubble tail relative to the bubble rect. - AlignmentGeometry _calculateTailAlignment({ - required Size childSize, - required Rect bubbleRect, - required Size availableSpace, - bool reverse = false, - }) { - final childEdgeX = switch (reverse) { - true => availableSpace.width - childSize.width, - false => childSize.width, - }; - - final idealBubbleLeft = childEdgeX - (bubbleRect.width / 2); - final maxLeft = availableSpace.width - bubbleRect.width; - final actualBubbleLeft = idealBubbleLeft.clamp(0, math.max(0, maxLeft)); - final tailOffset = childEdgeX - actualBubbleLeft; - - if (tailOffset == 0) return AlignmentDirectional.bottomCenter; - return AlignmentDirectional((tailOffset * 2 / bubbleRect.width) - 1, 1); - } - @override Widget build(BuildContext context) { - final child = SizeChangeListener( - onSizeChanged: (size) => setState(() => _childSize = size), - child: widget.child, - ); - - final childSize = _childSize; - // If the child size is not available or the overlay should not be visible, - // return the child without any overlay. - if (childSize == null || !widget.visible) return child; + // If the overlay should not be visible, return the child without any overlay. + if (!visible) return child; - final alignment = widget.anchor; + final alignment = anchor; final direction = Directionality.maybeOf(context); final targetAlignment = alignment.target.resolve(direction); final followerAlignment = alignment.follower.resolve(direction); - final availableSpace = MediaQuery.sizeOf(context); - - final reverse = targetAlignment.x < 0; - final config = widget.config.copyWith( - flipTail: reverse, - tailAlignment: (bubbleRect) { - final alignment = _calculateTailAlignment( - reverse: reverse, - bubbleRect: bubbleRect, - availableSpace: availableSpace, - childSize: childSize + widget.childSizeDelta, - ); - - return alignment.resolve(direction); - }, - ); return PortalTarget( anchor: Aligned( target: targetAlignment, follower: followerAlignment, - offset: widget.anchor.offset, - shiftToWithinBound: widget.anchor.shiftToWithinBound, + offset: anchor.offset, + shiftToWithinBound: anchor.shiftToWithinBound, ), - portalFollower: widget.builder(context, config, widget.reaction), + portalFollower: reaction, child: child, ); } } - -/// Defines the visual configuration of a ReactionBubble. -class ReactionBubbleConfig { - /// Creates a new instance of [ReactionBubbleConfig] with default values. - const ReactionBubbleConfig({ - this.flipTail = false, - this.fillColor, - this.maskColor, - this.borderColor, - this.maskWidth = 2.0, - this.borderWidth = 1.0, - this.bigTailCircleRadius = 4.0, - this.smallTailCircleRadius = 2.0, - this.tailAlignment = _defaultTailAlignment, - }); - - /// Whether to flip the tail horizontally. - final bool flipTail; - - /// Fill color of the bubble. - final Color? fillColor; - - /// Mask color of the bubble (used for visual masking). - final Color? maskColor; - - /// Border color of the bubble. - final Color? borderColor; - - /// Width of the mask stroke. - final double maskWidth; - - /// Width of the border stroke. - final double borderWidth; - - /// Radius of the larger circle at the bubble tail. - final double bigTailCircleRadius; - - /// Radius of the smaller circle at the bubble tail. - final double smallTailCircleRadius; - - /// Function that defines the alignment of the tail within the bubble rect. - final Alignment Function(Rect) tailAlignment; - - static Alignment _defaultTailAlignment(Rect rect) => Alignment.bottomCenter; - - /// The total height contribution of the bubble tail. - double get tailHeight => bigTailCircleRadius * 2 + smallTailCircleRadius * 2; - - /// Returns a copy of this config with optional overrides. - ReactionBubbleConfig copyWith({ - bool? flipTail, - Color? fillColor, - Color? maskColor, - Color? borderColor, - double? maskWidth, - double? borderWidth, - double? bigTailCircleRadius, - double? smallTailCircleRadius, - Alignment Function(Rect)? tailAlignment, - }) { - return ReactionBubbleConfig( - flipTail: flipTail ?? this.flipTail, - fillColor: fillColor ?? this.fillColor, - maskColor: maskColor ?? this.maskColor, - borderColor: borderColor ?? this.borderColor, - maskWidth: maskWidth ?? this.maskWidth, - borderWidth: borderWidth ?? this.borderWidth, - bigTailCircleRadius: bigTailCircleRadius ?? this.bigTailCircleRadius, - smallTailCircleRadius: smallTailCircleRadius ?? this.smallTailCircleRadius, - tailAlignment: tailAlignment ?? this.tailAlignment, - ); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - return other is ReactionBubbleConfig && - runtimeType == other.runtimeType && - flipTail == other.flipTail && - fillColor == other.fillColor && - maskColor == other.maskColor && - borderColor == other.borderColor && - maskWidth == other.maskWidth && - borderWidth == other.borderWidth && - bigTailCircleRadius == other.bigTailCircleRadius && - smallTailCircleRadius == other.smallTailCircleRadius && - tailAlignment == other.tailAlignment; - } - - @override - int get hashCode => - flipTail.hashCode ^ - fillColor.hashCode ^ - maskColor.hashCode ^ - borderColor.hashCode ^ - maskWidth.hashCode ^ - borderWidth.hashCode ^ - bigTailCircleRadius.hashCode ^ - smallTailCircleRadius.hashCode ^ - tailAlignment.hashCode; -} - -/// A CustomPainter that draws a ReactionBubble based on a ReactionBubbleConfig. -class ReactionBubblePainter extends CustomPainter { - /// Creates a [ReactionBubblePainter] with the specified configuration. - ReactionBubblePainter({ - this.config = const ReactionBubbleConfig(), - }) : _fillPaint = Paint() - ..color = config.fillColor ?? Colors.white - ..style = PaintingStyle.fill, - _maskPaint = Paint() - ..color = config.maskColor ?? Colors.white - ..style = PaintingStyle.fill, - _borderPaint = Paint() - ..color = config.borderColor ?? Colors.black - ..style = PaintingStyle.stroke - ..strokeWidth = config.borderWidth; - - /// Configuration used to style the bubble. - final ReactionBubbleConfig config; - - final Paint _fillPaint; - final Paint _borderPaint; - final Paint _maskPaint; - - @override - void paint(Canvas canvas, Size size) { - final tailHeight = config.tailHeight; - final fullHeight = size.height + tailHeight; - final bubbleHeight = fullHeight - tailHeight; - final bubbleWidth = size.width; - - final bubbleRect = RRect.fromRectAndRadius( - Rect.fromLTRB(0, 0, bubbleWidth, bubbleHeight), - Radius.circular(bubbleHeight / 2), - ); - - final alignment = config.tailAlignment.call(bubbleRect.outerRect); - final bigTailCircleCenter = alignment.withinRect(bubbleRect.tallMiddleRect); - - final bigTailCircleRect = Rect.fromCircle( - center: bigTailCircleCenter, - radius: config.bigTailCircleRadius, - ); - - final smallTailCircleOffset = Offset( - config.flipTail ? bigTailCircleRect.right : bigTailCircleRect.left, - bigTailCircleRect.bottom + config.smallTailCircleRadius, - ); - - final smallTailCircleRect = Rect.fromCircle( - center: smallTailCircleOffset, - radius: config.smallTailCircleRadius, - ); - - final reactionBubbleMaskPath = _buildCombinedPath( - bubbleRect.inflate(config.maskWidth), - bigTailCircleRect.inflate(config.maskWidth), - smallTailCircleRect.inflate(config.maskWidth), - ); - - canvas.drawPath(reactionBubbleMaskPath, _maskPaint); - - final reactionBubblePath = _buildCombinedPath( - bubbleRect, - bigTailCircleRect, - smallTailCircleRect, - ); - - canvas.drawPath(reactionBubblePath, _borderPaint); - canvas.drawPath(reactionBubblePath, _fillPaint); - } - - /// Builds a combined path of the bubble and tail circles. - Path _buildCombinedPath( - RRect bubble, - Rect bigCircle, - Rect smallCircle, - ) { - final bubblePath = Path()..addRRect(bubble); - final bigTailPath = Path()..addOval(bigCircle); - final smallTailPath = Path()..addOval(smallCircle); - - return Path.combine( - PathOperation.union, - Path.combine(PathOperation.union, bubblePath, bigTailPath), - smallTailPath, - ); - } - - @override - bool shouldRepaint(covariant ReactionBubblePainter oldDelegate) { - return true; - } -} diff --git a/packages/stream_chat_flutter/lib/src/reactions/user_reactions.dart b/packages/stream_chat_flutter/lib/src/reactions/user_reactions.dart index 7d3244508d..654ca829e9 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/user_reactions.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/user_reactions.dart @@ -1,13 +1,11 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; -import 'package:stream_chat_flutter/src/misc/reaction_icon.dart'; -import 'package:stream_chat_flutter/src/reactions/indicator/reaction_indicator_icon_list.dart'; -import 'package:stream_chat_flutter/src/reactions/reaction_bubble_overlay.dart'; import 'package:stream_chat_flutter/src/stream_chat_configuration.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template streamUserReactions} /// A widget that displays the reactions of a user to a message. @@ -100,13 +98,7 @@ class _UserReactionItem extends StatelessWidget { final theme = StreamChatTheme.of(context); final messageTheme = theme.getMessageTheme(reverse: isCurrentUserReaction); - final config = StreamChatConfiguration.of(context); - final reactionIcons = config.reactionIcons; - - final reactionIcon = reactionIcons.firstWhere( - (it) => it.type == reaction.type, - orElse: () => const StreamReactionIcon.unknown(), - ); + final resolver = StreamChatConfiguration.of(context).reactionIconResolver; return Column( mainAxisSize: MainAxisSize.min, @@ -130,29 +122,24 @@ class _UserReactionItem extends StatelessWidget { bottom: 8, end: isCurrentUserReaction ? null : 0, start: isCurrentUserReaction ? 0 : null, - child: IgnorePointer( - child: RepaintBoundary( - child: CustomPaint( - painter: ReactionBubblePainter( - config: ReactionBubbleConfig( - flipTail: isCurrentUserReaction, - fillColor: messageTheme.reactionsBackgroundColor, - borderColor: messageTheme.reactionsBorderColor, - maskColor: messageTheme.reactionsMaskColor, - ), - ), - child: Padding( - padding: const EdgeInsets.all(8), - child: ReactionIndicatorIconList( - indicatorIcons: [ - ReactionIndicatorIcon( - type: reactionIcon.type, - isSelected: isCurrentUserReaction, - builder: reactionIcon.builder, - ), - ], - ), + child: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: messageTheme.reactionsMaskColor, + borderRadius: const BorderRadius.all(Radius.circular(26)), + ), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: messageTheme.reactionsBackgroundColor, + border: Border.all( + color: messageTheme.reactionsBorderColor ?? Colors.transparent, ), + borderRadius: const BorderRadius.all(Radius.circular(24)), + ), + child: StreamEmoji( + size: StreamEmojiSize.sm, + emoji: resolver.resolve(context, reaction.type), ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index b24fdbdca2..158980f0ad 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter_portal/flutter_portal.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/video/vlc/vlc_manager.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -155,36 +154,34 @@ class StreamChatState extends State { @override Widget build(BuildContext context) { final theme = _getTheme(context, widget.streamChatThemeData); - return Portal( - child: StreamChatConfiguration( - data: streamChatConfigData, - child: StreamChatTheme( - data: theme, - child: Builder( - builder: (context) { - final materialTheme = Theme.of(context); - final streamTheme = StreamChatTheme.of(context); - return Theme( - data: materialTheme.copyWith( - primaryIconTheme: streamTheme.primaryIconTheme, - colorScheme: materialTheme.colorScheme.copyWith( - secondary: streamTheme.colorTheme.accentPrimary, - ), + return StreamChatConfiguration( + data: streamChatConfigData, + child: StreamChatTheme( + data: theme, + child: Builder( + builder: (context) { + final materialTheme = Theme.of(context); + final streamTheme = StreamChatTheme.of(context); + return Theme( + data: materialTheme.copyWith( + primaryIconTheme: streamTheme.primaryIconTheme, + colorScheme: materialTheme.colorScheme.copyWith( + secondary: streamTheme.colorTheme.accentPrimary, ), - child: StreamChatCore( - client: client, - onBackgroundEventReceived: widget.onBackgroundEventReceived, - backgroundKeepAlive: widget.backgroundKeepAlive, - connectivityStream: widget.connectivityStream, - child: Builder( - builder: (context) { - return widget.child ?? const Empty(); - }, - ), + ), + child: StreamChatCore( + client: client, + onBackgroundEventReceived: widget.onBackgroundEventReceived, + backgroundKeepAlive: widget.backgroundKeepAlive, + connectivityStream: widget.connectivityStream, + child: Builder( + builder: (context) { + return widget.child ?? const Empty(); + }, ), - ); - }, - ), + ), + ); + }, ), ), ); diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart b/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart index f25fbaede7..2673bca8f5 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart @@ -161,7 +161,7 @@ class StreamChatConfigurationData { Widget loadingIndicator = const StreamLoadingIndicator(), Widget Function(BuildContext, User)? defaultUserImage, Widget Function(BuildContext, User)? placeholderUserImage, - List? reactionIcons, + ReactionIconResolver? reactionIconResolver, bool? enforceUniqueReactions, bool draftMessagesEnabled = false, MessagePreviewFormatter? messagePreviewFormatter, @@ -170,7 +170,7 @@ class StreamChatConfigurationData { loadingIndicator: loadingIndicator, defaultUserImage: defaultUserImage ?? _defaultUserImage, placeholderUserImage: placeholderUserImage, - reactionIcons: reactionIcons ?? StreamReactionIcon.defaultReactions, + reactionIconResolver: reactionIconResolver ?? const DefaultReactionIconResolver(), enforceUniqueReactions: enforceUniqueReactions ?? true, draftMessagesEnabled: draftMessagesEnabled, messagePreviewFormatter: messagePreviewFormatter ?? MessagePreviewFormatter(), @@ -181,7 +181,7 @@ class StreamChatConfigurationData { required this.loadingIndicator, required this.defaultUserImage, required this.placeholderUserImage, - required this.reactionIcons, + required this.reactionIconResolver, required this.enforceUniqueReactions, required this.draftMessagesEnabled, required this.messagePreviewFormatter, @@ -193,13 +193,13 @@ class StreamChatConfigurationData { Widget? loadingIndicator, Widget Function(BuildContext, User)? defaultUserImage, Widget Function(BuildContext, User)? placeholderUserImage, - List? reactionIcons, + ReactionIconResolver? reactionIconResolver, bool? enforceUniqueReactions, bool? draftMessagesEnabled, MessagePreviewFormatter? messagePreviewFormatter, }) { return StreamChatConfigurationData( - reactionIcons: reactionIcons ?? this.reactionIcons, + reactionIconResolver: reactionIconResolver ?? this.reactionIconResolver, defaultUserImage: defaultUserImage ?? this.defaultUserImage, placeholderUserImage: placeholderUserImage ?? this.placeholderUserImage, loadingIndicator: loadingIndicator ?? this.loadingIndicator, @@ -223,8 +223,11 @@ class StreamChatConfigurationData { /// The widget that will be built when the user image is loading. final Widget Function(BuildContext, User)? placeholderUserImage; - /// Assets used for rendering reactions. - final List reactionIcons; + /// The resolver used to convert reaction types into widgets and to + /// provide the list of supported/default reaction types. + /// + /// Defaults to [DefaultReactionIconResolver]. + final ReactionIconResolver reactionIconResolver; /// Whether a new reaction should replace the existing one. final bool enforceUniqueReactions; diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 4df35aad63..248d646b99 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -13,7 +13,15 @@ export 'package:stream_core_flutter/stream_core_flutter.dart' StreamComponentFactory, StreamContextMenu, StreamContextMenuAction, - StreamContextMenuSeparator; + StreamContextMenuSeparator, + StreamEmoji, + StreamEmojiButton, + StreamEmojiChipBar, + StreamEmojiChipItem, + StreamEmojiSize, + StreamEmojiData, + StreamEmojiPickerSheet, + streamSupportedEmojis; export 'src/ai_assistant/ai_typing_indicator_view.dart'; export 'src/ai_assistant/stream_typewriter_builder.dart'; @@ -106,7 +114,7 @@ export 'src/misc/date_divider.dart'; export 'src/misc/info_tile.dart'; export 'src/misc/markdown_message.dart'; export 'src/misc/option_list_tile.dart'; -export 'src/misc/reaction_icon.dart'; +export 'src/misc/reaction_icon_resolver.dart'; export 'src/misc/stream_modal.dart'; export 'src/misc/stream_neumorphic_button.dart'; export 'src/misc/swipeable.dart'; @@ -120,11 +128,9 @@ export 'src/poll/stream_poll_option_votes_dialog.dart'; export 'src/poll/stream_poll_options_dialog.dart'; export 'src/poll/stream_poll_results_dialog.dart'; export 'src/poll/stream_poll_text_field.dart'; +export 'src/reactions/detail/reaction_detail_sheet.dart'; export 'src/reactions/indicator/reaction_indicator.dart'; -export 'src/reactions/indicator/reaction_indicator_icon_list.dart'; export 'src/reactions/picker/reaction_picker.dart'; -export 'src/reactions/picker/reaction_picker_icon_list.dart'; -export 'src/reactions/reaction_bubble.dart'; export 'src/reactions/user_reactions.dart'; export 'src/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart'; export 'src/scroll_view/channel_scroll_view/stream_channel_grid_view.dart'; diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index fe4ccfe430..f36439211b 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 4e7e22b4b61e9b9569e2933407d49fff370e6bec + ref: 490feb000a1b4a2f6baa56621dc8941363195742 path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_dark.png index 1fc77511d4c1e4ec9b22c22cae3aa8a4c3e2999a..1eeb82f5cb77eed7aa80a5c433937da18150af14 100644 GIT binary patch literal 4802 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 literal 4823 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 IG5>G>1wW-`3jhEB diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_light.png index d16e2d5884eb3d606366b44909efeacc815e1439..87e6cfd76f0b36f33dde417a5c4c6cb4e2ae7e86 100644 GIT binary patch literal 5575 zcmeI0X;f3$md9@bh*$y!nFItx1q7K9WC*4dL;;mKAVVmEf`-97X2KA(ln{haP7E>> zARt3PWE2P>Q$!^gWDbzRFpn}LL-MX&^i*EZUVrGd>VCL)oqO*&=bnB3d+*=A zv6fd&`431O004mh@+D(y0DvR{0MudsKJbpk{3`-@;r2g&*>*p8gzxwG-iG*Fo1OyF9u@9zKXRztiTogmy}C7d=C)N0S; z0aZ%ZAE2AH5q;=ymmjECUOHMKDwJ3#Qj`-kbnfSFrS$Gwrxmlgp(mnWJ@&XySs%x z=Us`@^Pg;OYC;4A2UDM^IJtXyy_|-Pz0{P4%*@UbLsutF%*{VI7#cowu(7c@?Odat z0N)r0*%vtdwl>EMiUKMB6@nvh-$Dk*Bf0y|nE2 z{B}g^X6vJq=B@Xv3vHFK&3p>2^MmDa(mV+V0&#hn`hmAkyVlpZ3i6_Fjy2!c@ENMe zrsDZZRUrkoO7_wg;%ddlyY7zut=1nYp^+?Yql^ptcfeV-6Y<-+R4ZGHfXD~OsF|++U7UIUlIu}~g012Y=UNiAX7TgP#zv#Z2*~Zq z>jwn{M8U}~L7QJx`7s-^P4Rq~9!PMac5?3uBhvTCEkZoB?5FkgQuYN=FD{^zW$tdQA){Nl);p3I6S-ma7YH@2w1qF_o!-k^FeUaqo=;-O0nb^+fx{N7E zppHBwb_%6By|54u_MZMPB=5&<5y3AY&<=`ro>8q<*n4|^psXcSTKz%$Y1x>eN(^!y zJQ+5K0IXI`051%I$eJL4ORz(L^=$27N`Ah=V%<_h$k(^L=#kn$`sdFq_-i~2gTchT z@tg^QY6I#R;cUkWBLDY)S98FLg7>|bYO12J5BWUd;! z*~rHajmvAH?sp#F}Sc-8~6liNF+7nh!OF8BAl9u_qG+VnMI zb+R+guE^d5i9}{;{moKe7F0EM;wiHIILS~51|yTn*uC|BsuPo4GMzdK^C7Fyhcr)0 zNG{~_Ud*$Xd^oke+2)ViL1UP-t{VJ{8`F?>clvQKn##O%q} z&q$GOe0bKz*_%(ob`UpPW2rJ?<#qZw6tbNmo_Pt4zF>jD#1FcpFB4f)ZMn*$l7j4A zxqp`8!?$;LVRC=%|T)xX3-r~ooQn4>_)(hY3Hdl+^*r+iHzbI*hkbIYx?n~7nRFLru?L?6#pSfvm=QO4M&%g`Xl7Q$mX=B2uS@n z1MTjwK3>u=$y#pW?4GE_>4?Dv08`6C@QW4*f)QJ_4vk|eAqyBKk{P4oFxa^UPW0CD9`8B9(B4t)n^Uvm!oZVN9lj7=^<4!eD;;ad$PCbF#z%i!H91U$82$ zB}yN0>sGQuT-6J`H5}5S@a4D+#Ymd{O^pRdI!okdybjo6^I!9OsKy>aqI=Vs4?QzN zbxQW)1dT6TC~&>cm8@;HIggt?WzJg7q^PDof6hvYABh`s>rQzE zi6xv}t)>yKlSLcDdn79$53Bi(Ci1M3;s}IzNgPp{2Yl-jWMmY$irRjxs7E%@vHU;-O<&)Y^H?}Np z;z|@Zp%SH|^j38~Xz-1y7++RBN@j7YQavAklxGU&4wYu~C&e+zeNv{&dV>a1}>~-d1=%7zptbjV( ztPxsZ8@Z=LGv*bK%0)%j&+PWMJ*P#v(9ytGW$-EF*R+55$l4BKw$oFSt0zX}U&HE} zGe(Ca*moZjr6Ka<_%1{Hwf#L-x42feh7{0U8B-T47L>1@JDHj)K@FD&1e9(dbbN+{ zmT(-?i}ibUOSs^A7K!a<;R*fiPVT{Xqu(P?$SO}MNf#BuhE+2vLd}QouvDzb_EfAs zkX;NpA|XK}5Wo!5x^g3523r?6mnlsx0M6Rpg05tqV=ry>cs3lWyzaAAWzeueu3=%A zVI2!dc1tJdQ0>_BfPh^pzSyby7zhPv;Va77mAMlH#=7^A)N>u5BmILwNLe@cI8VRp zYP3Uz*IU?9BMZX`aI@1C))2nH(w78y^?o^of3m{U>dTMk%L<1;+|C~YGoG2)p_oai z#lc*I1DF>^!Jk!3NPv35Q&m}h{^L% zp$2UlP;x;?yOirx`Hn=k?%=|7WNO@ye26Tw!-*h)5(r!K{JJEsosFt0jg0;HB%4&c z^9n8QONGrfX?m5GCyn4(UfT|uH=$S?0wP1mV6`vrug+la!LNeZ4{zS&0d%|#}nvqG8t7LrnO_N;f*dtNw zEUze*BsK6VU?!T#HkFQ@B`t9(84I|8E5^mK1j2?cN>LWzQM5zU8m)QKL%$ITW+x>) z>vw63LsdS zzC)?XLWCkHO!Q-L08R;Dacl5qYOwB9;goT3D_NDv_}{L0*Wq(n=YK#Y0gZS`v=o=8?PB>)EtG%OE|4HojgrrW@3q_ z6f(pW4A&kH?0jue+g2|S(+;qR>qAnMvfpX|fXOKrgq&P<89iSB`ovj}OY=dvycQxN zgn$2kAUbeyPizs_oty-X5V=BJmDk~((6b*AWKsT@`puD^0-GPwW4Nrp*D?|f&Lic@ z8yicdQx-tXAfr*O5EsOc%GC3}nYDTcp(Jabq)Z*GG%XvHN!s_ zQ<@pjeff7JNDCefZAmJ9vwg2Dg1RU!B~?7-48UMkl2Q+SBI>@R7M(jA&UiDG;WTX_ zm3ZLYeKA*Ys+7M4$VBWd7Ip{e8yo+;su!n&9~k&7Ssl5Y=TH~qV{9A{6x7Zm=4`p< z;K8K{RzEdgh{_L2nIWwBGLY^EK2h2P0)JKAx9@h@*q}-orhl?5!{6c{tpJPzgeA zwm>CaV%MS!I%z zE*U4LmY~&Ts6{)++5Npf#BvAn?*|^o=-P1qXFB^|<=#K})c3mLujZettiP=Be{YR% aQIdBT_xg+<^@D$6fy*XWjZ4nE-Tgb!7SoCV delta 3536 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 UmvN;W1^~Y^mggAKZTO>;M1& diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_dark.png index e3bb2bc834fce0633a1fe9e587617c848c9b5132..a53003596e22ed78103b5ad89ed615cae414b71f 100644 GIT binary patch literal 4812 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 L-!aP3@bCTuGbk{I diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_dark.png index 2827d1b37ca96f860026a65e1337c0efb5a0d188..b6cbb2d55596217a42f5f275e426ce765d7b08f8 100644 GIT binary patch literal 7291 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 diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_light.png index 09fda075ae3cc4d9d0db3681cf541a9a2530e129..8474fe51ce077361caf1d7cf83d29cbeed317cc2 100644 GIT binary patch literal 9814 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 diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_with_reactions_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_with_reactions_dark.png index 32ad0abe98b8aee04a7a06963286371998fa9d48..50dc02fb0c09a14d5cefe6cad9d9ab4d28b4f038 100644 GIT binary patch literal 7296 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 E8<7Fa%>V!Z literal 8808 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 UgL6o?0l)*Hp?|kR?ZM0c0U>bXf&c&j diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png index 0065e1d2e7c05a3b6664b1727d0a1a9298bfe9f9..d060b781dd2cb55ac4c050222ce188d9112a2782 100644 GIT binary patch literal 9208 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

T)^kv$fP^ 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 literal 10510 zcmeHtXHZjLzit93C<1mAX$n#WEEMTYN>D+n3WO>mMNkO61XMslid3cd7D6b7YJ!4* zNDqW6pdf@0AVLV31j5<;&$)N*J99tXId|sH+&3SRwRiSj`?uDwtmk>wesj;*fa@sV zQ4k2k1u?v31_FVTKp>U}9EX83cUEg&13zrOIuHvE;1kO6=-?RaYi6JWsvh7cf`s z7cHBcR&^_ri@YmtR@CLF`=6Yi(!&)>JS;qt1cE$2q_q0^%6s%qXR&H)*F`Gmks(GnrT3mxSllGMKM4x7h%pS~hTnoxg5P};fYHSz#&_11ALV1KUTQmaq)uAo0i%aCC zZNZ@5M@q~j*XH0_>^ygg9Xtd6i+-2go;-Qb#*CjcTjzc!$j^@hj9k~;lvHQD^d@~@ zybZlDjd>e?*D%?v0@iJ}@b!(s)^GDRiNWbl=1#z!iJBX!+w0RP*S7sN>c}%8_`GC% zJSxHC*^>!ayn$@q{U8_RQZfjmrFHUBPERIwJ(68$OfHOhUuiFk5EQ(+F}U*Qmk52k zk*VW2fg7=BL~pOpv@zem8@?9*Hi7tU*jZ(71%mNkN^59~X?;FG92)&aO{CD)y`HLX zE}sf`+pV8VAokja>+h4`%nEDd`s^UFYjCX~MiZ%DR3L=;lU<5}LhZ`~+;^73*tv-n zgJI!&3+^R_oh64{>XqZ)zBNkTUTZZ;RA)ZK(7*R47e{_6pmy71VZ&Q3AMRMWoJb1S z*E;!`qBatLA|}Q_<3!@!JfZXW`&hby0$=>yjVAnpiCW2r4{p%)pXuMQdpnZ#7n70s zt(*k)ke#Qr+7F&q46muaqTt=N(cd>I_d&SMNo*6>>eA6WN&ar65luiVEPN=nYbP(qMV3GV2N z$J2AH!7pDfA90wW2mbx)x=wo^#vt3+OU(4YIhFXntH#nGfq!~yqrE-Uz@BV%fj7`* zgC-aHeA<2_p<_n~514a??F-$j8xyi4oznju`7{!?Vl4bpSE*5KHgIpN6wz4*%;Eli zGx|ahHy4jLolY7j^r3Rr=lMm#*+jurj0W)?&oCt2PE1U^@sC7SL4l*d-slE3vE%A! zUmrRFGrQ)wbC!=5s@*O0dcR7Xkewh9^5>MzEpv0kJ567DsvUhYaeP0_6Q;I(?T5Py zZON-RlB?_;1Bp#<|NdC9LQEK(Z|R_RG6Q2aZ`h9z4k%+3m)v><)CUP-tfQKqHa$*&i2mF zzj*OETr7M6g)WRQtJx`nzl4nry}r1;KF%<5&Dp5Bk&_sUjTE%UN7%;;Sl^WHE;S}6*g zTJAa&0F;E${l8>*)y5l$OrQNYPyy)ea&U;QZpYD%du7JVJKh*t7kxoVP^(dJzbP_e zO9_~iG0j56eOVOhVgqRkFI}rPsDF^B34-d(H*k}(k%P1gpP%F5&&I)NDVi(a1Z?JZ zX&zJcuH**nV;`T8#JSiDnjUkl=cD6yVvbsWDMi)`bHG~H+Rfaww1+H^R&g%2rl;K= z(=)}yc9uzVe_NA_JEMZscUA+?pFVB98OJ&@>C^_h@s`b3kA9YAa5O0F*OM^<(;PrO z!c64ePA=sZ8ZJsyZ$_eyyvTQMWN5(Gi=O6H+Ddiqi`gmVdM{8)aY~7-fdfzJw#Vhi z;i`oaqq!Ky)*>qOE_`2zdFmkhYZLBzuv0Eq;N6MrbNk!k^A{meT=(A%4mR>0-%@JO zIw^ciH)wV{!19W$?DAecdBwb2@Ck`pR)*>dzxJymy1qpgswPA_=jBJor;vC#y++H& zsbe3m>Fg#TEJ33m6!+B96Nvv6%zNBj7)xEyn6 zmmXjy<+cCQTWNN3Ra`S%yF;Xi#QP0`GYWd>YMi&yj+oOBMvnF8!3*wLw_yxU>t$x; zq<^G&)?7Vyuy(A(kDGLBU@zG4j*K*F-j;3r#WG}tC`nU9i4wR<=@(jSPUh#JYl1PQ zLK^$(6!j>(zrX6Ot*zzTm^lvy$ax>IEl~#OGW^l4dCtM7Q+4OgpC{27C}8CAjg)gj zc|*)Sc2USU;W6G*@l3VAsb>GRSYzgz3DtF<(YDe@G|y2^d5ZK7XfgRf@RGfro^f`1 zR!2m5FJ2~zjpK#ZT6U7Es%jBAoPgTu8^_iX-N~(PXlpu&&^A{)qd%?w(v3c4iJ@#~ z37qdmE)h_q0|!lPu$Q2bFW&S(C^E`ZKr9vO7ggU8Ll_8!m=V#=m&l*$RfK@q!OdZ} zl-%W41cZ8l)+qtCn||H(zkrn$wz-(M^0S~LOZNK5d8sXC*I7fBueA>Eb3TkUVrg(o zJKFDXk`3NnA&f8$A0Bq*rqZ{uWlFO*XYlA@99FnV&@qts&zX0 z=!?%V&8qnHv%J*)Ho9go{R0X$CrP!6i{qq!*=a+U41HYKEaBwi|1qW0JX{_Swg#t9 zrJ+-J$BYqAeb*a%DQW_$%zQzOjo146`c#|=yh_9%Q8M-thDI6pTiWWWPB2Io4B4$G zmEDKaZzK};cPHVDuWs!oniRjgch-kr^5hPs^=54Ck;6uIgH2D@9l{Yf40Yr$P#ah; zLD1Hgb9)2!D-`>`g$evKq5BnayaU8eA@#O!REcpg1DQwqvu8jKV*eq`u~pv|R7<*F z8sk_FEKOB&5;ax`e=0SRU%i1yxN*bxE}Y)IUyE>V!vBdr#fc++JQ_7G&T{>_O2e-W z5qAUUESm~LL3_(=&F_MJrYCBAog%p}dj|7SfhB06hyb(o^SDl{4_gQgrQdIm`wV@av2r4JY(s6>; z9JEy{uC({Icd;&fw}{|}X65zq8pq8S0=33eDh)STPhQ3ZXa?n&qOFI&yflw3G?d%h z>TIZHm6Vh7`@PP-EI3|LFJ5~HSf+wHh^S?uwmsGTJIOHSZa4jJ5f5ymy%`!LW`?m^ z>@W5Q{R1<(;@>`p=L>i~g58PZq_ke=Q@UkZ+(~POs+-9GfLgWiM$I0CJ=U3%YRFc}WlxkG6s3uNOYqps^d1m?5)njM$hF7>wWJU7D zXTIS6q`~Ym4+D z41y=#LLW?|;fQa2qS~iIYHp&fBi->w$);dWR`8eVcLR?sBvOB?mAN1#ZMb?YXql`4ujSG-H5pXwAjR2$w`^``Z+FryA{I zS=M4N3s2hGVe?`aDk7@?gxXKqV#hl`bnEHcVTA0XQH3-T;c+!um2K6>`Bcaq`wY3$ z`10}7(T=OmEdDPW8nv!ZR`HJqL09OU8AItYfV}6^I+}L=_whZPjW%Yl`{&lD6 z!STH~=YR<4Gw_8^BUm~r)C6lX~0#YfF27^JXYO6LZp!dg*gCdRqhwqD9 zXifmP0H^^rFPV~^z2KlyV>1(Y10`_`?|uka*M0~z55jhZTYQaaI;NAhxeq{12#Ht= z)VPHV>X}Ee#?Y6Dn~R%>@yO%cpy?BO?<&)-kCR1h4Or&0&E1|E5@BNNWru6xfGUH$ zA|n|aNLm$*RTsvR5%M?tp56g{z2u1oXmofv`?;KzHLb~Cb zQzVl|4oMdzSYGE5Xng0+%?G--e?6OIi_N!?5&$ClvseYpnx<1b-tqZxE!)>1LCpss zfsFpe#o7#!YfeWDBhCH5ut;CyLsnL&iq;}-5>opY=g)J2e&~v2IY%j6oZ|krf)?Dp z3Wk}eq^wboAHS`2-S!Gce(QsEQC%QN&7l|}5zwT8+<-iJqV0c@3m~ zOD~1L+wN5U-kwR-ksrf9`p|&y!@NA5Oi(dN_lyT1KQ=Ip6QQ90WxoIC#Q%3TAeG~+ z6FpsG+grgLprLD?k$`^5!2$aGD&qgR5vZA@p|o^}&(vI*R|Q-dB!! z@4TtHbfzepyD54lxf)%kg36W}9+4|y!^fdv@U*#N=fxW z?7N7bfkJom{Gi8f&?T;vpO}`Wa#Pr02{zN0lr9M5z4exVkYQHbVQ0y13k~x2yNH+3 z7PZtR>ZFb|UoFe$;eFLNH=v}cR3zcl?I?F5iH*-9-(dGl+9yMzplnFyyN~=Tm>H>+ z798fnH^po4R#q@kohA8d45m_J5ua)@&C((n#)9M%Gz%=ViUscLF6pQZFjL}8OHzvg#dVdaU3TyTX*=AVYp6)jGOoxmG#U_ z;q1}dnQ_S!M8_@|^F_8wP<4JB_P}y+kJ%)nB~dY!JRps#N7|xdI(FBbR@+xF;eWHH zYY#(jn^#xrwf88Is&to{=ctcI>~da*M(7e^>g<|ST(>xE}{&wtqk_h@Ma|8#6CbVRoV zKerJt&XzAv*I2mULT~94N;<|?+$)AD-EMBVuKH-{My~uXzA2JI>t-KVPD>Jruq)2b0;$iN?3f8KaN(|{lyqh4eVErq%K%&EFw+_f+gIUQKF^P1UaCPZqfg%GfipAYRx;pg_A5ZY z@d@PN;e|Xa#f-LvcCxWq#mDQD*$Mor1siX|Dx+|Xt}J1;P>e#ectw8A+KA}~O=E&y z-Q`7ZUjw7otJhBL8o)ON_KM(iZ%c1^1;5`-UEcr`z{e5bT&6?&SXD_nNbwM|?J2_K)X3`CK>(Fc9_W;v&hI%cWtMH z&IJ4kSI{B4#vvQV^FL@dXeD{F6~C;jZfteHQZX4@^%-+!1^3L{9Ll9p53g_mfUlg? zZY&k%*$63ME)*WsUMP9$+x z8W@`<`aADaDaY?m@!`xnG7; zTIC?g(b3%vy9%9VkfNfZyqot{8CZZ9T1s(w(wd{AV=hDDqr+rc=9avRSlPK(r0v73 zSy^9}q7t2%5ylHfTiov7sGi`z1URh_N9;=%Br&i6wR96uk-rQ9(3`7qTV-#CoSTnN zVs7AO{brc`0qar5P?rYr?&x)Twy7u}t}F2Qok3Ioc#g_Z0#swjZT7TKJ7;HSYdgEC z?eXqwaILa`Mrdc^|D~@_cI7*o#K+G!gTpcmgu-kY+hqIw3FWfxyg;*%owX5c7(+>Y zd#FIt<@$My&&qFT-iUIUQ*AMgRDZN%=;MWQ`z5l~hdcT&Un1CNI)?nkEct8=?ijd(Oj9Ms&{}hJ&11$p z>YJJz1Pxjbx&RYvF4Lg)9Cl3~TR?q}e0M@URi1_rT9Khpv6M8?C>h2v(J-c$!p)oI z&fN(pxGmC3K~b@^w)Qy}*Hf~^&znm;%cA~{eiOE-V}YZ|_;IJ^khEwO4PEO_zjK5^wzAyOJm3+@e>xBZFB@>f#d>q9*a?z7k2uL6{W^jx_F zz~66VlDM-HuDkmMX@(!Kl6lrpGZbHF$SapUH%0-}=;wQ3vg=D-y^WML@i3-h@Jhv? zWJW8+>h`(l%JIf+SqNGF$U#@el0kf2vv{7N0_OuL1&-P}^S-b?T^` z=ahiC-NWGub2$%}3m!v`=PIo#zA1a}UAlz`f8XYguJmcHa;goS9<;~!=q>tq15P{* zh!TwyHd_1IzQZ$f(TelR@y(@X(5W1#mtKpt+mk?;lMQcyLe^bq(=$8Ab5eP|#Dzpn zoQJEiH|ZxzGzytsin6m1%d*iFz*hp{I^PLwXnh#o-Su;+f4Jxq1i@`>DPWj%k7<=G zsR7z_g*)B|6;nfM_BYu)_YW#keV?+W3^#SQn@BS^Ln(=b`HBrq{4MSh_l9dbi1+u~ z;#d1DxwsQO8R{M0F|-aqCwtrgQxsN-aTbQ2gdAY4$&$I9;g2Ivrrf;zr$SY^0Gc4L z>T8K5cQ^b2lK=NbVJJ+-$U|eZ)nDuHyGXncxewh7wK0z?G`uk`ds|=v13Sz18}s& z!C8t*qJNZQxvvmL8X|R8)tA!;SZ=vlaID_wqnY=Ow0XyhW6X@(RO*8HmKkJh!I(fb z<;i^@2qpvUGDOo3NB{Qz)vh89+;rG@VJ*@S=b1WOykey4w_8w|lhEi|NkbH3z1@cmrS# zKKE7mtz0{IE_c~Gh}jZz3j>^S-i1x7bO*u%5JO5@HJDxfpxZBXrP2>Kz&I&hfXmkb z(0}q9(E?HI>u8L>2W@qzK3k0)qBav%*H6AKG#qzD^(Z$otq>3ZH_mj*RCXTPfw3{jOq{x(gmAQh1~oqElhHT zTv!#L(>p!aO53bkp=OForu0i?$bH>f$?4n$=X9SnY{xIUseE6yPPII3WG3OgHmW?n z)c(g-W|U@KtZUv4EFWXznVg-;kIPWyR!E6h9a=>yV9sWgS?Jt@jH_@}UK?Qy&`%yP zceAG2?q@8X6^PmDI=j1nV3=5~y?^SqtNWO<4=8f;w3G0+!h2+E*46dm3%79pS*{_CleS(WjCZw$N41Ge{=8%xayikPbL)w!FjDxnt0<^OPdjeW z_~gd}1?j|~A;=-_KsR83-4u(b+Z`TB{X-BSM~xl42jY z-F@|-nzh2}`>qxkW(#Dm#dkm?8vA-O>FO~{l;uL86sV~v7+G0^U!h*D5o?ehMx954;1BhD^NmMn)Le`U-N3B?JLxA@kIp_bRTrOgAeQO%3a=|C!!ut1xc zBCUzkd(U_UxL%6_s-E2GG(cBr@{o&Y1@et*{d5fnYEVh8UI0%5LZ?(#|96V{=7Bz2 z$g*k%wqJc9@d&P(Y0g9hL`~SKi@{hFjuOW0GcK!RzzbD~8WzYYg bBViYw_rulr&BnYP2>3wsjBi!zIK21|c|U%E diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png index 8be55f6a6e3396eda82066d6f332d08b59b47ada..a26852bf7f21fc94b8acbe013277fe588c013b6b 100644 GIT binary patch literal 11490 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 diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_dark.png index 10b912349654d9e3a223026c72de10e9ce83be87..061a202ad7ab1209878907135b5cd2ed3672d8dd 100644 GIT binary patch literal 9259 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 U!MZyD2m%@0Hn~-K^WpRV0_nDc<^TWy diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_light.png index 7fd124e7d98360b4200d4e6afd6fda2c90e712b0..1ef1d5649cf61cb090382535e4bc2f2f6cb0628a 100644 GIT binary patch literal 11397 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 I&ZD^h1x7V>^#A|> literal 11117 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 diff --git a/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart index 8d77a506b7..27dce547e1 100644 --- a/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart @@ -80,21 +80,18 @@ void main() { (tester) async { MessageAction? messageAction; - // Define custom reaction icons for testing - final testReactionIcons = [ - StreamReactionIcon( - type: 'like', - builder: (context, isActive, size) => const Icon(Icons.thumb_up), - ), - StreamReactionIcon( - type: 'love', - builder: (context, isActive, size) => const Icon(Icons.favorite), - ), - ]; + // Define custom reaction icons via resolver for testing. + const testReactionResolver = _TestReactionIconResolver( + defaultReactionTypes: {'like', 'love'}, + iconByType: { + 'like': Icons.thumb_up, + 'love': Icons.favorite, + }, + ); await tester.pumpWidget( _wrapWithMaterialApp( - reactionIcons: testReactionIcons, + reactionIconResolver: testReactionResolver, Builder( builder: (context) => TextButton( onPressed: () async { @@ -241,14 +238,16 @@ void main() { Widget _wrapWithMaterialApp( Widget child, { Brightness? brightness, - List? reactionIcons, + ReactionIconResolver? reactionIconResolver, }) { return Portal( child: MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData(brightness: brightness), builder: (context, child) => StreamChatConfiguration( - data: StreamChatConfigurationData(reactionIcons: reactionIcons), + data: StreamChatConfigurationData( + reactionIconResolver: reactionIconResolver ?? const _TestReactionIconResolver(), + ), child: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), child: child ?? const SizedBox.shrink(), @@ -272,3 +271,38 @@ Widget _wrapWithMaterialApp( ), ); } + +class _TestReactionIconResolver extends ReactionIconResolver { + const _TestReactionIconResolver({ + this.defaultReactionTypes = const {'like', 'love', 'haha', 'wow', 'sad'}, + this.iconByType = const {}, + }); + + final Set defaultReactionTypes; + final Map iconByType; + + @override + Set get defaultReactions => defaultReactionTypes; + + @override + Set get supportedReactions => { + ...defaultReactionTypes, + ...iconByType.keys, + }; + + @override + String? emojiCode(String type) => streamSupportedEmojis[type]?.emoji; + + @override + Widget resolve(BuildContext context, String type) { + if (iconByType[type] case final icon?) { + return Icon(icon); + } + + if (emojiCode(type) case final emoji?) { + return Text(emoji); + } + + return const Text('❓'); + } +} diff --git a/packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart index dbc2b34769..aa2db5ea2d 100644 --- a/packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart @@ -77,7 +77,7 @@ void main() { (tester) async { User? tappedUser; - // Create just the StreamUserAvatar directly + // Render the reactions modal and assert avatar tap callback behavior. await tester.pumpWidget( _wrapWithMaterialApp( client: mockClient, @@ -98,14 +98,25 @@ void main() { matching: find.byType(StreamUserAvatar), ); - // Verify the avatar is rendered + final avatarTapTarget = find.ancestor( + of: avatar.first, + matching: find.byWidgetPredicate( + (widget) => widget is GestureDetector && widget.child is StreamUserAvatar, + ), + ); + + // Verify the avatar widgets and scoped tap target are rendered. expect(avatar, findsNWidgets(2)); + expect(avatarTapTarget, findsOneWidget); - // Tap on the first avatar directly - await tester.tap(avatar.first); - await tester.pumpAndSettle(); + final gestureDetector = tester.widget(avatarTapTarget); + expect(gestureDetector.onTap, isNotNull); + + // Invoke only the tap target that wraps the first avatar. + gestureDetector.onTap!.call(); + await tester.pump(); - // Verify the callback was called + // Verify the callback was called. expect(tappedUser, isNotNull); }, ); @@ -115,30 +126,21 @@ void main() { (tester) async { MessageAction? messageAction; - // Define custom reaction icons for testing - final testReactionIcons = [ - StreamReactionIcon( - type: 'like', - builder: (context, isActive, size) => const Icon(Icons.thumb_up), - ), - StreamReactionIcon( - type: 'love', - builder: (context, isActive, size) => const Icon(Icons.favorite), - ), - StreamReactionIcon( - type: 'camera', - builder: (context, isActive, size) => const Icon(Icons.camera), - ), - StreamReactionIcon( - type: 'call', - builder: (context, isActive, size) => const Icon(Icons.call), - ), - ]; + // Define a custom reaction resolver for testing. + const testReactionResolver = _TestReactionIconResolver( + defaultReactionTypes: {'like', 'love', 'camera', 'call'}, + iconByType: { + 'like': Icons.thumb_up, + 'love': Icons.favorite, + 'camera': Icons.camera, + 'call': Icons.call, + }, + ); await tester.pumpWidget( _wrapWithMaterialApp( client: mockClient, - reactionIcons: testReactionIcons, + reactionIconResolver: testReactionResolver, Builder( builder: (context) => TextButton( onPressed: () async { @@ -254,7 +256,7 @@ Widget _wrapWithMaterialApp( Widget child, { required StreamChatClient client, Brightness? brightness, - List? reactionIcons, + ReactionIconResolver? reactionIconResolver, }) { return MaterialApp( debugShowCheckedModeBanner: false, @@ -265,7 +267,9 @@ Widget _wrapWithMaterialApp( // Mock the connectivity stream to always return wifi. connectivityStream: Stream.value([ConnectivityResult.wifi]), streamChatThemeData: StreamChatThemeData(brightness: brightness), - streamChatConfigData: StreamChatConfigurationData(reactionIcons: reactionIcons), + streamChatConfigData: StreamChatConfigurationData( + reactionIconResolver: reactionIconResolver ?? const _TestReactionIconResolver(), + ), child: child, ), ), @@ -286,3 +290,38 @@ Widget _wrapWithMaterialApp( ), ); } + +class _TestReactionIconResolver extends ReactionIconResolver { + const _TestReactionIconResolver({ + this.defaultReactionTypes = const {'like', 'love', 'haha', 'wow', 'sad'}, + this.iconByType = const {}, + }); + + final Set defaultReactionTypes; + final Map iconByType; + + @override + Set get defaultReactions => defaultReactionTypes; + + @override + Set get supportedReactions => { + ...defaultReactionTypes, + ...iconByType.keys, + }; + + @override + String? emojiCode(String type) => streamSupportedEmojis[type]?.emoji; + + @override + Widget resolve(BuildContext context, String type) { + if (iconByType[type] case final icon?) { + return Icon(icon); + } + + if (emojiCode(type) case final emoji?) { + return Text(emoji); + } + + return const Text('❓'); + } +} diff --git a/packages/stream_chat_flutter/test/src/misc/reaction_bubble_test.dart b/packages/stream_chat_flutter/test/src/misc/reaction_bubble_test.dart deleted file mode 100644 index b51094fdf4..0000000000 --- a/packages/stream_chat_flutter/test/src/misc/reaction_bubble_test.dart +++ /dev/null @@ -1,234 +0,0 @@ -import 'package:alchemist/alchemist.dart'; -import 'package:flutter/material.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -import '../material_app_wrapper.dart'; -import '../mocks.dart'; - -void main() { - goldenTest( - 'it should show a like - light theme', - fileName: 'reaction_bubble_like_light', - constraints: const BoxConstraints.tightFor(width: 100, height: 100), - builder: () { - final client = MockClient(); - final clientState = MockClientState(); - final themeData = ThemeData.light( - useMaterial3: false, - ); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - - final theme = StreamChatThemeData.fromTheme(themeData); - return MaterialAppWrapper( - theme: themeData, - home: StreamChat( - client: client, - streamChatThemeData: theme, - connectivityStream: Stream.value([ConnectivityResult.mobile]), - child: Scaffold( - body: Center( - child: StreamReactionBubble( - reactions: [ - Reaction( - type: 'like', - user: User(id: 'test'), - ), - ], - borderColor: theme.ownMessageTheme.reactionsBorderColor!, - backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!, - maskColor: theme.ownMessageTheme.reactionsMaskColor!, - ), - ), - ), - ), - ); - }, - ); - - goldenTest( - 'it should show a like - dark theme', - fileName: 'reaction_bubble_like_dark', - constraints: const BoxConstraints.tightFor(width: 100, height: 100), - builder: () { - final client = MockClient(); - final clientState = MockClientState(); - final themeData = ThemeData.dark(); - final theme = StreamChatThemeData.fromTheme(themeData); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - - return MaterialAppWrapper( - theme: themeData, - home: StreamChat( - client: client, - streamChatThemeData: StreamChatThemeData.fromTheme(themeData), - connectivityStream: Stream.value([ConnectivityResult.mobile]), - child: Scaffold( - body: Center( - child: StreamReactionBubble( - reactions: [ - Reaction( - type: 'like', - user: User(id: 'test'), - ), - ], - borderColor: theme.ownMessageTheme.reactionsBorderColor!, - backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!, - maskColor: theme.ownMessageTheme.reactionsMaskColor!, - ), - ), - ), - ), - ); - }, - ); - - goldenTest( - 'it should show three reactions - light theme', - fileName: 'reaction_bubble_3_light', - constraints: const BoxConstraints.tightFor(width: 140, height: 140), - builder: () { - final client = MockClient(); - final clientState = MockClientState(); - final themeData = ThemeData.light(); - final theme = StreamChatThemeData.fromTheme(themeData); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - - return MaterialAppWrapper( - theme: themeData, - home: StreamChat( - client: client, - streamChatThemeData: StreamChatThemeData.fromTheme(themeData), - connectivityStream: Stream.value([ConnectivityResult.mobile]), - child: Scaffold( - body: Center( - child: StreamReactionBubble( - reactions: [ - Reaction( - type: 'like', - user: User(id: 'test'), - ), - Reaction( - type: 'like', - user: User(id: 'user-id'), - ), - Reaction( - type: 'like', - user: User(id: 'test'), - ), - ], - borderColor: theme.ownMessageTheme.reactionsBorderColor!, - backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!, - maskColor: theme.ownMessageTheme.reactionsMaskColor!, - ), - ), - ), - ), - ); - }, - ); - - goldenTest( - 'it should show three reactions - dark theme', - fileName: 'reaction_bubble_3_dark', - constraints: const BoxConstraints.tightFor(width: 140, height: 140), - builder: () { - final client = MockClient(); - final clientState = MockClientState(); - final themeData = ThemeData.dark(); - final theme = StreamChatThemeData.fromTheme(themeData); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - - return MaterialAppWrapper( - theme: themeData, - home: StreamChat( - client: client, - streamChatThemeData: StreamChatThemeData.fromTheme(themeData), - connectivityStream: Stream.value([ConnectivityResult.mobile]), - child: Scaffold( - body: Center( - child: StreamReactionBubble( - reactions: [ - Reaction( - type: 'like', - user: User(id: 'test'), - ), - Reaction( - type: 'like', - user: User(id: 'user-id'), - ), - Reaction( - type: 'like', - user: User(id: 'test'), - ), - ], - borderColor: theme.ownMessageTheme.reactionsBorderColor!, - backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!, - maskColor: theme.ownMessageTheme.reactionsMaskColor!, - ), - ), - ), - ), - ); - }, - ); - - goldenTest( - 'it should show two reactions with customized ui', - fileName: 'reaction_bubble_2', - constraints: const BoxConstraints.tightFor(width: 200, height: 200), - builder: () { - final client = MockClient(); - final clientState = MockClientState(); - final themeData = ThemeData( - useMaterial3: false, - ); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - - return MaterialAppWrapper( - theme: themeData, - home: StreamChat( - client: client, - connectivityStream: Stream.value([ConnectivityResult.mobile]), - streamChatThemeData: StreamChatThemeData.fromTheme(themeData), - child: Scaffold( - body: Center( - child: StreamReactionBubble( - reactions: [ - Reaction( - type: 'like', - user: User(id: 'test'), - ), - Reaction( - type: 'love', - user: User(id: 'user-id'), - ), - Reaction( - type: 'unknown', - user: User(id: 'test'), - ), - ], - borderColor: Colors.red, - backgroundColor: Colors.blue, - maskColor: Colors.green, - reverse: true, - flipTail: true, - tailCirclesSpacing: 4, - ), - ), - ), - ), - ); - }, - ); -} diff --git a/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_dark.png b/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..30f4eaac57f544cc0b9238d6d6657610e326e20e GIT binary patch literal 6150 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 EPwz|l2LJ#7 literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_filtered_dark.png b/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_filtered_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..bbd46e6ea88ff2c3710e46bbbbed4a5a3178efca GIT binary patch literal 5743 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 literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_filtered_light.png b/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_filtered_light.png new file mode 100644 index 0000000000000000000000000000000000000000..a4b6f5d57bf5e6303d57bd18885bdd93e1181023 GIT binary patch literal 6028 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 mockClient.state).thenReturn(mockClientState); + + final currentUser = OwnUser(id: 'current-user', name: 'Current User'); + when(() => mockClientState.currentUser).thenReturn(currentUser); + }); + + tearDown(() => reset(mockClient)); + + testWidgets('shows total reaction count and all reactions by default', (tester) async { + final message = _buildMessage( + latestReactions: [ + Reaction( + type: 'love', + messageId: 'test-message', + userId: 'user-1', + user: User(id: 'user-1', name: 'User 1'), + createdAt: DateTime.now(), + ), + Reaction( + type: 'like', + messageId: 'test-message', + userId: 'user-2', + user: User(id: 'user-2', name: 'User 2'), + createdAt: DateTime.now(), + ), + ], + reactionGroups: { + 'love': ReactionGroup(count: 1, sumScores: 1), + 'like': ReactionGroup(count: 1, sumScores: 1), + }, + ); + + await tester.pumpWidget( + _wrapWithMaterialApp( + client: mockClient, + _ReactionDetailSheetLauncher(message: message), + ), + ); + + await tester.tap(find.text('Open Sheet')); + await tester.pumpAndSettle(); + + expect(find.byType(ReactionDetailSheet), findsOneWidget); + expect(find.text('2 Reactions'), findsOneWidget); + expect(find.byType(StreamUserAvatar), findsNWidgets(2)); + expect(find.text('User 1'), findsOneWidget); + expect(find.text('User 2'), findsOneWidget); + }); + + testWidgets('applies initial reaction filter when provided', (tester) async { + final message = _buildMessage( + latestReactions: [ + Reaction( + type: 'love', + messageId: 'test-message', + userId: 'user-1', + user: User(id: 'user-1', name: 'User 1'), + createdAt: DateTime.now(), + ), + Reaction( + type: 'like', + messageId: 'test-message', + userId: 'user-2', + user: User(id: 'user-2', name: 'User 2'), + createdAt: DateTime.now(), + ), + ], + reactionGroups: { + 'love': ReactionGroup(count: 1, sumScores: 1), + 'like': ReactionGroup(count: 1, sumScores: 1), + }, + ); + + await tester.pumpWidget( + _wrapWithMaterialApp( + client: mockClient, + _ReactionDetailSheetLauncher( + message: message, + initialReactionType: 'love', + ), + ), + ); + + await tester.tap(find.text('Open Sheet')); + await tester.pumpAndSettle(); + + expect(find.text('1 Reaction'), findsOneWidget); + expect(find.byType(StreamUserAvatar), findsOneWidget); + expect(find.text('User 1'), findsOneWidget); + expect(find.text('User 2'), findsNothing); + }); + + testWidgets('pops with SelectReaction when own reaction row is tapped', (tester) async { + MessageAction? action; + + final reaction = Reaction( + type: 'love', + messageId: 'test-message', + userId: 'current-user', + user: User(id: 'current-user', name: 'Current User'), + createdAt: DateTime.now(), + ); + + final message = _buildMessage( + latestReactions: [reaction], + ownReactions: [reaction], + reactionGroups: { + 'love': ReactionGroup(count: 1, sumScores: 1), + }, + ); + + await tester.pumpWidget( + _wrapWithMaterialApp( + client: mockClient, + _ReactionDetailSheetLauncher( + message: message, + onAction: (value) => action = value, + ), + ), + ); + + await tester.tap(find.text('Open Sheet')); + await tester.pumpAndSettle(); + + expect(find.text('Tap to remove'), findsOneWidget); + + await tester.tap(find.text('Current User')); + await tester.pumpAndSettle(); + + expect(action, isA()); + expect((action! as SelectReaction).reaction.type, 'love'); + }); + + testWidgets('does not pop when non-own reaction row is tapped', (tester) async { + MessageAction? action; + + final message = _buildMessage( + latestReactions: [ + Reaction( + type: 'love', + messageId: 'test-message', + userId: 'user-1', + user: User(id: 'user-1', name: 'User 1'), + createdAt: DateTime.now(), + ), + ], + ownReactions: [ + Reaction( + type: 'love', + messageId: 'test-message', + userId: 'current-user', + user: User(id: 'current-user', name: 'Current User'), + createdAt: DateTime.now(), + ), + ], + reactionGroups: { + 'love': ReactionGroup(count: 1, sumScores: 1), + }, + ); + + await tester.pumpWidget( + _wrapWithMaterialApp( + client: mockClient, + _ReactionDetailSheetLauncher( + message: message, + onAction: (value) => action = value, + ), + ), + ); + + await tester.tap(find.text('Open Sheet')); + await tester.pumpAndSettle(); + + expect(find.text('Tap to remove'), findsNothing); + + await tester.tap(find.text('User 1')); + await tester.pumpAndSettle(); + + expect(action, isNull); + expect(find.byType(ReactionDetailSheet), findsOneWidget); + }); + + group('ReactionDetailSheet Golden Tests', () { + final message = _buildMessage( + latestReactions: [ + Reaction( + type: 'love', + messageId: 'test-message', + userId: 'user-1', + user: User(id: 'user-1', name: 'User 1'), + createdAt: DateTime(2026, 1, 1, 10, 0), + ), + Reaction( + type: 'like', + messageId: 'test-message', + userId: 'user-2', + user: User(id: 'user-2', name: 'User 2'), + createdAt: DateTime(2026, 1, 1, 10, 1), + ), + ], + reactionGroups: { + 'love': ReactionGroup(count: 1, sumScores: 1), + 'like': ReactionGroup(count: 1, sumScores: 1), + }, + ); + + Future settleSheet(WidgetTester tester) async { + // Pump once to trigger post-frame modal opening, then settle animation. + await tester.pump(); + await tester.pumpAndSettle(const Duration(seconds: 1)); + } + + for (final brightness in Brightness.values) { + final theme = brightness.name; + + goldenTest( + 'ReactionDetailSheet in $theme theme', + fileName: 'reaction_detail_sheet_$theme', + constraints: const BoxConstraints(maxWidth: 400, maxHeight: 700), + pumpBeforeTest: settleSheet, + builder: () => _wrapWithMaterialApp( + client: mockClient, + brightness: brightness, + _ReactionDetailSheetGoldenHost(message: message), + ), + ); + + goldenTest( + 'ReactionDetailSheet filtered in $theme theme', + fileName: 'reaction_detail_sheet_filtered_$theme', + constraints: const BoxConstraints(maxWidth: 400, maxHeight: 700), + pumpBeforeTest: settleSheet, + builder: () => _wrapWithMaterialApp( + client: mockClient, + brightness: brightness, + _ReactionDetailSheetGoldenHost( + message: message, + initialReactionType: 'love', + ), + ), + ); + } + }); +} + +class _ReactionDetailSheetLauncher extends StatelessWidget { + const _ReactionDetailSheetLauncher({ + required this.message, + this.initialReactionType, + this.onAction, + }); + + final Message message; + final String? initialReactionType; + final ValueChanged? onAction; + + @override + Widget build(BuildContext context) { + return Center( + child: TextButton( + onPressed: () async { + final action = await ReactionDetailSheet.show( + context: context, + message: message, + initialReactionType: initialReactionType, + ); + + onAction?.call(action); + }, + child: const Text('Open Sheet'), + ), + ); + } +} + +class _ReactionDetailSheetGoldenHost extends StatefulWidget { + const _ReactionDetailSheetGoldenHost({ + required this.message, + this.initialReactionType, + }); + + final Message message; + final String? initialReactionType; + + @override + State<_ReactionDetailSheetGoldenHost> createState() => _ReactionDetailSheetGoldenHostState(); +} + +class _ReactionDetailSheetGoldenHostState extends State<_ReactionDetailSheetGoldenHost> { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + ReactionDetailSheet.show( + context: context, + message: widget.message, + initialReactionType: widget.initialReactionType, + ); + }); + } + + @override + Widget build(BuildContext context) { + return const SizedBox.expand(); + } +} + +Message _buildMessage({ + List? latestReactions, + List? ownReactions, + Map? reactionGroups, +}) { + return Message( + id: 'test-message', + text: 'This is a test message', + createdAt: DateTime.now(), + user: User(id: 'test-user', name: 'Test User'), + latestReactions: latestReactions, + ownReactions: ownReactions, + reactionGroups: reactionGroups, + ); +} + +Widget _wrapWithMaterialApp( + Widget child, { + required StreamChatClient client, + Brightness? brightness, +}) { + return MaterialApp( + debugShowCheckedModeBanner: false, + theme: ThemeData(brightness: brightness), + builder: (context, child) => StreamChat( + client: client, + // Mock the connectivity stream to always return wifi. + connectivityStream: Stream.value([ConnectivityResult.wifi]), + streamChatThemeData: StreamChatThemeData(brightness: brightness), + child: child ?? const SizedBox.shrink(), + ), + home: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: ColoredBox( + color: theme.colorTheme.overlay, + child: Padding( + padding: const EdgeInsets.all(8), + child: child, + ), + ), + ); + }, + ), + ); +} diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_dark.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_dark.png index f6f83ee599b22b6c05b7271000cc771d3059ff7a..da6b13b24c01dae8865071a529bda070f2877767 100644 GIT binary patch delta 1615 zcma)+eLT|%9LG1d8Fmc2GkMPAA=aU%xnZHz88OZzSstz%HmsK9;peP&x*Y1#m6S+S z%VXG6!|V=cULJ0)<&;vb%!cVM64vSNzq^0#`}+R#`Q!V2eO}+!=lyLnTtk!x05Iph zL4i>xs^%U^!z^R0V44iNO%m&j8Irey;(Ggnn};bwx%uNnc}l|dNTG5!Gy1mIsQHX@ zdn6>V!ZqTPy**Cfpb5OUIIed*(&)JTw;YQrdH(tXy=~QXH((!#WRGH z<4|OWH|!MQ8?b#OC7>9EMs9`z|I+`J;u1k2r*})UAeFA7lu9D z@aKHSObFt5DtvJYxH2QqrhJjQYK5^og5;&7i?q6jXzsI-xV;e1)Jf?rn%9lC6&>Xm z72TRVp2D<(y|^%ob$>YQALv@?zf~X`>iOq*Ex>{owp^d zm8S7COct!^N-aOtCyf6&|Jkh7pz3T}1{&t?qcU`Sm9~+AZB@%1C zf1rWo}>U&@BZuevoxDeYMz4qntkP1 z&yBNQs;auM{_8uP(ONTj3{5oY!Kpj*$>Tl=9}(d-B;)e@AjOmr*4ZsHy?$E=OA=Iy`10! z;liZ9gAaXNBJEsQWvBs?i;QXA1z_}oxq{7&4Uf0l8GN-?nI6N5-f7DH!Gb;Jb9^ZE z9}~q+^ns&LNPO{+<>+4vy`_6A}59D&}F2_z<5|jCHlS&5!Al$)1m!s+@`NQb)BuDzA^K*PY zzn)ZgwalHK4AM&NY8^*U%%WX#O)}z*|xAP75k=@vIYjP zg#J5W39x3y(#lBm@auv%2G|vg30q4`$zx%5gy)ZO+paFveqG7FcFksEZ&FT9>{RtI z5vWnUBiD5>i-!F(a`a8*{nOD?AAke3w6{a-Asj(M$&}WH%7@GA9`XU_U_%g z1O4aMxfZ2ni2-4^mS1ik^|4lb{|Z!?hb!yo@C^+OrKG1Lnp8Fi+QIMRTBd99<0r(7 z?nag1AWw`zT&h2F^QM!Br{^HRVq}i7NzY^MlI|>YiNmz|L7AFbZ=xBC#UeDwT4aq=>t@IQOiw!I$ zCnsG|21J%S)4Mdyq0D4KH1RtBSjC4gb0S*(qWaj-K?9)+3phsqPO@*fFrjt0r^S?) zrNZ(mh~wGXU>erzRU_9>HXpclz$jj6)qJs9SC?Vbn(UwWOF9lAd>P^xgBhf`sigar&1Q5B)@nC4VGIo>8OA4HKQ{ xn&2GhBHo^GPDdAC*Hei66k``;fPnuGsj;>4O7m3jC(VF1YHx5@P!pa~^cPqB^>Y9K delta 1411 zcmajfdo&XY90%~ZLmpFNnLIMxgvc|mDUH~MV}x*J7-60v&$ilbEJeh<-Q&tTB(YH@ zdCW7LT(m?Std)7J#hTK@c6Isdo^$`XzrTK;bH0Ck|M;Ds1oTv~5Kfc{a&>m}%_v)A z$NH%HYsm^%%INSgmAFBd1X!C|OH5;VAabrCyw|lix;{KAP%54RVc}#~Lw<#4#u z#K<|X(f*JSX_`*DUDrpzEQiF9^)iAQ^h(z%*{7=bErYUC7N|lmEL`Mf^t~=hPQebh zG=%Z69mxqH|Z zNicBY&RgLZOV;&Ga(Joeuicfj93g)n3%@U%u5=<^;4m4r=)N zWA)H9jz0to=Re9P@T{+ufT$SyPr5J0IoqwjNF-bAj^e8^#R>{0${pm)wWViT-W?Rt&2j*i$sYtEu%1NmX*cy`|m54EU)nI7T| z`-#&(D-?+vAQ9{??OsPd?t&7Vk;-PpYw2&-_UCbeNZdGjDmm;Rvp}zi=dpTP)Q!6` z^q>jf9uDX8pb~_)0ocsJTBFkgyz;7iNGKFzS(A&>50C4o9Wga_ATl{W=JO}h@1=fD z(+XS5DsB!Y&qBQ1u+h%^IG4_1=P1Y*pXzv) zDK%8Q&2B+CqK#_D+Qw>pQ&V@swq)XqMdW49?RjD&cD|KNem34rYZb6_r5b)#rR128 z8Po|^DAA)f>Ir8uY;!KYX=TNamSHZ&?v#QlR>___dGj`a0M`m&1G6R|wW;EIh7$r- z+7q9!b1-=2tF672JZIC~W>b!AjaEp&KWn*_T=x@^a*Dx3niCeJwU~|+zd<&B^SnvB zxm5^ixFT-ODowK=4;C!bw;+zu<^l@`L!(rc@fFB|Newsg))a|tlSDI#fgW4!dHKAN zheG0vX6&)w`rOKM_pTRBl){oLl@h^bP!HQ{s>V=WDO6f$Q zMfQ|ty52s-jH_){apsl|Q1%5pfS%Zu0#unG+h@o)CF3FnNHk~}=qe%sE+VL<8;TJk z(V%w6eyl#%q>h`)L2{*(ce5#n96!oP1|-$x-%c1q(m2POCT&UV-jHgxdf^$>7H@9L zZ-c@-yYHVA%QM&ch`sj^Lx_#)R?NxUce~jyVB+RAf`Vwh;Xne>>xN$&#eF4ZVC_Jy zBGyhBeZT!IP8B#ITcWgt%;b5 zl9k8u3YCswl}E{IZj2(2ymfc)-<@;6=X}4P^ZkC$_j^8ne9nh;#Mwbr8LkWffGWw6 z;3mTtvb2VPW%XCz#Vi>p#NbKp5LqNZjx%MwT#TCo9(d5HI|%@a1tfy4dwl-q5AU6I z9CX36f4T%zY}Zwh!|#V3^|A2SvkOAfCmca=-2%C8H1P$gN3o5AhwFf{K9cBEWUs8G zra*}S(|SL~m_k6uc#Q0?JvwBGUruM5m^$xd{?f{R+8zLHEQlBCxRA;K1PP6>;) z^hNj!Cl-UjG;XXdWxYHO0JbrR*hP`x63qjR)Oa49w%Ya;J1&vvB@DSx*O3sknc2>P zYAFKunoQQ#f+~GTwRk=fj5O}R)9F~h-h+#O^|ZzLtEj0pqg4Q$?rK!zR9PndrWsNr zzPh3HTVJ0Se*`mHyQi(Kjq~1+!kZ7p>CNVk<;5i?CPG@!i|}SNflhA-U+X*;LO-Ab zCZup{3yw?r2Obe_QhtZa%3&!GDEwhn%Ek7VwOR+R^`h8%_I9nAkk>&TUQ zKisa+4HP*m)<-xCAxNXcmI2OwIl3d1I8Rw9_cRi^3+D;WTSd5z(XHX zge%*CptEPS1=LUqMcEeWnD;z9*FeT(dfIrOrDf}8g2*J@&b-ieV0d`Z?u}((hCrs# zoSeq4uFGC!lJKVVD&v8u$5Aca$|xfvBQHNc2~YL6Al$&6nuZMFoT(o21rErE~l6ZmMq{H~3xBZXx&8pK1mG+q=64GR@4UmY=ga`Pb Glz#yCh_vYd literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_fallback_light.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_fallback_light.png new file mode 100644 index 0000000000000000000000000000000000000000..c5e7d0846f5b59529eb29f44c8204c0aae728209 GIT binary patch literal 1744 zcma)7`8OMg7LL3ir3leli;&akq$8?^R*Mv=ps}=Qr^rxBQWTRK6{Ml97Bh)uY_(UP zy$r@!Dj22FmSE^uf>dbwG{m!pkYsY^FX%nz-tXM+r~93IzjM<(-JMmGbd>-AfQk#& z>8cEeWO)&+Agd;RJ6SRSksMsS!LmpMhdh<_a-^%y4uD3M{t5t~Kz4Du9Whrl-{*U+K&L9N zHyw|fsL9X6w{32A9HnxPl#_v`0ze&tKx1SX^aqHG7)pNWpaHquVff?8aLv$EA|Y4k zk8k3Q*YERttc7L0>Vhwb;ys9Dns)LY(l>`{nT1DMkoNU+haW|9X2#wvSD9rObz#dH zbQYt`c_%gUfYO}jCNPbl{dH*CYBE|mM5zg?QJtB>m0qkqS#Ca2uneUODtC=2HQk&8t?3k=!@7N%Vv zj=5;?Ilot=(mpTo>aPHddjc>%8y)$QMtSZmzpY$5^-{n@ZMaQWV-70sl%`v^&7KCr z;GDZz{@L&W^13l)tNO$bQ@#kf=I?jsVLVRapZXlqGPwGIB-|#75bOsJar@;;BbH1_ zR_r;`$3gj^J_MOPsmYloL%8D$C7{%K|3g?+qveZR#4W&60g`C7+ zMxub3Sorq-X>WNS}Gwfy+z zkX!HQZ3Q#Ry;)Lm9e;D>)a=rIwU=1gT!Dw7BvBaMo|VWG#r?->qW>0c!d{$qUbW$i zOZNkrc+hNI;Q?%EDcE1f@HUL^k3DV?f0?5V%XO727a4|D6 zyFP@3IjX2i&`G^y^~1;MJ}(mf_s>>+9}c;GRM5c5=7oncPXeG&=*8Vi=lcmQ`(drl zi0b`?I1dY3Tb1|iA1}x06Ug%~9R!NaXA^nW*%~k7K2Mu6#sgR9HG9Q_L{>?{n_@!_$y(-kHx*c%=IlGImCH@}wY(~Tw!VHy#I}l{ zgAYl64*6|S_uJRhh@QU5?b8T>;m6^^v%G}y<&ub38^L;wFyA5D6`Z;BZ{-lglYb4U z<{`=Q+f6EnHb=uX(LS1UcaPDTzbI>&P0c>B(pJ#cp5*I3tE#ltUCy|@IcV`(Dnc+7 zXxVQ=hIKc`QhAwFOBbR5_m|0NQ@p3YMd5O(Nm($bM+_&E3E_0;(Z}G_{+c?y;CSZj zo?~&)2X4&kGDHDtx9k-b!&X2S;KTTssn^o1LoX zi`iFNx1a=yWe)OA>och-iQu)fxBe-}))e6~50=Qt@ Kof;hiQ~m+N;zZ*B literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_light.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_light.png index c360f09bc00652f24dba30d775114c17eb1b0e6f..b9abc15b6cd3dbd07e8eca42a4405088394ad9c8 100644 GIT binary patch delta 1895 zcmb7_Yd8}O8^@VblRP6uOcvEb4tvMz%p5AD9Fo&WPoo?%k(SdoB01!F3?WuMCL~On zXik&Fki$IK!w`)kW{xpqd-dV{_I`Qqum9h5-S>U{umAP`B$NGH76LD2?qFx_9!=y- zAa8>`Rl8QXpO;2Lxv@-)JaoCy0bKqv6C_7+O~{j%0LVXig@7PlhE%Gj+vTR1uMCrp zGHiyl04IEO(*ceZc6u=g=@k(m&>=PYRqjUidxxco_y*?BsayRsA8r=Dockw&xJ=#? zHg6N*4MLxt7<03`_)eeN)eFieI;7re`z3%zoB-;{iQ;w##DJ+FNqNu-nUfG3C3{4& zepbSP-yx!M?TG&ch->+^#k(qTPFqDjk3=7+F;4N&^@$5A9UK>%E8cWRYS*ou&W{UZ z<8+{@fhJz{VtE#0^-C44Nr8Ch%rQ{_K|C=iJt)$I(7EyG=h$~RkbbFP9-Fi@9lU0H z%LTof2eyq1H24Pc3m1PQCP%uY+EHj$;lIW}y0L5^qE96n^c}e>dai}@)lr(l{K$g= z{L9%KGly^O5l3tGloRbqLxCe;ApKB}Y;#{*g{RB4g~HX5O3(Goa6B#}j?1#gVl8TS zB#8)+bFlghgl9vrH@0ilr#?fmq8;UAB{cIaw`Epofq$UbG13Y9TCKafvh0;JPin&s z*0@88`d~22&f+BD^j6%~;K;}vx7-+$meGapCYh=IMUL6j+1vYUl;=c!MlFWpPyKzj zgWGijhFOk%9*4JVN^?g&aM4HTD?ya*+ySew-qxOdbN1kyH&xUM+fb3rQRQmu(1(kf zJ>&}&OK!SNGJoFO=6{i0N#d~1C`b$H(>5dvJ+L^mw71Af2t1d1C%5=-sa-+YrJ`+K z4esODGKIA0+gjoywxNCVUU{vF)~)L+M`cw_XZ1pV?5g9_Sj*%f?7Mac@_TN#qoONE zTI2hI6%R_Ip}J(BNAGx>j_1o|CRgf$^ubN6!QKcliAgZ z<%=lcqeHF27S%q!B^H_X$3u;z%VFbKB_k^UA~aOn*?jY`={T}{l007Jq&HJ>3o|q{ z1VES=*_ZL@bu67Id5~DO`M)NGYkV4uk{{4qOfu}t3-mVVT;y$rZ*8MWCLdW>rjK6b zoPNZ9;px61l8A|7-qA4)dI%4VV7eU9)A-neC^ijBCdokoi>lPeGGsca* z7uY~WVQ0Qe?qPaKpX@Z=sbw|V7mNmm;b;a7e@5)23z3KkgQjI!M^3fS-0TRNu~fEx&g7GE3ZJxr}(Id()R5tkV9KAG|WY z90$)s{m&j_)<{ro2#Eqqnp)!zc=}dOf>N@|R1*sKb=y^X9Rhji_O#@JU}5XyYUz>c z@^J@*%#T739_Kp-&$$s4)lAy_{BVWtqj|Kp4ctmG-_4kq2pHq>TQ_Yq@YdHtVw&CZydhjNy}W?8P+W z!tu4PuNRr3BU`U7{YARrGa`XmvIvdlU55Dr3nJ@D+m0&Ws-oB8<2`Uw@qF?|D(m-d*f zvMDvhm!J-B9Lijqpg2?Fw$OO*W)508T+x3^Z=%^Qze z>dIMZ9ar9yNnToP&EHBDf(C;&t_tS{uBL7bpUWCjE>$4A<@W0GZV=LLKmTl(r;WAU zoojo++woMJp8k;ZE3eSty|dzLLn(NbTov<6!U|7U^psjg7dw7I!1r!gW*eN& ziJ$FnGT!TFB|kWEn^gvT8W4;u^zzHK>6TdWpM&Qx1>#^S=6YEtU^X(QrxZ}n`RmB4bt!k^y-Ry5 zyydfsK!50Ifc?+EX5Url(#ugXXVaQ}Voud*l~^bmZeGz=tVSuYnP7!ek5CYhalqtC9$!*9Ld`r`r%ARP=eI(upYGGN$e6j|c(f0oaG+iAuBEkC-q T4d&n?vOf;zF58i9u0Qw>TnMn5 delta 1560 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 R3@#D0FH5{N?wN^K{69xku(bdH delta 1211 zcmey&{hD)vLp{q>PZ!6KiaBrZ`sPc!%CLP%Pm6z}a(hRn^QE?Bj2D+o6%cY-mEqT+ zY_7sLM_WaOui&w@1HQb}(qUaAis}RTet^kve|wrX*kQqA3=S zOLuK{`}EoR{JHu~yZy_`j>mP+6wi|u2n;W}z^w4gKG~r|N1|RpiJPtEL=&UprUVcl zC-(!pZNtJ;lP`{vC;zee#I$=RN!(>*;LvZGpy(oSP%*(%m%u?s7eGXJr9$k&yK-uq$7A^F7aS>myVCr2|@)E}!=xhg6l`+fV` zuz;+elTJN;|`<&|+-`cMDwd%9g;V#C)126bO za#goit=h&kxnR{(Cgw*Ct8|!m{g!GMj&cm0{&}^zi^POAnXOB9&Rdn~$f4urdPbaC zCSF~f;aNAM*kYwaQ?y@r7Tewr;N!IOI3IE89%IkDoA1Ixj_m3__Z zd|%6tq{L@Op8o#c%*Uko!RO({{>d2(>vjJ3-sgMsT{5mj^kD1rb2epGoxy8*X8Ns7 zpM70z(K_pYZiY63d@^!N!qtx%t@Qny=wOsqU(&x)pn<#Y|L(m{zTQ*w&3(nsJjrnK z7dy}T+^_#VzC9PYy#%b3?ZIX*;X?V<_ct2;SheeYiO&^Tpu&fuRoia%3E#Q7X34wH ze|x#gLMM4bj8MEhcZqy{_{Y+&)rX!>RkPC!H(u-e(ojKC_+iDWGPVQJ{HJ~_yJl6t z_3ZSkwR<*in_QXe)z5fxhD@bW>Fpa5clK>>?cXLhH&2q`eeRowXMf)gsJ*kgVsVVh z8L99~#?M661i5EsnaZ{&?${i8aQj!WdwWe9mtFgju(@gXU%Qt=LYoRc<}@!0HdKh5 zY`15lwr%+3|Jf^V9(!G%GjHb_^Rm|a!Q~CrR~YN}Z<+A^q?XE`OJD!I;uT~&!uU}1 zfot*3tDn!?@;+?cc~x4Cr$gB>lToSQe~4$>^N0QM#c}Boh6?LEP0I&& zL7%%%Uf0VcImigb2`*eL9C1S6;gxQINx|CFB?OMU81x-Fb9zE+=b2wqj(WBj>pT8f zJoBsP-H*5CFxM+Ow_NV_ka`_(^{H|FtFQ{iKbJ}?F1~pvUVPG&O~srk&V)Pvi&01} z>#Bbbu1pV~uTcBr;+?~@3-9GW>8iHQztmp;kL}N;LmTJYXB>EO?&9j|79Pbt3`!Qq z=i5sdu%!gFw8+eClbI>^#p6Vi`AZiu9*&8u%uXo>8w4T^6kNni(D-QF|4e87Ql}`b R3ti0s1fH&bF6*2UngB6RF}wf( diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_own_light.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_own_light.png index c0ac2c43b0799cde7d38edf1403cb429e50839e0..c5e7d0846f5b59529eb29f44c8204c0aae728209 100644 GIT binary patch delta 1700 zcma)5eLNEg1D?GT5$2HZP+pLonGH@ zIIoWkC!%6FIv-=pXkCptpPRAm{O%YNqU;mxCZ+)SYZ<|M^S{UA*77XowCUm8JG9 zO;3sMlbWxF^1arUW{&e)Jn9tXzo?9f@(5^mHX0Icw~5cSzEVFQ8lZNqzVvwI_GV5) ziDG5Nd2Cp^iuams^rp!BkgBmkZ^IqT2NY)xKLH1Mc_YwAQ;~Y% zlym|FR)?>SUy8LaSx7VQ0;PXaND?Dlf03KMBeQ9+vO_j z+@daYIg8F_m0j6RC+?A(^F2jYQMBLoty(R|DyKao1!j)P1xaf%TeEqNcf@svUxN$o zur#S5r)2OzYkhz-z3AIoH0i|PFy+tNrAa#`h#m?38(uXj?cNS02bokBD*R6#8u>Ih zWD8iBaeXBAg6St_uT-ObLGIgM0hsUsp#0Z6nDRzt{+zI_+#vm8&}40-V^<>sk$+Op zv)fUk12aAAWz9SjIY3&oAa7P5|85!!Q)>R_&H_ZhOa9e_7q_fhO_4`Z9Ha4}0njkd zAFno|N#seCfPC9PTjj)mK?d&mWha8LIXw;qPfF zvU|9uKyni?e%~2EY*tm#N>QWVvaz!RArs|a16NZzWT!cIi196qySkMXIb<0^Ac2b) zhNfwhtRC*b(~l&E1BF<_Ud}rW&_jiY*&uP5M6=c}vo&H2XtO6x%vGT+eP3%_XPG~o z;aU`S0(TjX0A{11_azf%tw|AlCzp2L6xPB8=?uZq(GgI?6bv`cDYT6(m6}s0#@~1y zdAd|xD%T}WJh5yGvexH33n43(Ck5Rj1MhoOQ#?y*I2N6?@!rglhK?fFnp6dqBrOqImj*b%OLsPiRp z4ysQ@w0!@1pI`6hXAd#YyIE3k9e4BdNy^;PeXSSh182ee;c?Okx(hp5AWis({bc_w z+N6stGV*n|PUL0Lk-EDwkS-=I-8WAj28d znSK;>T|3S$W~)9d4s}FD6K|M)(f+#+Gkv~sxL-cm_r2fe2T(x+Cz`nrtV9$5Kv!4y z!cOIt`$;Xk5v^BX)w|pTZ(AoPjd$%IE+?4aNeeGrMJml1Uo^CNn}*>ESpYI=W7opEU5(A=ZcM#4{?sLVlNmip#ZOwi)Szo#X_-A#e~5S70(*(p{BMbEUJ z6wV$D8S~t~Wh9VPMPQch-E!}e_CmD?c#B#0Xp452*E<}#FZ*BD&_T}%Oe-q*c*`ej5}l_7WTO>!rG;~%hj#EfH_*v1!r>T{GCb? zu!d`s344O*3Npp5+RUb%JvGlNixJC5@47WGimmke(?FM0)pfv-Q)uMFpUsuD|0i+$ bzY>LVW#?f`qKak(-~jF@uS<=t!D)X1m1RLs delta 1311 zcmaKsZ#2^j9L5(jVrn5x%7{a8i6thLCE65DQ(VQeBu%U>|3p7XqVK7INk-F!3vUiI-hb~cGU zH-6K%FkzQK-rFAM5Ks3H{SYHGkRvajf|eA&B97Jv)!0{RBx}CgM#6u|>mN1pz>OfX!h0HkSQKrr{xfM#In|21O< zQt8;wFKW7k>}+PN@x=|9NhY-!1~lvKbXG3sJ!}NPJKR-#`C42RnFnt5K#gO5#_E>O zU$du%I-gWU}sQmUCn=+ zW4(4&w_DeVA5*GgEh8rj38Oi8&t;*4>o&QVK0m=(w25Z1TDAw*6N{$fko_zitC6pC zE4a!H{ks-A-VA4*8)^TW`dji`*7etf3NyiaGS5g z6|*5Sg+-tA81RCl*|)JYndo<1tr*5z9j6AkLxx)`Egd>drmCZCQ+~~g=pHyjXsot) zuO+%68E-Z{Pu&4R<5Tvt3j;yUNny#`7#uSY9jWj~h1Jqa6Z0q@Onk>zrBTYzi0!ny z#iDCwN>hnC4jywLG_=Tqc7b3-IYB8l$SEQL&D*s>L_83#Y}&2!GK_^R%eiRIviH4j z5}T_UypAqg{nSptn5O-B$mRlCkFV#->21MR*f_fp3 zxb{~y=|LB2L}7a`yO^}2D;cCEO?uBYiAG}8xu!_7tjadD)ojyIqu5+lK3GsR#{*X4 zjS4-l3r>~zbvnX?$_eb8sq1lkiDB%0d#b%`R}dW4AoE~xHQK>SbM zSS*Ma(unhP@(&;;{!jf7oCFy8YBX5U?U=Y~F1gLPG`q#CLUh2H`VP^ya^(=;-PtdT zfptO2Ixse61uE>VN8Qy?*^h#D>)f1YhPfybW~?RU!PZH$<2RC0Y28!FsWh2;z!h-| zmXG%Bi%ZqvmaD?WEtUe>8}93i-BaEMkZ@KiW~Og+g0#ji8umC1;0Fk6Y-|VG7wIhZ zme-z-U&^3u34G-cHs}4jXeTu9%gr7g69L+HaOePCisx3g0D+Tj`Z3AVd)FY>u#;t- zPOv-NmsHCYVdF!#~`kCQLp72@8i+9=7oezsG7lvKU-3#GT2 zaRZJE^H0hUfF$u4BW;4AR+Nt`W^B~D-3*P_mFIEK`fplL`?LNZApfQdJOe8sFB{`S TxCe1UkotVQ{k>{E32A=;@{D StreamSvgIcon( - icon: StreamSvgIcons.loveReaction, - size: iconSize, - color: isSelected ? Colors.red : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'thumbsUp', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.thumbsUpReaction, - size: iconSize, - color: isSelected ? Colors.blue : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'thumbsDown', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.thumbsDownReaction, - size: iconSize, - color: isSelected ? Colors.orange : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'lol', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.lolReaction, - size: iconSize, - color: isSelected ? Colors.amber : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'wut', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.wutReaction, - size: iconSize, - color: isSelected ? Colors.purple : Colors.grey.shade700, - ), - ), - ]; - - group('ReactionIndicatorIconList', () { - testWidgets( - 'renders all reaction icons', - (WidgetTester tester) async { - final indicatorIcons = reactionIcons.map((icon) { - return ReactionIndicatorIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionIndicatorIconList( - indicatorIcons: [...indicatorIcons], - ), - ), - ); - - await tester.pumpAndSettle(); - - // Should find the ReactionIndicatorIconList widget - expect(find.byType(ReactionIndicatorIconList), findsOneWidget); - }, - ); - - testWidgets( - 'uses custom iconBuilder when provided', - (WidgetTester tester) async { - final indicatorIcons = reactionIcons.map((icon) { - return ReactionIndicatorIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionIndicatorIconList( - indicatorIcons: [...indicatorIcons], - iconBuilder: (context, icon) { - return Container( - key: Key('custom-icon-${icon.type}'), - child: icon.build(context), - ); - }, - ), - ), - ); - - await tester.pumpAndSettle(); - - // Verify custom builder was used - expect(find.byKey(const Key('custom-icon-love')), findsOneWidget); - }, - ); - - testWidgets( - 'properly handles icons with selected state', - (WidgetTester tester) async { - final indicatorIcons = reactionIcons.map((icon) { - return ReactionIndicatorIcon( - type: icon.type, - builder: icon.builder, - isSelected: icon.type == 'love', // First icon is selected - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionIndicatorIconList( - indicatorIcons: [...indicatorIcons], - ), - ), - ); - - await tester.pumpAndSettle(); - - // Should render all icons - expect(find.byType(ReactionIndicatorIconList), findsOneWidget); - }, - ); - - testWidgets( - 'updates when indicatorIcons change', - (WidgetTester tester) async { - // Build with initial set of reaction icons - final initialIcons = reactionIcons.sublist(0, 2).map((icon) { - return ReactionIndicatorIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionIndicatorIconList( - indicatorIcons: [...initialIcons], - ), - ), - ); - - await tester.pumpAndSettle(); - - // Rebuild with all reaction icons - final allIcons = reactionIcons.map((icon) { - return ReactionIndicatorIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionIndicatorIconList( - indicatorIcons: [...allIcons], - ), - ), - ); - - await tester.pumpAndSettle(); - - // Should have all icons rendered - expect(find.byType(ReactionIndicatorIconList), findsOneWidget); - }, - ); - - group('Golden tests', () { - for (final brightness in [Brightness.light, Brightness.dark]) { - final theme = brightness.name; - - goldenTest( - 'ReactionIndicatorIconList in $theme theme', - fileName: 'reaction_indicator_icon_list_$theme', - constraints: const BoxConstraints.tightFor(width: 400, height: 100), - builder: () { - final indicatorIcons = reactionIcons.map((icon) { - return ReactionIndicatorIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - return _wrapWithMaterialApp( - brightness: brightness, - ReactionIndicatorIconList( - indicatorIcons: [...indicatorIcons], - ), - ); - }, - ); - - goldenTest( - 'ReactionIndicatorIconList with selected icon in $theme theme', - fileName: 'reaction_indicator_icon_list_selected_$theme', - constraints: const BoxConstraints.tightFor(width: 400, height: 100), - builder: () { - final indicatorIcons = reactionIcons.map((icon) { - return ReactionIndicatorIcon( - type: icon.type, - builder: icon.builder, - isSelected: icon.type == 'love', - ); - }); - - return _wrapWithMaterialApp( - brightness: brightness, - ReactionIndicatorIconList( - indicatorIcons: [...indicatorIcons], - ), - ); - }, - ); - } - }); - }); -} - -Widget _wrapWithMaterialApp( - Widget child, { - Brightness? brightness, -}) { - return MaterialApp( - debugShowCheckedModeBanner: false, - home: StreamChatTheme( - data: StreamChatThemeData(brightness: brightness), - child: Builder( - builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.overlay, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: child, - ), - ), - ); - }, - ), - ), - ); -} diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_test.dart b/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_test.dart index f23f41aaec..cf7ea2c28c 100644 --- a/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_test.dart @@ -4,48 +4,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() { - final reactionIcons = [ - StreamReactionIcon( - type: 'love', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.loveReaction, - size: iconSize, - color: isSelected ? Colors.red : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'thumbsUp', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.thumbsUpReaction, - size: iconSize, - color: isSelected ? Colors.blue : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'thumbsDown', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.thumbsDownReaction, - size: iconSize, - color: isSelected ? Colors.orange : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'lol', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.lolReaction, - size: iconSize, - color: isSelected ? Colors.amber : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'wut', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.wutReaction, - size: iconSize, - color: isSelected ? Colors.purple : Colors.grey.shade700, - ), - ), - ]; + const resolver = _TestReactionIconResolver(); testWidgets( 'renders with correct message and reaction icons', @@ -74,16 +33,16 @@ void main() { _wrapWithMaterialApp( StreamReactionIndicator( message: message, - reactionIcons: reactionIcons, ), + reactionIconResolver: resolver, ), ); await tester.pumpAndSettle(); - // Verify the widget renders with correct structure + // Verify the widget renders with correct structure. expect(find.byType(StreamReactionIndicator), findsOneWidget); - expect(find.byType(ReactionIndicatorIconList), findsOneWidget); + expect(find.byType(StreamEmoji), findsNWidgets(2)); }, ); @@ -110,66 +69,222 @@ void main() { _wrapWithMaterialApp( StreamReactionIndicator( message: message, - reactionIcons: reactionIcons, onTap: () { tapped = true; }, ), + reactionIconResolver: resolver, ), ); await tester.pumpAndSettle(); - // Tap the indicator + // Tap the indicator. await tester.tap(find.byType(InkWell)); await tester.pump(); - // Verify the callback was called + // Verify the callback was called. expect(tapped, isTrue); }, ); testWidgets( - 'uses custom reactionIconBuilder when provided', + 'renders no emojis when reactionGroups are missing', (WidgetTester tester) async { final message = Message( id: 'test-message', text: 'Hello world', user: User(id: 'test-user'), + ); + + await tester.pumpWidget( + _wrapWithMaterialApp( + StreamReactionIndicator( + message: message, + ), + reactionIconResolver: resolver, + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byType(StreamEmoji), findsNothing); + }, + ); + + testWidgets( + 'updates emoji count when reaction groups change', + (WidgetTester tester) async { + final initialMessage = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), reactionGroups: { 'love': ReactionGroup( - count: 5, - sumScores: 5, + count: 1, + sumScores: 1, + firstReactionAt: DateTime.now(), + lastReactionAt: DateTime.now(), + ), + }, + ); + + final updatedMessage = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), + reactionGroups: { + 'love': ReactionGroup( + count: 1, + sumScores: 1, + firstReactionAt: DateTime.now(), + lastReactionAt: DateTime.now(), + ), + 'like': ReactionGroup( + count: 1, + sumScores: 1, + firstReactionAt: DateTime.now(), + lastReactionAt: DateTime.now(), + ), + 'wow': ReactionGroup( + count: 1, + sumScores: 1, firstReactionAt: DateTime.now(), lastReactionAt: DateTime.now(), ), }, ); + await tester.pumpWidget( + _wrapWithMaterialApp( + StreamReactionIndicator(message: initialMessage), + reactionIconResolver: resolver, + ), + ); + + await tester.pumpAndSettle(); + // Initially only one reaction group is visible. + expect(find.byType(StreamEmoji), findsOneWidget); + + await tester.pumpWidget( + _wrapWithMaterialApp( + StreamReactionIndicator(message: updatedMessage), + reactionIconResolver: resolver, + ), + ); + + await tester.pumpAndSettle(); + // Updated message contains three reaction groups. + expect(find.byType(StreamEmoji), findsNWidgets(3)); + }, + ); + + testWidgets( + 'respects custom reaction sorting', + (WidgetTester tester) async { + final message = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), + reactionGroups: { + 'love': ReactionGroup( + count: 5, + sumScores: 5, + firstReactionAt: DateTime(2026, 1, 1, 10, 0), + lastReactionAt: DateTime(2026, 1, 1, 10, 0), + ), + 'like': ReactionGroup( + count: 1, + sumScores: 1, + firstReactionAt: DateTime(2026, 1, 1, 9, 0), + lastReactionAt: DateTime(2026, 1, 1, 9, 0), + ), + }, + ); + await tester.pumpWidget( _wrapWithMaterialApp( StreamReactionIndicator( message: message, - reactionIcons: reactionIcons, - reactionIconBuilder: (context, icon) { - final count = message.reactionGroups?[icon.type]?.count ?? 0; - return Row( - children: [ - icon.build(context), - const SizedBox(width: 4), - Text('$count', key: const Key('reaction-count')), - ], - ); - }, + reactionSorting: ReactionSorting.byCount, + ), + reactionIconResolver: resolver, + ), + ); + + await tester.pumpAndSettle(); + + // Validate display order for custom sorting (ascending count). + final rendered = tester.widgetList( + find.byType(StreamEmoji), + ); + + final first = rendered.first.props.emoji as Text; + final second = rendered.elementAt(1).props.emoji as Text; + + expect(first.data, resolver.emojiCode('like')); + expect(second.data, resolver.emojiCode('love')); + }, + ); + + testWidgets( + 'uses custom reaction resolver rendering', + (WidgetTester tester) async { + final message = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), + reactionGroups: { + 'customParty': ReactionGroup( + count: 1, + sumScores: 1, + firstReactionAt: DateTime.now(), + lastReactionAt: DateTime.now(), + ), + }, + ); + + await tester.pumpWidget( + _wrapWithMaterialApp( + StreamReactionIndicator(message: message), + reactionIconResolver: const _TypeBasedReactionIconResolver(), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(const Key('custom-type-customParty')), findsOneWidget); + }, + ); + + testWidgets( + 'renders resolver fallback for unsupported reaction type', + (WidgetTester tester) async { + final message = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), + reactionGroups: { + 'customUnsupported': ReactionGroup( + count: 1, + sumScores: 1, + firstReactionAt: DateTime.now(), + lastReactionAt: DateTime.now(), ), + }, + ); + + await tester.pumpWidget( + _wrapWithMaterialApp( + StreamReactionIndicator(message: message), + reactionIconResolver: const _StrictReactionIconResolver(), ), ); await tester.pumpAndSettle(); - // Verify the custom count text is displayed - expect(find.byKey(const Key('reaction-count')), findsOneWidget); - expect(find.text('5'), findsOneWidget); + expect(find.byType(StreamEmoji), findsOneWidget); + expect(find.text('❓'), findsOneWidget); }, ); @@ -206,8 +321,8 @@ void main() { brightness: brightness, StreamReactionIndicator( message: message, - reactionIcons: reactionIcons, ), + reactionIconResolver: resolver, ); }, ); @@ -242,8 +357,37 @@ void main() { brightness: brightness, StreamReactionIndicator( message: message, - reactionIcons: reactionIcons, ), + reactionIconResolver: resolver, + ); + }, + ); + + goldenTest( + 'StreamReactionIndicator with resolver fallback in $theme theme', + fileName: 'stream_reaction_indicator_fallback_$theme', + constraints: const BoxConstraints.tightFor(width: 200, height: 60), + builder: () { + final message = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), + reactionGroups: { + 'customUnsupported': ReactionGroup( + count: 1, + sumScores: 1, + firstReactionAt: DateTime.now(), + lastReactionAt: DateTime.now(), + ), + }, + ); + + return _wrapWithMaterialApp( + brightness: brightness, + StreamReactionIndicator( + message: message, + ), + reactionIconResolver: const _StrictReactionIconResolver(), ); }, ); @@ -254,25 +398,97 @@ void main() { Widget _wrapWithMaterialApp( Widget child, { Brightness? brightness, + ReactionIconResolver? reactionIconResolver, }) { return MaterialApp( debugShowCheckedModeBanner: false, - home: StreamChatTheme( - data: StreamChatThemeData(brightness: brightness), - child: Builder( - builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.overlay, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: child, - ), - ), - ); - }, + theme: ThemeData(brightness: brightness), + builder: (context, child) => StreamChatConfiguration( + data: StreamChatConfigurationData( + reactionIconResolver: reactionIconResolver ?? const _TestReactionIconResolver(), + ), + child: StreamChatTheme( + data: StreamChatThemeData(brightness: brightness), + child: child ?? const SizedBox.shrink(), ), ), + home: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.overlay, + body: Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: child, + ), + ), + ); + }, + ), ); } + +class _TestReactionIconResolver extends ReactionIconResolver { + const _TestReactionIconResolver(); + + static const _reactionTypes = {'like', 'haha', 'love', 'wow', 'sad'}; + + @override + Set get defaultReactions => _reactionTypes; + + @override + Set get supportedReactions => _reactionTypes; + + @override + String? emojiCode(String type) => streamSupportedEmojis[type]?.emoji; + + @override + Widget resolve(BuildContext context, String type) { + return Text(emojiCode(type) ?? type); + } +} + +class _TypeBasedReactionIconResolver extends ReactionIconResolver { + const _TypeBasedReactionIconResolver(); + + @override + Set get defaultReactions => const {'customParty'}; + + @override + Set get supportedReactions => const {'customParty'}; + + @override + String? emojiCode(String type) => null; + + @override + Widget resolve(BuildContext context, String type) { + return SizedBox.square(key: Key('custom-type-$type')); + } +} + +class _StrictReactionIconResolver extends ReactionIconResolver { + const _StrictReactionIconResolver(); + + @override + Set get defaultReactions => const {'love'}; + + @override + Set get supportedReactions => const {'love'}; + + @override + String? emojiCode(String type) => streamSupportedEmojis[type]?.emoji; + + @override + Widget resolve(BuildContext context, String type) { + if (!supportedReactions.contains(type)) { + return const Text('❓'); + } + + if (emojiCode(type) case final emoji?) { + return Text(emoji); + } + + return const Text('❓'); + } +} diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_dark.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_dark.png index 3ea44c39935fa5286a26d79e03e6a1420938d59a..308a019318eba652a338453b6a972fc6098f02db 100644 GIT binary patch literal 4053 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 Y5(VwoCkxW!fxaAsHMPc68(&ZO51Y}Oh5!Hn literal 2324 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 BrGx+g diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_dark.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_dark.png index 3de2c605ae107a597306b22b8bd6a405d48b206c..c9b7c2dc00291f08714ef3fbaab28a2288b4593d 100644 GIT binary patch literal 4706 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 literal 2645 zcmdT``#;l*8~;!ztG<$#Q!3+>a)?k$a+%~lvLfuT$lQzp{=6ccj>WXRd_wPMo;`DQb|)Ew{sL*87HrV#xE`SDVM) z%->nx3v^zX(B}g{CtO2G88~mB0RRPuPXfT{y&#|y^nVcQgx?lWf5rsMt4NtCH#e%Z z09P+70ka1KN4CEb!PDt5y6y#8k-ZcZaA&YC%e z`4gsxYC3o0nu{1?>OinH_Sj;#j=FB>=<8aud$^s2KGjYc1s9b=uR|(k`nIrs^4<;? zJyK+(p`RFnhZt=X4aHJU9!G!YwHb;a!)gcEN%sRTQp+Usly_OS6NB!2=xzeNx?H15 zAkeB>k(%iG)=Kb3xZHX|d#5OIEC8O6F|2FX;|-&bUJVxfawkolMaiv^rPv|WRG|c( z<<8YT>BVDpHkPe*DhHMf&!AEpBUqVDiWKjmbVm-oQdxtZ70T0K5&d3Le9B#1^|-ZP z18gBVw)VqoVaQnv4F_r7Y8SZg4}Susxn2i3&~0q^N8bC~+OaJkQRs|EnZ!4=`je_( z2^WVp_BG4&2{)grtK3>u?m8Gm4!0ocwtsx#O&=vq$Fx0a7k^uKzSiPG7^^?a_Ozx& zzp9A4Sn=xC_^dK~^qG?k85*A{$hSPIS-L%z}G;$V1w_4@NnxnqgTiv=EGAFP%Grr_fXVl?&3q#OJ zNcn5(C%-nIeld&PXYhHSBh~WN`>qb4MdYk3HlJDUG_~m|A#zPOiB|gl*_J~vQK zl*au0WAuuZMoW+zD`csACP9draHPb*bkD1RDZ>KR2kstcJe_-EUhKYV|N zDEK;4U@JU;(5wxLYX3fUEWLC8FGPqH936~TB63;mY zN*Zsdn1z}v#@zp<hxpqJHb~DCXqhw-!E0ynLU*vD*A0c5nk9?h zdJnEM^;o^68;`MJI|m`ys26y1Niq{t9G8l3R~@Ln>!}&1)kZX3?Em`uz2u*~mwNAN z2a==1oz!kL<_Mg#AD*J8^brWjPMk$7n<6>n9q}z-UB)n>J^!?M==wQgvjPTk1;5tw zUu=GqCD^s@xA}<{f}%aTd3kujR&B?6RqI@=$ykwWJYwDf-F<7;@$llt6MzPcDD=(-Y6F=`$}prlJegv!&zdU#rF(r z*Ru}Y$4=P+fHUYYkfE-; i7YND*l>WyTia diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_subset_dark.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_subset_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..17f7230f6e9c7f519eb26f31dd8dacb1586d30c7 GIT binary patch literal 3785 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 Tkand5wJ6BU2#FvWxZwT=*6kDL literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_subset_light.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_subset_light.png new file mode 100644 index 0000000000000000000000000000000000000000..7d72f1d1f078cd5c8c12faad876c247e4c060aec GIT binary patch literal 4286 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 StreamSvgIcon( - icon: StreamSvgIcons.loveReaction, - size: iconSize, - color: isSelected ? Colors.red : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'thumbsUp', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.thumbsUpReaction, - size: iconSize, - color: isSelected ? Colors.blue : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'thumbsDown', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.thumbsDownReaction, - size: iconSize, - color: isSelected ? Colors.orange : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'lol', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.lolReaction, - size: iconSize, - color: isSelected ? Colors.amber : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'wut', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.wutReaction, - size: iconSize, - color: isSelected ? Colors.purple : Colors.grey.shade700, - ), - ), - ]; - - group('ReactionIconButton', () { - testWidgets( - 'renders correctly with selected state', - (WidgetTester tester) async { - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionIconButton( - icon: ReactionPickerIcon( - isSelected: true, - type: reactionIcons.first.type, - builder: reactionIcons.first.builder, - ), - onPressed: () {}, - ), - ), - ); - - expect(find.byType(ReactionIconButton), findsOneWidget); - expect(find.byType(IconButton), findsOneWidget); - }, - ); - - testWidgets( - 'triggers callback when pressed', - (WidgetTester tester) async { - var callbackTriggered = false; - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionIconButton( - icon: ReactionPickerIcon( - type: reactionIcons.first.type, - builder: reactionIcons.first.builder, - ), - onPressed: () { - callbackTriggered = true; - }, - ), - ), - ); - - await tester.tap(find.byType(IconButton)); - await tester.pump(); - - expect(callbackTriggered, isTrue); - }, - ); - - group('Golden tests', () { - for (final brightness in [Brightness.light, Brightness.dark]) { - final theme = brightness.name; - - goldenTest( - 'ReactionIconButton unselected in $theme theme', - fileName: 'reaction_icon_button_unselected_$theme', - constraints: const BoxConstraints.tightFor(width: 60, height: 60), - builder: () => _wrapWithMaterialApp( - brightness: brightness, - ReactionIconButton( - icon: ReactionPickerIcon( - type: reactionIcons.first.type, - builder: reactionIcons.first.builder, - ), - onPressed: () {}, - ), - ), - ); - - goldenTest( - 'ReactionIconButton selected in $theme theme', - fileName: 'reaction_icon_button_selected_$theme', - constraints: const BoxConstraints.tightFor(width: 60, height: 60), - builder: () => _wrapWithMaterialApp( - brightness: brightness, - ReactionIconButton( - icon: ReactionPickerIcon( - isSelected: true, - type: reactionIcons.first.type, - builder: reactionIcons.first.builder, - ), - onPressed: () {}, - ), - ), - ); - } - }); - }); - - group('ReactionPickerIconList', () { - testWidgets( - 'renders all reaction icons', - (WidgetTester tester) async { - final pickerIcons = reactionIcons.map((icon) { - return ReactionPickerIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionPickerIconList( - reactionIcons: [...pickerIcons], - onIconPicked: (_) {}, - ), - ), - ); - - // Wait for animations to complete - await tester.pumpAndSettle(); - - // Should find same number of IconButtons as reactionIcons - expect(find.byType(IconButton), findsNWidgets(reactionIcons.length)); - }, - ); - - testWidgets( - 'triggers onIconPicked when a reaction icon is tapped', - (WidgetTester tester) async { - final pickerIcons = reactionIcons.map((icon) { - return ReactionPickerIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - ReactionPickerIcon? pickedIcon; - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionPickerIconList( - reactionIcons: [...pickerIcons], - onIconPicked: (icon) => pickedIcon = icon, - ), - ), - ); - - // Wait for animations to complete - await tester.pumpAndSettle(); - - // Tap the first reaction icon - await tester.tap(find.byType(IconButton).first); - await tester.pump(); - - // Verify callback was triggered with correct reaction type - expect(pickedIcon, isNotNull); - expect(pickedIcon!.type, 'love'); - }, - ); - - testWidgets( - 'shows reaction icons with animation', - (WidgetTester tester) async { - final pickerIcons = reactionIcons.map((icon) { - return ReactionPickerIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionPickerIconList( - reactionIcons: [...pickerIcons], - onIconPicked: (_) {}, - ), - ), - ); - - // Initially the animations should be starting - await tester.pump(); - - // After animation completes - await tester.pumpAndSettle(); - - // Should have all reactions visible - expect( - find.byType(ReactionIconButton), - findsNWidgets(reactionIcons.length), - ); - }, - ); - - testWidgets( - 'properly handles icons with selected state', - (WidgetTester tester) async { - // Create icons with one being selected - final pickerIcons = reactionIcons.map((icon) { - return ReactionPickerIcon( - type: icon.type, - builder: icon.builder, - isSelected: icon.type == 'love', // First icon is selected - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - Material( - child: ReactionPickerIconList( - reactionIcons: [...pickerIcons], - onIconPicked: (_) {}, - ), - ), - ), - ); - - // Wait for animations - await tester.pumpAndSettle(); - - // All reaction buttons should be rendered - expect( - find.byType(ReactionIconButton), - findsNWidgets(reactionIcons.length), - ); - }, - ); - - testWidgets( - 'updates when reactionIcons change', - (WidgetTester tester) async { - // Build with initial set of reaction icons - final initialIcons = reactionIcons.sublist(0, 2).map((icon) { - return ReactionPickerIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionPickerIconList( - reactionIcons: [...initialIcons], - onIconPicked: (_) {}, - ), - ), - ); - - await tester.pumpAndSettle(); - expect(find.byType(IconButton), findsNWidgets(2)); - - // Rebuild with all reaction icons - final allIcons = reactionIcons.map((icon) { - return ReactionPickerIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionPickerIconList( - reactionIcons: [...allIcons], - onIconPicked: (_) {}, - ), - ), - ); - - await tester.pumpAndSettle(); - expect(find.byType(IconButton), findsNWidgets(5)); - }, - ); - - group('Golden tests', () { - for (final brightness in [Brightness.light, Brightness.dark]) { - final theme = brightness.name; - - goldenTest( - 'ReactionPickerIconList in $theme theme', - fileName: 'reaction_picker_icon_list_$theme', - constraints: const BoxConstraints.tightFor(width: 400, height: 100), - builder: () { - final pickerIcons = reactionIcons.map((icon) { - return ReactionPickerIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - return _wrapWithMaterialApp( - brightness: brightness, - ReactionPickerIconList( - reactionIcons: [...pickerIcons], - onIconPicked: (_) {}, - ), - ); - }, - ); - - goldenTest( - 'ReactionPickerIconList with selected reaction in $theme theme', - fileName: 'reaction_picker_icon_list_selected_$theme', - constraints: const BoxConstraints.tightFor(width: 400, height: 100), - builder: () { - final pickerIcons = reactionIcons.map((icon) { - return ReactionPickerIcon( - type: icon.type, - builder: icon.builder, - isSelected: icon.type == 'love', // First icon is selected - ); - }); - - return _wrapWithMaterialApp( - brightness: brightness, - ReactionPickerIconList( - reactionIcons: [...pickerIcons], - onIconPicked: (_) {}, - ), - ); - }, - ); - } - }); - }); -} - -Widget _wrapWithMaterialApp( - Widget child, { - Brightness? brightness, -}) { - return MaterialApp( - debugShowCheckedModeBanner: false, - home: StreamChatTheme( - data: StreamChatThemeData(brightness: brightness), - child: Builder( - builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.overlay, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: child, - ), - ), - ); - }, - ), - ), - ); -} diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_test.dart b/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_test.dart index 5828e34e8a..b8aa9c6bc3 100644 --- a/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_test.dart +++ b/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_test.dart @@ -1,54 +1,14 @@ import 'package:alchemist/alchemist.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter/src/misc/staggered_scale_transition.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() { - final reactionIcons = [ - StreamReactionIcon( - type: 'love', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.loveReaction, - size: iconSize, - color: isSelected ? Colors.red : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'thumbsUp', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.thumbsUpReaction, - size: iconSize, - color: isSelected ? Colors.blue : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'thumbsDown', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.thumbsDownReaction, - size: iconSize, - color: isSelected ? Colors.orange : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'lol', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.lolReaction, - size: iconSize, - color: isSelected ? Colors.amber : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'wut', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.wutReaction, - size: iconSize, - color: isSelected ? Colors.purple : Colors.grey.shade700, - ), - ), - ]; + const resolver = _TestReactionIconResolver(); testWidgets( - 'renders with correct message and reaction icons', + 'renders with correct message and reaction buttons', (WidgetTester tester) async { final message = Message( id: 'test-message', @@ -60,20 +20,22 @@ void main() { _wrapWithMaterialApp( StreamReactionPicker( message: message, - reactionIcons: reactionIcons, onReactionPicked: (_) {}, ), + reactionIconResolver: resolver, ), ); await tester.pumpAndSettle(const Duration(seconds: 1)); - // Verify the widget renders with correct structure + // Verify the widget renders with correct structure. expect(find.byType(StreamReactionPicker), findsOneWidget); - expect(find.byType(ReactionPickerIconList), findsOneWidget); - - // Verify the correct number of reaction icons - expect(find.byType(IconButton), findsNWidgets(reactionIcons.length)); + // Verify the correct number of reaction buttons. + expect( + find.byType(StreamEmojiButton), + findsNWidgets(resolver.defaultReactions.length), + ); + expect(find.byKey(const Key('add_reaction')), findsOneWidget); }, ); @@ -92,25 +54,233 @@ void main() { _wrapWithMaterialApp( StreamReactionPicker( message: message, - reactionIcons: reactionIcons, onReactionPicked: (reaction) { pickedReaction = reaction; }, ), + reactionIconResolver: resolver, ), ); - // Wait for animations to complete + // Wait for animations to complete. await tester.pumpAndSettle(const Duration(seconds: 1)); - // Tap the first reaction icon - await tester.tap(find.byType(IconButton).first); + // Tap the first reaction button. + await tester.tap(find.byKey(const Key('love'))); await tester.pump(); - // Verify the callback was called with the correct reaction + // Verify the callback was called with the correct reaction. expect(pickedReaction, isNotNull); - // Updated to match first reaction type in the list expect(pickedReaction!.type, 'love'); + expect(pickedReaction!.emojiCode, resolver.emojiCode('love')); + }, + ); + + testWidgets( + 'reuses own reaction when selected', + (WidgetTester tester) async { + final existingReaction = Reaction( + type: 'love', + messageId: 'test-message', + userId: 'test-user', + ); + + final message = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), + ownReactions: [existingReaction], + ); + + Reaction? pickedReaction; + + await tester.pumpWidget( + _wrapWithMaterialApp( + StreamReactionPicker( + message: message, + onReactionPicked: (reaction) { + pickedReaction = reaction; + }, + ), + reactionIconResolver: resolver, + ), + ); + + // Wait for animations to complete. + await tester.pumpAndSettle(const Duration(seconds: 1)); + + await tester.tap(find.byKey(const Key('love'))); + await tester.pump(); + + expect(pickedReaction, same(existingReaction)); + }, + ); + + testWidgets( + 'marks own reactions as selected', + (WidgetTester tester) async { + final message = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), + ownReactions: [ + Reaction( + type: 'love', + messageId: 'test-message', + userId: 'test-user', + ), + ], + ); + + await tester.pumpWidget( + _wrapWithMaterialApp( + StreamReactionPicker( + message: message, + onReactionPicked: (_) {}, + ), + reactionIconResolver: resolver, + ), + ); + + // Wait for animations to complete. + await tester.pumpAndSettle(const Duration(seconds: 1)); + + final selectedButton = tester.widget( + find.byKey(const Key('love')), + ); + final unselectedButton = tester.widget( + find.byKey(const Key('like')), + ); + + expect(selectedButton.props.isSelected, isTrue); + expect(unselectedButton.props.isSelected, isFalse); + }, + ); + + testWidgets( + 'updates reaction buttons when resolver default reactions change', + (WidgetTester tester) async { + final message = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), + ); + + const compactResolver = _CustomReactionIconResolver({'love', 'like'}); + + await tester.pumpWidget( + _wrapWithMaterialApp( + StreamReactionPicker( + message: message, + onReactionPicked: (_) {}, + ), + reactionIconResolver: compactResolver, + ), + ); + + await tester.pumpAndSettle(const Duration(seconds: 1)); + // Initial resolver exposes two quick reactions. + expect(find.byType(StreamEmojiButton), findsNWidgets(2)); + + await tester.pumpWidget( + _wrapWithMaterialApp( + StreamReactionPicker( + message: message, + onReactionPicked: (_) {}, + ), + reactionIconResolver: resolver, + ), + ); + + await tester.pumpAndSettle(const Duration(seconds: 1)); + // Updated resolver exposes the default quick-reaction set. + expect( + find.byType(StreamEmojiButton), + findsNWidgets(resolver.defaultReactions.length), + ); + }, + ); + + testWidgets( + 'uses only defaultReactions even when supportedReactions contains more types', + (WidgetTester tester) async { + final message = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), + ); + + const subsetResolver = _SubsetDefaultReactionIconResolver(); + + await tester.pumpWidget( + _wrapWithMaterialApp( + StreamReactionPicker( + message: message, + onReactionPicked: (_) {}, + ), + reactionIconResolver: subsetResolver, + ), + ); + + // Wait for animations to complete. + await tester.pumpAndSettle(const Duration(seconds: 1)); + + // Picker should render only the resolver's quick-reaction defaults. + expect(find.byType(StreamEmojiButton), findsNWidgets(1)); + expect(find.byKey(const Key('love')), findsOneWidget); + expect(find.byKey(const Key('like')), findsNothing); + expect(find.byKey(const Key('wow')), findsNothing); + }, + ); + + testWidgets( + 'uses custom reaction resolver rendering', + (WidgetTester tester) async { + final message = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), + ); + + await tester.pumpWidget( + _wrapWithMaterialApp( + StreamReactionPicker( + message: message, + onReactionPicked: (_) {}, + ), + reactionIconResolver: const _TypeBasedReactionIconResolver(), + ), + ); + + // Wait for animations to complete. + await tester.pumpAndSettle(const Duration(seconds: 1)); + + expect(find.byKey(const Key('custom-type-customParty')), findsOneWidget); + }, + ); + + testWidgets( + 'renders picker without staggered transition animation', + (WidgetTester tester) async { + final message = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), + ); + + await tester.pumpWidget( + _wrapWithMaterialApp( + StreamReactionPicker( + message: message, + onReactionPicked: (_) {}, + ), + reactionIconResolver: resolver, + ), + ); + + await tester.pumpAndSettle(const Duration(seconds: 1)); + + expect(find.byType(StaggeredScaleTransition), findsNothing); }, ); @@ -133,9 +303,9 @@ void main() { brightness: brightness, StreamReactionPicker( message: message, - reactionIcons: reactionIcons, onReactionPicked: (_) {}, ), + reactionIconResolver: resolver, ); }, ); @@ -162,9 +332,31 @@ void main() { brightness: brightness, StreamReactionPicker( message: message, - reactionIcons: reactionIcons, onReactionPicked: (_) {}, ), + reactionIconResolver: resolver, + ); + }, + ); + + goldenTest( + 'StreamReactionPicker with subset defaults in $theme theme', + fileName: 'stream_reaction_picker_subset_$theme', + constraints: const BoxConstraints.tightFor(width: 400, height: 100), + builder: () { + final message = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), + ); + + return _wrapWithMaterialApp( + brightness: brightness, + StreamReactionPicker( + message: message, + onReactionPicked: (_) {}, + ), + reactionIconResolver: const _SubsetDefaultReactionIconResolver(), ); }, ); @@ -175,25 +367,111 @@ void main() { Widget _wrapWithMaterialApp( Widget child, { Brightness? brightness, + ReactionIconResolver? reactionIconResolver, }) { return MaterialApp( debugShowCheckedModeBanner: false, - home: StreamChatTheme( - data: StreamChatThemeData(brightness: brightness), - child: Builder( - builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.overlay, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: child, - ), - ), - ); - }, + theme: ThemeData(brightness: brightness), + builder: (context, child) => StreamChatConfiguration( + data: StreamChatConfigurationData( + reactionIconResolver: reactionIconResolver ?? const _TestReactionIconResolver(), + ), + child: StreamChatTheme( + data: StreamChatThemeData(brightness: brightness), + child: child ?? const SizedBox.shrink(), ), ), + home: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.overlay, + body: Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: child, + ), + ), + ); + }, + ), ); } + +class _TestReactionIconResolver extends ReactionIconResolver { + const _TestReactionIconResolver(); + + static const _reactionTypes = {'like', 'haha', 'love', 'wow', 'sad'}; + + @override + Set get defaultReactions => _reactionTypes; + + @override + Set get supportedReactions => _reactionTypes; + + @override + String? emojiCode(String type) => streamSupportedEmojis[type]?.emoji; + + @override + Widget resolve(BuildContext context, String type) { + return Text(emojiCode(type) ?? type); + } +} + +class _CustomReactionIconResolver extends ReactionIconResolver { + const _CustomReactionIconResolver(this._types); + + final Set _types; + + @override + Set get defaultReactions => _types; + + @override + Set get supportedReactions => _types; + + @override + String? emojiCode(String type) => streamSupportedEmojis[type]?.emoji; + + @override + Widget resolve(BuildContext context, String type) => Text(emojiCode(type) ?? type); +} + +class _TypeBasedReactionIconResolver extends ReactionIconResolver { + const _TypeBasedReactionIconResolver(); + + @override + Set get defaultReactions => const {'customParty'}; + + @override + Set get supportedReactions => const {'customParty'}; + + @override + String? emojiCode(String type) => null; + + @override + Widget resolve(BuildContext context, String type) { + return SizedBox.square(key: Key('custom-type-$type')); + } +} + +class _SubsetDefaultReactionIconResolver extends ReactionIconResolver { + const _SubsetDefaultReactionIconResolver(); + + @override + Set get defaultReactions => const {'love'}; + + @override + Set get supportedReactions => const {'love', 'like', 'wow'}; + + @override + String? emojiCode(String type) => streamSupportedEmojis[type]?.emoji; + + @override + Widget resolve(BuildContext context, String type) { + if (emojiCode(type) case final emoji?) { + return Text(emoji); + } + + return const Text('❓'); + } +} diff --git a/packages/stream_chat_flutter/test/src/reactions/reaction_bubble_overlay_test.dart b/packages/stream_chat_flutter/test/src/reactions/reaction_bubble_overlay_test.dart new file mode 100644 index 0000000000..80eccd5a27 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/reactions/reaction_bubble_overlay_test.dart @@ -0,0 +1,219 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_portal/flutter_portal.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter/src/reactions/reaction_bubble_overlay.dart'; + +void main() { + testWidgets( + 'returns child directly when not visible', + (tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Portal( + child: Scaffold( + body: ReactionBubbleOverlay( + visible: false, + reaction: Text('reaction'), + child: Text('child'), + ), + ), + ), + ), + ); + + expect(find.text('child'), findsOneWidget); + expect(find.text('reaction'), findsNothing); + expect(find.byType(PortalTarget), findsNothing); + }, + ); + + testWidgets( + 'shows portal target when visible', + (tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Portal( + child: Scaffold( + body: ReactionBubbleOverlay( + visible: true, + reaction: Text('reaction'), + child: Text('child'), + ), + ), + ), + ), + ); + + expect(find.text('child'), findsOneWidget); + expect(find.text('reaction'), findsOneWidget); + expect(find.byType(PortalTarget), findsOneWidget); + }, + ); + + testWidgets( + 'supports custom anchor', + (tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Portal( + child: Scaffold( + body: ReactionBubbleOverlay( + visible: true, + anchor: ReactionBubbleAnchor.topStart(offset: Offset(4, -8)), + reaction: Text('reaction'), + child: Text('child'), + ), + ), + ), + ), + ); + + final portalTarget = tester.widget(find.byType(PortalTarget)); + final anchor = portalTarget.anchor as Aligned; + + expect(anchor.target, Alignment.topLeft); + expect(anchor.follower, Alignment.bottomCenter); + expect(anchor.offset, const Offset(4, -8)); + expect(anchor.shiftToWithinBound.x, isTrue); + expect(anchor.shiftToWithinBound.y, isFalse); + expect(find.text('child'), findsOneWidget); + }, + ); + + testWidgets( + 'forwards payload and callback to custom builder', + (tester) async { + String? capturedPayload; + ValueSetter? capturedCallback; + String? pickedValue; + + await tester.pumpWidget( + MaterialApp( + home: Portal( + child: Scaffold( + body: GenericBubbleOverlay( + payload: 'payload-value', + onPicked: (value) { + pickedValue = value; + }, + reactionBuilder: (context, payload, onPicked) { + capturedPayload = payload; + capturedCallback = onPicked; + return const Text('reaction'); + }, + child: const Text('child'), + ), + ), + ), + ), + ); + + expect(capturedPayload, 'payload-value'); + expect(capturedCallback, isNotNull); + + capturedCallback?.call('picked-value'); + expect(pickedValue, 'picked-value'); + }, + ); + + testWidgets( + 'uses start anchor when reverse is false in LTR', + (tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Portal( + child: Scaffold( + body: GenericBubbleOverlay( + payload: 'payload', + reverse: false, + anchorOffset: Offset(12, -6), + reactionBuilder: _defaultReactionBuilder, + child: Text('child'), + ), + ), + ), + ), + ); + + final portalTarget = tester.widget(find.byType(PortalTarget)); + final anchor = portalTarget.anchor as Aligned; + + expect(anchor.target, Alignment.topLeft); + expect(anchor.follower, Alignment.bottomLeft); + expect(anchor.offset, const Offset(12, -6)); + }, + ); + + testWidgets( + 'uses end anchor when reverse is true in LTR', + (tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Portal( + child: Scaffold( + body: GenericBubbleOverlay( + payload: 'payload', + reverse: true, + anchorOffset: Offset(-3, 9), + reactionBuilder: _defaultReactionBuilder, + child: Text('child'), + ), + ), + ), + ), + ); + + final portalTarget = tester.widget(find.byType(PortalTarget)); + final anchor = portalTarget.anchor as Aligned; + + expect(anchor.target, Alignment.topRight); + expect(anchor.follower, Alignment.bottomRight); + expect(anchor.offset, const Offset(-3, 9)); + }, + ); +} + +typedef GenericReactionBuilder = Widget Function(BuildContext context, String payload, ValueSetter? onPicked); + +class GenericBubbleOverlay extends StatelessWidget { + const GenericBubbleOverlay({ + super.key, + required this.payload, + required this.reactionBuilder, + required this.child, + this.onPicked, + this.visible = true, + this.reverse = false, + this.anchorOffset = Offset.zero, + }); + + final String payload; + final GenericReactionBuilder reactionBuilder; + final ValueSetter? onPicked; + final Widget child; + final bool visible; + final bool reverse; + final Offset anchorOffset; + + @override + Widget build(BuildContext context) { + return ReactionBubbleOverlay( + visible: visible, + anchor: ReactionBubbleAnchor( + offset: anchorOffset, + follower: AlignmentDirectional(reverse ? 1 : -1, 1), + target: AlignmentDirectional(reverse ? 1 : -1, -1), + ), + reaction: reactionBuilder(context, payload, onPicked), + child: child, + ); + } +} + +Widget _defaultReactionBuilder( + BuildContext context, + String payload, + ValueSetter? onPicked, +) { + return const SizedBox.shrink(); +} From e9e41e9110ce2cd29ece827f0fbad9e53a10531b Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 4 Mar 2026 09:12:17 +0100 Subject: [PATCH 12/33] feat(ui): update icons (#2519) * Deprecate old icons and add list of replacements * migrated icons in the sdk * chore: Update Goldens * remove unused import * chore: Update Goldens --------- Co-authored-by: renefloor <15101411+renefloor@users.noreply.github.com> --- .../stream_chat_flutter/example/lib/main.dart | 4 +- .../attachment_upload_state_builder.dart | 16 ++-- .../lib/src/attachment/file_attachment.dart | 22 ++--- .../attachment_actions_modal.dart | 24 +++--- .../stream_command_autocomplete_options.dart | 28 +++--- .../src/bottom_sheets/edit_message_sheet.dart | 8 +- .../src/bottom_sheets/error_alert_sheet.dart | 4 +- .../stream_channel_info_bottom_sheet.dart | 16 ++-- .../full_screen_media_desktop.dart | 4 +- .../lib/src/gallery/gallery_footer.dart | 12 +-- .../lib/src/gallery/gallery_header.dart | 10 +-- .../lib/src/icons/stream_svg_icon.dart | 80 ++++++++++++++++++ .../lib/src/indicators/sending_indicator.dart | 16 ++-- .../src/message_input/attachment_button.dart | 2 +- .../options/stream_file_picker.dart | 10 +-- .../options/stream_gallery_picker.dart | 6 +- .../options/stream_image_picker.dart | 8 +- .../options/stream_poll_creator.dart | 8 +- .../options/stream_video_picker.dart | 8 +- .../stream_attachment_picker.dart | 22 ++--- .../audio_recorder/stream_audio_recorder.dart | 27 +++--- .../clear_input_item_button.dart | 4 +- .../lib/src/message_input/command_button.dart | 4 +- .../quoting_message_top_area.dart | 4 +- .../message_input/stream_message_input.dart | 18 ++-- .../stream_message_input_attachment_list.dart | 3 +- .../stream_message_send_button.dart | 4 +- .../message_list_view/message_list_view.dart | 8 +- .../unread_indicator_button.dart | 5 +- .../moderated_message_actions_modal.dart | 3 +- .../message_widget_content.dart | 4 +- .../src/message_widget/pinned_message.dart | 4 +- .../lib/src/misc/back_button.dart | 4 +- .../lib/src/misc/giphy_chip.dart | 6 +- .../lib/src/misc/visible_footnote.dart | 4 +- .../poll_option_reorderable_list_view.dart | 2 +- .../creator/stream_poll_creator_dialog.dart | 2 +- .../poll/stream_poll_option_votes_dialog.dart | 6 +- .../src/poll/stream_poll_results_dialog.dart | 6 +- .../stream_channel_grid_view.dart | 4 +- .../stream_channel_list_tile.dart | 2 +- .../stream_channel_list_view.dart | 4 +- .../stream_draft_list_view.dart | 4 +- .../stream_member_grid_view.dart | 4 +- .../stream_member_list_view.dart | 4 +- .../stream_message_search_grid_view.dart | 4 +- .../stream_message_search_list_view.dart | 4 +- .../photo_gallery/stream_photo_gallery.dart | 4 +- .../stream_photo_gallery_tile.dart | 10 +-- .../stream_poll_vote_list_view.dart | 6 +- .../stream_scroll_view_load_more_error.dart | 4 +- .../stream_thread_list_view.dart | 4 +- .../stream_unread_threads_banner.dart | 6 +- .../stream_user_grid_view.dart | 4 +- .../stream_user_list_tile.dart | 4 +- .../stream_user_list_view.dart | 4 +- .../lib/src/user/user_mention_tile.dart | 4 +- .../voice_recording_attachment_test.dart | 14 ++- .../goldens/ci/edit_message_sheet_0.png | Bin 5501 -> 4175 bytes .../goldens/ci/error_alert_sheet_0.png | Bin 1735 -> 1205 bytes .../ci/channel_header_bottom_widget.png | Bin 1906 -> 1763 bytes .../full_screen_media_test.dart | 2 +- .../test/src/gallery/gallery_footer_test.dart | 2 +- .../test/src/gallery/gallery_header_test.dart | 2 +- .../gallery/goldens/ci/gallery_footer_0.png | Bin 2220 -> 1220 bytes .../gallery/goldens/ci/gallery_header_0.png | Bin 1716 -> 1166 bytes .../goldens/ci/sending_indicator_0.png | Bin 374 -> 200 bytes .../goldens/ci/sending_indicator_1.png | Bin 373 -> 200 bytes .../goldens/ci/sending_indicator_2.png | Bin 309 -> 200 bytes .../goldens/ci/sending_indicator_3.png | Bin 508 -> 200 bytes .../indicators/sending_indicator_test.dart | 33 ++++---- .../message_input/attachment_button_test.dart | 2 +- ...stream_audio_recorder_button_idle_dark.png | Bin 1651 -> 716 bytes ...tream_audio_recorder_button_idle_light.png | Bin 1679 -> 717 bytes ...io_recorder_button_recording_hold_dark.png | Bin 4570 -> 2703 bytes ...o_recorder_button_recording_hold_light.png | Bin 4582 -> 2830 bytes ..._recorder_button_recording_locked_dark.png | Bin 6313 -> 4183 bytes ...recorder_button_recording_locked_light.png | Bin 5919 -> 4056 bytes ...recorder_button_recording_stopped_dark.png | Bin 5747 -> 4238 bytes ...ecorder_button_recording_stopped_light.png | Bin 5339 -> 3977 bytes .../stream_audio_recorder_test.dart | 34 ++++---- .../message_input/clear_input_item_test.dart | 2 +- .../goldens/ci/attachment_button_0.png | Bin 1354 -> 199 bytes .../goldens/ci/clear_input_item_0.png | Bin 815 -> 316 bytes .../goldens/ci/command_button_0.png | Bin 534 -> 202 bytes .../stream_message_send_button_test.dart | 12 ++- .../moderated_message_actions_modal_dark.png | Bin 2496 -> 2184 bytes .../moderated_message_actions_modal_light.png | Bin 2519 -> 2254 bytes .../ci/stream_message_actions_modal_dark.png | Bin 4802 -> 3526 bytes .../ci/stream_message_actions_modal_light.png | Bin 5575 -> 4306 bytes ...am_message_actions_modal_reversed_dark.png | Bin 4812 -> 3559 bytes ...m_message_actions_modal_reversed_light.png | Bin 5522 -> 4260 bytes ...ons_modal_reversed_with_reactions_dark.png | Bin 7291 -> 6059 bytes ...ns_modal_reversed_with_reactions_light.png | Bin 9814 -> 8695 bytes ...sage_actions_modal_with_reactions_dark.png | Bin 7296 -> 6051 bytes ...age_actions_modal_with_reactions_light.png | Bin 9908 -> 8773 bytes .../message_actions_modal_test.dart | 9 +- .../moderated_message_actions_modal_test.dart | 9 +- ...poll_option_reorderable_list_view_dark.png | Bin 6852 -> 5708 bytes ...ption_reorderable_list_view_error_dark.png | Bin 6932 -> 5822 bytes ...tion_reorderable_list_view_error_light.png | Bin 7018 -> 5805 bytes ...oll_option_reorderable_list_view_light.png | Bin 6901 -> 5680 bytes .../ci/stream_poll_creator_dialog_dark.png | Bin 18739 -> 18426 bytes .../ci/stream_poll_creator_dialog_light.png | Bin 18266 -> 17954 bytes ...m_poll_creator_full_screen_dialog_dark.png | Bin 14138 -> 13732 bytes ..._poll_creator_full_screen_dialog_light.png | Bin 13997 -> 13680 bytes ...oll_option_reorderable_list_view_test.dart | 11 ++- ...poll_option_reorderable_list_view_dark.png | Bin 6852 -> 5708 bytes ...oll_option_reorderable_list_view_error.png | Bin 7018 -> 5805 bytes ...oll_option_reorderable_list_view_light.png | Bin 6901 -> 5680 bytes .../ci/stream_poll_creator_dialog_dark.png | Bin 18739 -> 18426 bytes .../ci/stream_poll_creator_dialog_light.png | Bin 18266 -> 17954 bytes ...m_poll_creator_full_screen_dialog_dark.png | Bin 14138 -> 13732 bytes ..._poll_creator_full_screen_dialog_light.png | Bin 13997 -> 13680 bytes .../ci/stream_poll_results_dialog_dark.png | Bin 12111 -> 11526 bytes .../ci/stream_poll_results_dialog_light.png | Bin 11145 -> 10528 bytes ...poll_results_dialog_with_show_all_dark.png | Bin 10155 -> 9560 bytes ...oll_results_dialog_with_show_all_light.png | Bin 9473 -> 8856 bytes .../ci/stream_unread_threads_banner_dark.png | Bin 2270 -> 1736 bytes .../ci/stream_unread_threads_banner_light.png | Bin 2277 -> 1769 bytes sample_app/ios/Flutter/AppFrameworkInfo.plist | 2 - .../lib/pages/advanced_options_page.dart | 2 +- .../pages/channel_file_display_screen.dart | 4 +- sample_app/lib/pages/channel_list_page.dart | 20 ++--- .../pages/channel_media_display_screen.dart | 4 +- sample_app/lib/pages/chat_info_screen.dart | 48 +++++------ sample_app/lib/pages/choose_user_page.dart | 8 +- sample_app/lib/pages/draft_list_page.dart | 4 +- .../lib/pages/group_chat_details_screen.dart | 6 +- sample_app/lib/pages/group_info_screen.dart | 74 ++++++++-------- sample_app/lib/pages/new_chat_screen.dart | 14 +-- .../lib/pages/new_group_chat_screen.dart | 10 +-- .../lib/pages/pinned_messages_screen.dart | 4 +- sample_app/lib/pages/reminders_page.dart | 8 +- sample_app/lib/pages/user_mentions_page.dart | 4 +- sample_app/lib/widgets/channel_list.dart | 20 ++--- .../lib/widgets/chips_input_text_field.dart | 8 +- .../location/location_picker_dialog.dart | 4 +- .../lib/widgets/message_info_sheet.dart | 6 +- sample_app/lib/widgets/search_text_field.dart | 6 +- 140 files changed, 487 insertions(+), 407 deletions(-) diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index 9f21d9c77a..7284f89f0f 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -316,8 +316,8 @@ class _ChannelPageState extends State { color: _streamTheme.colorTheme.borders, ), child: Center( - child: StreamSvgIcon( - icon: StreamSvgIcons.reply, + child: Icon( + context.streamIcons.arrowShareLeft, size: lerpDouble(0, 18, progress), color: _streamTheme.colorTheme.accentPrimary, ), diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart index adbcbe84d1..5631314a20 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart @@ -122,8 +122,8 @@ class _PreparingState extends StatelessWidget { Align( alignment: Alignment.topRight, child: _IconButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.close, + icon: Icon( + context.streamIcons.crossMedium, color: StreamChatTheme.of(context).colorTheme.barsBg, ), onPressed: () => channel.cancelAttachmentUpload(attachmentId), @@ -162,8 +162,8 @@ class _InProgressState extends StatelessWidget { Align( alignment: Alignment.topRight, child: _IconButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.close, + icon: Icon( + context.streamIcons.crossMedium, color: StreamChatTheme.of(context).colorTheme.barsBg, ), onPressed: () => channel.cancelAttachmentUpload(attachmentId), @@ -201,9 +201,9 @@ class _FailedState extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _IconButton( - icon: StreamSvgIcon( + icon: Icon( + context.streamIcons.arrowRotateClockwise, size: 14, - icon: StreamSvgIcons.retry, color: theme.colorTheme.barsBg, ), onPressed: () { @@ -245,8 +245,8 @@ class _SuccessState extends StatelessWidget { child: CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.overlayDark, maxRadius: 12, - child: StreamSvgIcon( - icon: StreamSvgIcons.check, + child: Icon( + context.streamIcons.checkmark2, color: StreamChatTheme.of(context).colorTheme.barsBg, ), ), diff --git a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart index 9d107ff338..4bf4d4227c 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/attachment/handler/stream_attachment_handler.dart'; import 'package:stream_chat_flutter/src/attachment/thumbnail/file_attachment_thumbnail.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/indicators/upload_progress_indicator.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/utils.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template streamFileAttachment} /// Displays file attachments that have been sent in a chat. @@ -173,8 +173,8 @@ class _Trailing extends StatelessWidget { if (message.state.isCompleted) { return IconButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.cloudDownload, + icon: Icon( + context.streamIcons.arrowDown, color: theme.colorTheme.textHighEmphasis, ), visualDensity: VisualDensity.compact, @@ -196,8 +196,8 @@ class _Trailing extends StatelessWidget { preparing: () => Padding( padding: const EdgeInsets.all(8), child: _TrailingButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.close, + icon: Icon( + context.streamIcons.crossMedium, color: theme.colorTheme.barsBg, ), fillColor: theme.colorTheme.overlayDark, @@ -207,8 +207,8 @@ class _Trailing extends StatelessWidget { inProgress: (_, __) => Padding( padding: const EdgeInsets.all(8), child: _TrailingButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.close, + icon: Icon( + context.streamIcons.crossMedium, color: theme.colorTheme.barsBg, ), fillColor: theme.colorTheme.overlayDark, @@ -220,8 +220,8 @@ class _Trailing extends StatelessWidget { child: CircleAvatar( backgroundColor: theme.colorTheme.accentPrimary, maxRadius: 12, - child: StreamSvgIcon( - icon: StreamSvgIcons.check, + child: Icon( + context.streamIcons.checkmark2, color: theme.colorTheme.barsBg, ), ), @@ -229,8 +229,8 @@ class _Trailing extends StatelessWidget { failed: (_) => Padding( padding: const EdgeInsets.all(8), child: _TrailingButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.retry, + icon: Icon( + context.streamIcons.arrowRotateClockwise, color: theme.colorTheme.barsBg, ), fillColor: theme.colorTheme.overlayDark, diff --git a/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart b/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart index c87daabbdd..61b5bf44b7 100644 --- a/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart @@ -111,9 +111,9 @@ class AttachmentActionsModal extends StatelessWidget { _buildButton( context, context.translations.replyLabel, - StreamSvgIcon( + Icon( + context.streamIcons.arrowShareLeft, size: 24, - icon: StreamSvgIcons.reply, color: theme.colorTheme.textLowEmphasis, ), onReply, @@ -122,9 +122,9 @@ class AttachmentActionsModal extends StatelessWidget { _buildButton( context, context.translations.showInChatLabel, - StreamSvgIcon( + Icon( + context.streamIcons.eyeOpen, size: 24, - icon: StreamSvgIcons.eye, color: theme.colorTheme.textHighEmphasis, ), onShowMessage, @@ -135,9 +135,9 @@ class AttachmentActionsModal extends StatelessWidget { attachment.type == AttachmentType.video ? context.translations.saveVideoLabel : context.translations.saveImageLabel, - StreamSvgIcon( + Icon( + context.streamIcons.bookmark, size: 24, - icon: StreamSvgIcons.save, color: theme.colorTheme.textLowEmphasis, ), () { @@ -195,9 +195,9 @@ class AttachmentActionsModal extends StatelessWidget { _buildButton( context, context.translations.deleteLabel.sentenceCase, - StreamSvgIcon( + Icon( + context.streamIcons.trashBin, size: 24, - icon: StreamSvgIcons.delete, color: theme.colorTheme.accentError, ), () { @@ -328,8 +328,8 @@ class AttachmentActionsModal extends StatelessWidget { ? SizedBox( height: 100, width: 100, - child: StreamSvgIcon( - icon: StreamSvgIcons.error, + child: Icon( + context.streamIcons.exclamationCircle1, color: theme.colorTheme.disabled, ), ) @@ -338,8 +338,8 @@ class AttachmentActionsModal extends StatelessWidget { key: const Key('completedIcon'), height: 160, width: 160, - child: StreamSvgIcon( - icon: StreamSvgIcons.check, + child: Icon( + context.streamIcons.checkmark2, color: theme.colorTheme.disabled, ), ) diff --git a/packages/stream_chat_flutter/lib/src/autocomplete/stream_command_autocomplete_options.dart b/packages/stream_chat_flutter/lib/src/autocomplete/stream_command_autocomplete_options.dart index 49184fcf4a..ce8c22c145 100644 --- a/packages/stream_chat_flutter/lib/src/autocomplete/stream_command_autocomplete_options.dart +++ b/packages/stream_chat_flutter/lib/src/autocomplete/stream_command_autocomplete_options.dart @@ -44,8 +44,8 @@ class StreamCommandAutocompleteOptions extends StatelessWidget { return ListTile( dense: true, horizontalTitleGap: 0, - leading: StreamSvgIcon( - icon: StreamSvgIcons.lightning, + leading: Icon( + context.streamIcons.thunder, color: colorTheme.accentPrimary, size: 28, ), @@ -107,20 +107,20 @@ class _CommandIcon extends StatelessWidget { return CircleAvatar( backgroundColor: _streamChatTheme.colorTheme.accentPrimary, radius: 12, - child: const StreamSvgIcon( + child: Icon( + context.streamIcons.peopleRemove, size: 16, color: Colors.white, - icon: StreamSvgIcons.userRemove, ), ); case 'flag': return CircleAvatar( backgroundColor: _streamChatTheme.colorTheme.accentPrimary, radius: 12, - child: const StreamSvgIcon( + child: Icon( + context.streamIcons.flag2, size: 14, color: Colors.white, - icon: StreamSvgIcons.flag, ), ); case 'imgur': @@ -138,40 +138,40 @@ class _CommandIcon extends StatelessWidget { return CircleAvatar( backgroundColor: _streamChatTheme.colorTheme.accentPrimary, radius: 12, - child: const StreamSvgIcon( + child: Icon( + context.streamIcons.mute, size: 16, color: Colors.white, - icon: StreamSvgIcons.mute, ), ); case 'unban': return CircleAvatar( backgroundColor: _streamChatTheme.colorTheme.accentPrimary, radius: 12, - child: const StreamSvgIcon( + child: Icon( + context.streamIcons.peopleAdd, size: 16, color: Colors.white, - icon: StreamSvgIcons.userAdd, ), ); case 'unmute': return CircleAvatar( backgroundColor: _streamChatTheme.colorTheme.accentPrimary, radius: 12, - child: const StreamSvgIcon( + child: Icon( + context.streamIcons.volumeFull, size: 16, color: Colors.white, - icon: StreamSvgIcons.volumeUp, ), ); default: return CircleAvatar( backgroundColor: _streamChatTheme.colorTheme.accentPrimary, radius: 12, - child: const StreamSvgIcon( + child: Icon( + context.streamIcons.thunder, size: 16, color: Colors.white, - icon: StreamSvgIcons.lightning, ), ); } diff --git a/packages/stream_chat_flutter/lib/src/bottom_sheets/edit_message_sheet.dart b/packages/stream_chat_flutter/lib/src/bottom_sheets/edit_message_sheet.dart index ced57b6ecc..1e428db1f4 100644 --- a/packages/stream_chat_flutter/lib/src/bottom_sheets/edit_message_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/bottom_sheets/edit_message_sheet.dart @@ -89,8 +89,8 @@ class _EditMessageSheetState extends State { children: [ Padding( padding: const EdgeInsets.all(8), - child: StreamSvgIcon( - icon: StreamSvgIcons.edit, + child: Icon( + context.streamIcons.editBig, color: streamChatThemeData.colorTheme.disabled, ), ), @@ -100,8 +100,8 @@ class _EditMessageSheetState extends State { ), IconButton( visualDensity: VisualDensity.compact, - icon: StreamSvgIcon( - icon: StreamSvgIcons.closeSmall, + icon: Icon( + context.streamIcons.crossSmall, color: streamChatThemeData.colorTheme.textLowEmphasis, ), onPressed: Navigator.of(context).pop, diff --git a/packages/stream_chat_flutter/lib/src/bottom_sheets/error_alert_sheet.dart b/packages/stream_chat_flutter/lib/src/bottom_sheets/error_alert_sheet.dart index 744664dd8f..0d7871c4d4 100644 --- a/packages/stream_chat_flutter/lib/src/bottom_sheets/error_alert_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/bottom_sheets/error_alert_sheet.dart @@ -25,8 +25,8 @@ class ErrorAlertSheet extends StatelessWidget { const SizedBox( height: 26, ), - StreamSvgIcon( - icon: StreamSvgIcons.error, + Icon( + context.streamIcons.exclamationCircle1, color: _streamChatTheme.colorTheme.accentError, size: 24, ), diff --git a/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart b/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart index 778d6b3be5..6e6aa09763 100644 --- a/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart @@ -120,8 +120,8 @@ class StreamChannelInfoBottomSheet extends StatelessWidget { StreamOptionListTile( leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: StreamSvgIcon( - icon: StreamSvgIcons.user, + child: Icon( + context.streamIcons.people, color: colorTheme.textLowEmphasis, ), ), @@ -133,8 +133,8 @@ class StreamChannelInfoBottomSheet extends StatelessWidget { title: context.translations.leaveGroupLabel, leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: StreamSvgIcon( - icon: StreamSvgIcons.userRemove, + child: Icon( + context.streamIcons.peopleRemove, color: colorTheme.textLowEmphasis, ), ), @@ -144,8 +144,8 @@ class StreamChannelInfoBottomSheet extends StatelessWidget { StreamOptionListTile( leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: StreamSvgIcon( - icon: StreamSvgIcons.delete, + child: Icon( + context.streamIcons.trashBin, color: colorTheme.accentError, ), ), @@ -156,8 +156,8 @@ class StreamChannelInfoBottomSheet extends StatelessWidget { StreamOptionListTile( leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: StreamSvgIcon( - icon: StreamSvgIcons.closeSmall, + child: Icon( + context.streamIcons.crossSmall, color: colorTheme.textLowEmphasis, ), ), diff --git a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart index 214ad03621..884adcbcc5 100644 --- a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart +++ b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart @@ -148,9 +148,9 @@ class _FullScreenMediaDesktopState extends State { videoPackages.values.first.player.stop(); Navigator.of(context).pop(); }, - child: const StreamSvgIcon( + child: Icon( + context.streamIcons.crossMedium, size: 30, - icon: StreamSvgIcons.close, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart b/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart index 72e96f923d..0e1ef333b0 100644 --- a/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart +++ b/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart @@ -82,9 +82,9 @@ class _StreamGalleryFooterState extends State { else IconButton( key: shareButtonKey, - icon: StreamSvgIcon( + icon: Icon( + context.streamIcons.shareOs, size: 24, - icon: StreamSvgIcons.share, color: galleryFooterThemeData.shareIconColor, ), onPressed: () async { @@ -135,8 +135,8 @@ class _StreamGalleryFooterState extends State { ), ), IconButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.grid, + icon: Icon( + context.streamIcons.layoutGrid1, color: galleryFooterThemeData.gridIconButtonColor, ), onPressed: () => _showPhotosModal(context), @@ -184,8 +184,8 @@ class _StreamGalleryFooterState extends State { Align( alignment: Alignment.centerRight, child: IconButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.close, + icon: Icon( + context.streamIcons.crossMedium, color: galleryFooterThemeData.bottomSheetCloseIconColor, ), onPressed: () => Navigator.of(context).maybePop(), diff --git a/packages/stream_chat_flutter/lib/src/gallery/gallery_header.dart b/packages/stream_chat_flutter/lib/src/gallery/gallery_header.dart index f87983b829..3291f4ac72 100644 --- a/packages/stream_chat_flutter/lib/src/gallery/gallery_header.dart +++ b/packages/stream_chat_flutter/lib/src/gallery/gallery_header.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:stream_chat_flutter/src/attachment_actions_modal/attachment_actions_modal.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/theme/themes.dart'; import 'package:stream_chat_flutter/src/utils/utils.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template streamGalleryHeader} /// Header/AppBar widget for media display screen @@ -86,8 +86,8 @@ class StreamGalleryHeader extends StatelessWidget implements PreferredSizeWidget elevation: elevation, leading: showBackButton ? IconButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.close, + icon: Icon( + context.streamIcons.crossMedium, color: galleryHeaderThemeData.closeButtonColor, size: 24, ), @@ -99,8 +99,8 @@ class StreamGalleryHeader extends StatelessWidget implements PreferredSizeWidget actions: [ if (!message.isEphemeral) IconButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.menuPoint, + icon: Icon( + context.streamIcons.dotsGrid1x3Vertical, color: galleryHeaderThemeData.iconMenuPointColor, ), onPressed: () => _showMessageActionModalBottomSheet(context), diff --git a/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.dart b/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.dart index 46acd43a8d..fe8348db38 100644 --- a/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.dart +++ b/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.dart @@ -11,8 +11,88 @@ typedef StreamSvgIconData = SvgIconData; /// {@template StreamSvgIcon} /// Icon set of stream chat /// {@endtemplate} +@Deprecated('Use Icon(context.streamIcons.*) instead') class StreamSvgIcon extends StatelessWidget { /// Creates a [StreamSvgIcon]. + /// Replacement icons: + /// + /// Exact / close matches: + /// - StreamSvgIcons.arrowRight -> context.streamIcons.arrowRight + /// - StreamSvgIcons.attach -> context.streamIcons.paperclip1 + /// - StreamSvgIcons.camera -> context.streamIcons.camera1 + /// - StreamSvgIcons.check -> context.streamIcons.checkmark2 + /// - StreamSvgIcons.checkAll -> context.streamIcons.doupleCheckmark1Small + /// - StreamSvgIcons.checkSend -> context.streamIcons.circleCheck + /// - StreamSvgIcons.close -> context.streamIcons.crossMedium + /// - StreamSvgIcons.closeSmall -> context.streamIcons.crossSmall + /// - StreamSvgIcons.copy -> context.streamIcons.squareBehindSquare2Copy + /// - StreamSvgIcons.delete -> context.streamIcons.trashBin + /// - StreamSvgIcons.down -> context.streamIcons.chevronDown + /// - StreamSvgIcons.edit -> context.streamIcons.editBig + /// - StreamSvgIcons.error -> context.streamIcons.exclamationCircle1 + /// - StreamSvgIcons.eye -> context.streamIcons.eyeOpen + /// - StreamSvgIcons.flag -> context.streamIcons.flag2 + /// - StreamSvgIcons.left -> context.streamIcons.chevronLeft + /// - StreamSvgIcons.lightning -> context.streamIcons.thunder + /// - StreamSvgIcons.link -> context.streamIcons.chainLink3 + /// - StreamSvgIcons.lock -> context.streamIcons.lock + /// - StreamSvgIcons.mentions -> context.streamIcons.at + /// - StreamSvgIcons.mic -> context.streamIcons.microphone + /// - StreamSvgIcons.mute -> context.streamIcons.mute + /// - StreamSvgIcons.notification -> context.streamIcons.bellNotification + /// - StreamSvgIcons.pause -> context.streamIcons.pause + /// - StreamSvgIcons.penWrite -> context.streamIcons.pencil + /// - StreamSvgIcons.pin -> context.streamIcons.pin + /// - StreamSvgIcons.right -> context.streamIcons.chevronRight + /// - StreamSvgIcons.search -> context.streamIcons.magnifyingGlassSearch + /// - StreamSvgIcons.settings -> context.streamIcons.settingsGear2 + /// - StreamSvgIcons.smile -> context.streamIcons.emojiSmile + /// - StreamSvgIcons.stop -> context.streamIcons.stop + /// - StreamSvgIcons.time -> context.streamIcons.clock + /// - StreamSvgIcons.up -> context.streamIcons.chevronTop + /// - StreamSvgIcons.volumeUp -> context.streamIcons.volumeFull + /// + /// Conceptual matches (similar purpose, may differ visually): + /// - StreamSvgIcons.award -> context.streamIcons.trophy + /// - StreamSvgIcons.circleUp -> context.streamIcons.arrowUp (no circle) + /// - StreamSvgIcons.contacts -> context.streamIcons.users + /// - StreamSvgIcons.download -> context.streamIcons.arrowDown (no tray) + /// - StreamSvgIcons.emptyCircleRight -> context.streamIcons.chevronRight (no circle) + /// - StreamSvgIcons.files -> context.streamIcons.fileBend (folder vs document) + /// - StreamSvgIcons.grid -> context.streamIcons.layoutGrid1 (3x3 vs 2x2) + /// - StreamSvgIcons.group -> context.streamIcons.users + /// - StreamSvgIcons.loveReaction -> context.streamIcons.heart2 + /// - StreamSvgIcons.menuPoint -> context.streamIcons.dotsGrid1x3Vertical + /// - StreamSvgIcons.message -> context.streamIcons.bubble3ChatMessage + /// - StreamSvgIcons.messageUnread -> context.streamIcons.bubbleWideNotificationChatMessage + /// - StreamSvgIcons.pictures -> context.streamIcons.images1Alt + /// - StreamSvgIcons.play -> context.streamIcons.playSolid + /// - StreamSvgIcons.polls -> context.streamIcons.chart5 + /// - StreamSvgIcons.record -> context.streamIcons.video + /// - StreamSvgIcons.reload -> context.streamIcons.arrowRotateRightLeftRepeatRefresh + /// - StreamSvgIcons.reply -> context.streamIcons.arrowShareLeft + /// - StreamSvgIcons.retry -> context.streamIcons.arrowRotateClockwise + /// - StreamSvgIcons.save -> context.streamIcons.bookmark (arrow vs ribbon) + /// - StreamSvgIcons.send -> context.streamIcons.paperPlane + /// - StreamSvgIcons.sendMessage -> context.streamIcons.paperPlaneTopRight (no circle) + /// - StreamSvgIcons.share -> context.streamIcons.shareOs + /// - StreamSvgIcons.shareArrow -> context.streamIcons.shareRedirectLink + /// - StreamSvgIcons.threadReply -> context.streamIcons.bubbleAnnotation2ChatMessage + /// - StreamSvgIcons.user -> context.streamIcons.people + /// - StreamSvgIcons.userAdd -> context.streamIcons.peopleAdd + /// - StreamSvgIcons.userDelete -> context.streamIcons.peopleRemove + /// - StreamSvgIcons.userRemove -> context.streamIcons.peopleRemove + /// - StreamSvgIcons.userSettings -> context.streamIcons.peopleEditUserRights + /// - StreamSvgIcons.videoCall -> context.streamIcons.videoSolid + /// + /// Removed in new set (no equivalent): + /// - StreamSvgIcons.cloudDownload + /// - StreamSvgIcons.lolReaction + /// - StreamSvgIcons.moon + /// - StreamSvgIcons.thumbsDownReaction + /// - StreamSvgIcons.thumbsUpReaction + /// - StreamSvgIcons.wutReaction + @Deprecated('Use Icon(context.streamIcons.*) instead') const StreamSvgIcon({ super.key, this.icon, diff --git a/packages/stream_chat_flutter/lib/src/indicators/sending_indicator.dart b/packages/stream_chat_flutter/lib/src/indicators/sending_indicator.dart index 5951336615..b283bed6e1 100644 --- a/packages/stream_chat_flutter/lib/src/indicators/sending_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/indicators/sending_indicator.dart @@ -33,33 +33,33 @@ class StreamSendingIndicator extends StatelessWidget { final colorTheme = streamChatTheme.colorTheme; if (isMessageRead) { - return StreamSvgIcon( + return Icon( + context.streamIcons.doupleCheckmark1Small, size: size, - icon: StreamSvgIcons.checkAll, color: colorTheme.accentPrimary, ); } if (isMessageDelivered) { - return StreamSvgIcon( + return Icon( + context.streamIcons.doupleCheckmark1Small, size: size, - icon: StreamSvgIcons.checkAll, color: colorTheme.textLowEmphasis, ); } if (message.state.isCompleted) { - return StreamSvgIcon( + return Icon( + context.streamIcons.checkmark2, size: size, - icon: StreamSvgIcons.check, color: colorTheme.textLowEmphasis, ); } if (message.state.isOutgoing) { - return StreamSvgIcon( + return Icon( + context.streamIcons.clock, size: size, - icon: StreamSvgIcons.time, color: colorTheme.textLowEmphasis, ); } diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_button.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_button.dart index e77465572c..5bdb6bc9e7 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_button.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_button.dart @@ -53,7 +53,7 @@ class AttachmentButton extends StatelessWidget { color: color, iconSize: size, onPressed: onPressed, - icon: icon ?? const StreamSvgIcon(icon: StreamSvgIcons.attach), + icon: icon ?? Icon(context.streamIcons.paperclip1), ); } } diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_file_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_file_picker.dart index 396af5ace3..140b3d1989 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_file_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_file_picker.dart @@ -2,11 +2,11 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:stream_chat_flutter/src/attachment/handler/stream_attachment_handler.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/message_input/attachment_picker/stream_attachment_picker.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/utils.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// Widget used to pick files from the device class StreamFilePicker extends StatelessWidget { @@ -60,9 +60,9 @@ class StreamFilePicker extends StatelessWidget { final theme = StreamChatTheme.of(context); return OptionDrawer( child: EndOfFrameCallbackWidget( - child: StreamSvgIcon( + child: Icon( + context.streamIcons.fileBend, size: 240, - icon: StreamSvgIcons.files, color: theme.colorTheme.disabled, ), onEndOfFrame: (_) async { @@ -86,9 +86,9 @@ class StreamFilePicker extends StatelessWidget { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamSvgIcon( + Icon( + context.streamIcons.fileBend, size: 240, - icon: StreamSvgIcons.files, color: theme.colorTheme.disabled, ), Text( diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart index 7d100766f8..f81a8cd094 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart @@ -4,7 +4,6 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:photo_manager/photo_manager.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/message_input/attachment_picker/stream_attachment_picker.dart'; import 'package:stream_chat_flutter/src/message_input/attachment_picker/stream_attachment_picker_controller.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; @@ -13,6 +12,7 @@ import 'package:stream_chat_flutter/src/scroll_view/photo_gallery/stream_photo_g import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/utils.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// Max image resolution which can be resized by the CDN. /// Taken from https://getstream.io/chat/docs/flutter-dart/file_uploads/?language=dart#image-resizing @@ -109,9 +109,9 @@ class _StreamGalleryPickerState extends State { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamSvgIcon( + Icon( + context.streamIcons.images1Alt, size: 240, - icon: StreamSvgIcons.pictures, color: colorTheme.disabled, ), Text( diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_image_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_image_picker.dart index a9130ea73b..0448a2c894 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_image_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_image_picker.dart @@ -39,9 +39,9 @@ class StreamImagePicker extends StatelessWidget { final theme = StreamChatTheme.of(context); return OptionDrawer( child: EndOfFrameCallbackWidget( - child: StreamSvgIcon( + child: Icon( + context.streamIcons.camera1, size: 240, - icon: StreamSvgIcons.camera, color: theme.colorTheme.disabled, ), onEndOfFrame: (_) async { @@ -61,9 +61,9 @@ class StreamImagePicker extends StatelessWidget { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamSvgIcon( + Icon( + context.streamIcons.camera1, size: 240, - icon: StreamSvgIcons.camera, color: theme.colorTheme.disabled, ), Text( diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_poll_creator.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_poll_creator.dart index ecf95fe96d..122ef662bc 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_poll_creator.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_poll_creator.dart @@ -36,9 +36,9 @@ class StreamPollCreator extends StatelessWidget { return OptionDrawer( child: EndOfFrameCallbackWidget( - child: StreamSvgIcon( + child: Icon( + context.streamIcons.chart5, size: 180, - icon: StreamSvgIcons.polls, color: theme.colorTheme.disabled, ), onEndOfFrame: (_) => _openCreatePollFlow(), @@ -46,9 +46,9 @@ class StreamPollCreator extends StatelessWidget { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamSvgIcon( + Icon( + context.streamIcons.chart5, size: 240, - icon: StreamSvgIcons.polls, color: theme.colorTheme.disabled, ), const SizedBox(height: 8), diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_video_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_video_picker.dart index 4ba4e8f7b6..59db3a1cae 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_video_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_video_picker.dart @@ -31,9 +31,9 @@ class StreamVideoPicker extends StatelessWidget { final theme = StreamChatTheme.of(context); return OptionDrawer( child: EndOfFrameCallbackWidget( - child: StreamSvgIcon( + child: Icon( + context.streamIcons.video, size: 240, - icon: StreamSvgIcons.record, color: theme.colorTheme.disabled, ), onEndOfFrame: (_) async { @@ -51,9 +51,9 @@ class StreamVideoPicker extends StatelessWidget { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamSvgIcon( + Icon( + context.streamIcons.video, size: 240, - icon: StreamSvgIcons.record, color: theme.colorTheme.disabled, ), Text( diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart index feb7b57fb5..6cef2c49c1 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart @@ -205,8 +205,8 @@ class _TabbedAttachmentPickerOptions extends StatelessWidget { return IconButton( color: colorTheme.accentPrimary, disabledColor: colorTheme.disabled, - icon: const StreamSvgIcon( - icon: StreamSvgIcons.emptyCircleRight, + icon: Icon( + context.streamIcons.chevronRight, ), onPressed: onPressed, ); @@ -444,7 +444,7 @@ Widget tabbedAttachmentPickerBuilder({ final defaultOptions = [ TabbedAttachmentPickerOption( key: 'gallery-picker', - icon: const StreamSvgIcon(icon: StreamSvgIcons.pictures), + icon: Icon(context.streamIcons.images1Alt), supportedTypes: [ AttachmentPickerType.images, AttachmentPickerType.videos, @@ -471,7 +471,7 @@ Widget tabbedAttachmentPickerBuilder({ ), TabbedAttachmentPickerOption( key: 'file-picker', - icon: const StreamSvgIcon(icon: StreamSvgIcons.files), + icon: Icon(context.streamIcons.fileBend), supportedTypes: [AttachmentPickerType.files], optionViewBuilder: (context, controller) => StreamFilePicker( onFilePicked: (file) async { @@ -482,7 +482,7 @@ Widget tabbedAttachmentPickerBuilder({ ), TabbedAttachmentPickerOption( key: 'image-picker', - icon: const StreamSvgIcon(icon: StreamSvgIcons.camera), + icon: Icon(context.streamIcons.camera1), supportedTypes: [AttachmentPickerType.images], optionViewBuilder: (context, controller) => StreamImagePicker( onImagePicked: (image) async { @@ -493,7 +493,7 @@ Widget tabbedAttachmentPickerBuilder({ ), TabbedAttachmentPickerOption( key: 'video-picker', - icon: const StreamSvgIcon(icon: StreamSvgIcons.record), + icon: Icon(context.streamIcons.video), supportedTypes: [AttachmentPickerType.videos], optionViewBuilder: (context, controller) => StreamVideoPicker( onVideoPicked: (video) async { @@ -504,7 +504,7 @@ Widget tabbedAttachmentPickerBuilder({ ), TabbedAttachmentPickerOption( key: 'poll-creator', - icon: const StreamSvgIcon(icon: StreamSvgIcons.polls), + icon: Icon(context.streamIcons.chart5), supportedTypes: [AttachmentPickerType.poll], optionViewBuilder: (context, controller) { final initialPoll = controller.value.poll; @@ -583,7 +583,7 @@ Widget systemAttachmentPickerBuilder({ SystemAttachmentPickerOption( key: 'image-picker', supportedTypes: [AttachmentPickerType.images], - icon: const StreamSvgIcon(icon: StreamSvgIcons.pictures), + icon: Icon(context.streamIcons.images1Alt), title: context.translations.uploadAPhotoLabel, onTap: (context, controller) async { final result = await _pickSystemFile(controller, FileType.image); @@ -593,7 +593,7 @@ Widget systemAttachmentPickerBuilder({ SystemAttachmentPickerOption( key: 'video-picker', supportedTypes: [AttachmentPickerType.videos], - icon: const StreamSvgIcon(icon: StreamSvgIcons.record), + icon: Icon(context.streamIcons.video), title: context.translations.uploadAVideoLabel, onTap: (context, controller) async { final result = await _pickSystemFile(controller, FileType.video); @@ -603,7 +603,7 @@ Widget systemAttachmentPickerBuilder({ SystemAttachmentPickerOption( key: 'file-picker', supportedTypes: [AttachmentPickerType.files], - icon: const StreamSvgIcon(icon: StreamSvgIcons.files), + icon: Icon(context.streamIcons.fileBend), title: context.translations.uploadAFileLabel, onTap: (context, controller) async { final result = await _pickSystemFile(controller, FileType.any); @@ -613,7 +613,7 @@ Widget systemAttachmentPickerBuilder({ SystemAttachmentPickerOption( key: 'poll-creator', supportedTypes: [AttachmentPickerType.poll], - icon: const StreamSvgIcon(icon: StreamSvgIcons.polls), + icon: Icon(context.streamIcons.chart5), title: context.translations.createPollLabel(isNew: true), onTap: (context, controller) async { final initialPoll = controller.value.poll; diff --git a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart index 34d01c1745..0ff73b379f 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart @@ -5,7 +5,6 @@ import 'package:flutter_portal/flutter_portal.dart'; import 'package:stream_chat_flutter/src/audio/audio_playlist_controller.dart'; import 'package:stream_chat_flutter/src/audio/audio_playlist_state.dart'; import 'package:stream_chat_flutter/src/audio/audio_sampling.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/message_input/audio_recorder/audio_recorder_feedback.dart'; import 'package:stream_chat_flutter/src/message_input/audio_recorder/audio_recorder_state.dart'; import 'package:stream_chat_flutter/src/message_input/stream_message_input_icon_button.dart'; @@ -173,7 +172,7 @@ class StreamAudioRecorderButton extends StatelessWidget { state: recordState, button: RecordButton( onPressed: () {}, // Allows showing ripple effect on tap. - icon: const StreamSvgIcon(icon: StreamSvgIcons.mic), + icon: Icon(context.streamIcons.microphone), ), builder: (context, state, recordButton) => switch (state) { // Show only the record button if the recording is not in progress. @@ -449,17 +448,17 @@ class RecordStateLockedRecordingContent extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ StreamMessageInputIconButton( - icon: const StreamSvgIcon(icon: StreamSvgIcons.delete), + icon: Icon(context.streamIcons.trashBin), color: theme.colorTheme.accentPrimary, onPressed: onRecordCancel, ), StreamMessageInputIconButton( - icon: const StreamSvgIcon(icon: StreamSvgIcons.stop), + icon: Icon(context.streamIcons.stop), color: theme.colorTheme.accentError, onPressed: onRecordStop, ), StreamMessageInputIconButton( - icon: const StreamSvgIcon(icon: StreamSvgIcons.checkSend), + icon: Icon(context.streamIcons.circleCheck), color: theme.colorTheme.accentPrimary, onPressed: onRecordEnd, ), @@ -607,12 +606,12 @@ class _RecordStateStoppedContentState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ StreamMessageInputIconButton( - icon: const StreamSvgIcon(icon: StreamSvgIcons.delete), + icon: Icon(context.streamIcons.trashBin), color: theme.colorTheme.accentPrimary, onPressed: widget.onRecordCancel, ), StreamMessageInputIconButton( - icon: const StreamSvgIcon(icon: StreamSvgIcons.checkSend), + icon: Icon(context.streamIcons.circleCheck), color: theme.colorTheme.accentPrimary, onPressed: widget.onRecordFinish, ), @@ -735,9 +734,9 @@ class PlaybackControlButton extends StatelessWidget { ); }, ), - TrackState.idle => const StreamSvgIcon(icon: StreamSvgIcons.play), - TrackState.paused => const StreamSvgIcon(icon: StreamSvgIcons.play), - TrackState.playing => const StreamSvgIcon(icon: StreamSvgIcons.pause), + TrackState.idle => Icon(context.streamIcons.playSolid), + TrackState.paused => Icon(context.streamIcons.playSolid), + TrackState.playing => Icon(context.streamIcons.pause), }, ); } @@ -765,8 +764,8 @@ class PlaybackTimerIndicator extends StatelessWidget { final theme = StreamChatTheme.of(context); return Row( children: [ - StreamSvgIcon( - icon: StreamSvgIcons.mic, + Icon( + context.streamIcons.microphone, size: kDefaultMessageInputIconSize, color: switch (duration.inSeconds) { > 0 => theme.colorTheme.accentError, @@ -856,8 +855,8 @@ class SlideToCancelIndicator extends StatelessWidget { ), ), const SizedBox(width: 8), - StreamSvgIcon( - icon: StreamSvgIcons.left, + Icon( + context.streamIcons.chevronLeft, color: theme.colorTheme.textLowEmphasis, ), ], diff --git a/packages/stream_chat_flutter/lib/src/message_input/clear_input_item_button.dart b/packages/stream_chat_flutter/lib/src/message_input/clear_input_item_button.dart index ddfb0c20e0..514c84accf 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/clear_input_item_button.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/clear_input_item_button.dart @@ -34,9 +34,9 @@ class ClearInputItemButton extends StatelessWidget { // ignore: deprecated_member_use _streamChatTheme.colorTheme.textHighEmphasis.withOpacity(0.5), child: Center( - child: StreamSvgIcon( + child: Icon( + context.streamIcons.crossMedium, size: 24, - icon: StreamSvgIcons.close, color: _streamChatTheme.colorTheme.barsBg, ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_input/command_button.dart b/packages/stream_chat_flutter/lib/src/message_input/command_button.dart index 1e8c050cb0..c70e1e0d0d 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/command_button.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/command_button.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/message_input/stream_message_input_icon_button.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template commandButton} /// The button that allows a user to use commands in a chat. @@ -53,7 +53,7 @@ class CommandButton extends StatelessWidget { color: color, iconSize: size, onPressed: onPressed, - icon: icon ?? const StreamSvgIcon(icon: StreamSvgIcons.lightning), + icon: icon ?? Icon(context.streamIcons.thunder), ); } } diff --git a/packages/stream_chat_flutter/lib/src/message_input/quoting_message_top_area.dart b/packages/stream_chat_flutter/lib/src/message_input/quoting_message_top_area.dart index 74f26d9dac..08b00c6fc2 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/quoting_message_top_area.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/quoting_message_top_area.dart @@ -37,7 +37,7 @@ class QuotingMessageTopArea extends StatelessWidget { StreamMessageInputIconButton( iconSize: 24, color: _streamChatTheme.colorTheme.disabled, - icon: const StreamSvgIcon(icon: StreamSvgIcons.reply), + icon: Icon(context.streamIcons.arrowShareLeft), onPressed: null, ), Text( @@ -47,7 +47,7 @@ class QuotingMessageTopArea extends StatelessWidget { StreamMessageInputIconButton( iconSize: 24, color: _streamChatTheme.colorTheme.textLowEmphasis, - icon: const StreamSvgIcon(icon: StreamSvgIcons.closeSmall), + icon: Icon(context.streamIcons.crossSmall), onPressed: onQuotedMessageCleared?.call, ), ], diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart index 8a4b1f169c..07e291bb49 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart @@ -926,7 +926,7 @@ class StreamMessageInputState extends State with Restoration (widget.actionsLocation == ActionsLocation.right || widget.actionsLocation == ActionsLocation.rightInside) ? pi : 0, - child: const StreamSvgIcon(icon: StreamSvgIcons.emptyCircleRight), + child: Icon(context.streamIcons.chevronRight), ), onPressed: () { if (_actionsShrunk) { @@ -1181,10 +1181,10 @@ class StreamMessageInputState extends State with Restoration child: Row( mainAxisSize: MainAxisSize.min, children: [ - const StreamSvgIcon( + Icon( + context.streamIcons.thunder, size: 16, color: Colors.white, - icon: StreamSvgIcons.lightning, ), Text( _effectiveController.message.command!.toUpperCase(), @@ -1212,7 +1212,7 @@ class StreamMessageInputState extends State with Restoration child: StreamMessageInputIconButton( iconSize: 24, color: _messageInputTheme.actionButtonIdleColor, - icon: const StreamSvgIcon(icon: StreamSvgIcons.closeSmall), + icon: Icon(context.streamIcons.crossSmall), onPressed: _effectiveController.clear, ), ), @@ -1486,8 +1486,8 @@ class StreamMessageInputState extends State with Restoration .any((element) => element.group(0)?.split('.').last.isValidTLD() == true)) { showInfoBottomSheet( context, - icon: StreamSvgIcon( - icon: StreamSvgIcons.error, + icon: Icon( + context.streamIcons.exclamationCircle1, color: StreamChatTheme.of(context).colorTheme.accentError, size: 24, ), @@ -1667,8 +1667,8 @@ class OGAttachmentPreview extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.all(8), - child: StreamSvgIcon( - icon: StreamSvgIcons.link, + child: Icon( + context.streamIcons.chainLink3, color: colorTheme.accentPrimary, ), ), @@ -1706,7 +1706,7 @@ class OGAttachmentPreview extends StatelessWidget { ), IconButton( visualDensity: VisualDensity.compact, - icon: const StreamSvgIcon(icon: StreamSvgIcons.closeSmall), + icon: Icon(context.streamIcons.crossSmall), onPressed: onDismissPreviewPressed, ), ], diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart index f67ffb2ca7..2798a1289a 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/attachment/thumbnail/media_attachment_thumbnail.dart'; import 'package:stream_chat_flutter/src/attachment/voice_recording_attachment.dart'; import 'package:stream_chat_flutter/src/audio/audio_playlist_controller.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/indicators/upload_progress_indicator.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/utils.dart'; @@ -480,7 +479,7 @@ class RemoveAttachmentButton extends StatelessWidget { onPressed: onPressed, color: colorTheme.barsBg, padding: EdgeInsets.zero, - icon: const StreamSvgIcon(icon: StreamSvgIcons.close), + icon: Icon(context.streamIcons.crossMedium), style: IconButton.styleFrom( minimumSize: const Size(24, 24), tapTargetSize: MaterialTapTargetSize.shrinkWrap, diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_send_button.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_send_button.dart index 742846c694..e2bf088e20 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_send_button.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_send_button.dart @@ -52,12 +52,12 @@ class StreamMessageSendButton extends StatelessWidget { final idleIcon = switch (idleSendIcon) { final idleIcon? => idleIcon, - _ => const StreamSvgIcon(icon: StreamSvgIcons.sendMessage), + _ => Icon(context.streamIcons.paperPlaneTopRight), }; final activeIcon = switch (activeSendIcon) { final activeIcon? => activeIcon, - _ => const StreamSvgIcon(icon: StreamSvgIcons.circleUp), + _ => Icon(context.streamIcons.arrowUp), }; final theme = StreamMessageInputTheme.of(context); diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart index 59b10cf44d..72b923885a 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart @@ -1098,12 +1098,12 @@ class _StreamMessageListViewState extends State { return scrollToBottomDefaultTapAction(unreadCount); }, child: widget.reverse - ? StreamSvgIcon( - icon: StreamSvgIcons.down, + ? Icon( + context.streamIcons.chevronDown, color: _streamTheme.colorTheme.textHighEmphasis, ) - : StreamSvgIcon( - icon: StreamSvgIcons.up, + : Icon( + context.streamIcons.chevronTop, color: _streamTheme.colorTheme.textHighEmphasis, ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/unread_indicator_button.dart b/packages/stream_chat_flutter/lib/src/message_list_view/unread_indicator_button.dart index 1745566112..5d8c0c8c36 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/unread_indicator_button.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/unread_indicator_button.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:svg_icon_widget/svg_icon_widget.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// Function signature for handling the dismiss action on the unread indicator. typedef OnUnreadIndicatorDismissTap = Future Function(); @@ -98,7 +97,7 @@ class UnreadIndicatorButton extends StatelessWidget { const SizedBox(width: 12), IconButton( iconSize: 24, - icon: const SvgIcon(StreamSvgIcons.close), + icon: Icon(context.streamIcons.crossMedium), padding: const EdgeInsets.all(4), style: IconButton.styleFrom( foregroundColor: colorTheme.barsBg, diff --git a/packages/stream_chat_flutter/lib/src/message_modal/moderated_message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_modal/moderated_message_actions_modal.dart index 7edc807901..098189f6a7 100644 --- a/packages/stream_chat_flutter/lib/src/message_modal/moderated_message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_modal/moderated_message_actions_modal.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/misc/adaptive_dialog_action.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; @@ -55,7 +54,7 @@ class ModeratedMessageActionsModal extends StatelessWidget { clipBehavior: Clip.antiAlias, backgroundColor: colorTheme.barsBg, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - icon: const StreamSvgIcon(icon: StreamSvgIcons.flag), + icon: Icon(context.streamIcons.flag2), iconColor: colorTheme.accentPrimary, title: Text(context.translations.moderationReviewModalTitle), titleTextStyle: textTheme.headline.copyWith( diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart index edf7f194d6..7f9a1a93ff 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart @@ -331,8 +331,8 @@ class MessageWidgetContent extends StatelessWidget { right: reverse ? 0 : null, left: reverse ? null : 0, bottom: showBottomRow ? 18 : -2, - child: StreamSvgIcon( - icon: StreamSvgIcons.error, + child: Icon( + context.streamIcons.exclamationCircle1, color: streamChatTheme.colorTheme.accentError, ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_widget/pinned_message.dart b/packages/stream_chat_flutter/lib/src/message_widget/pinned_message.dart index 49b9a4f219..ae33dcb9ed 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/pinned_message.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/pinned_message.dart @@ -27,9 +27,9 @@ class PinnedMessage extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - const StreamSvgIcon( + Icon( + context.streamIcons.pin, size: 16, - icon: StreamSvgIcons.pin, ), const SizedBox( width: 4, diff --git a/packages/stream_chat_flutter/lib/src/misc/back_button.dart b/packages/stream_chat_flutter/lib/src/misc/back_button.dart index d2c11d003c..8f8f31c026 100644 --- a/packages/stream_chat_flutter/lib/src/misc/back_button.dart +++ b/packages/stream_chat_flutter/lib/src/misc/back_button.dart @@ -26,9 +26,9 @@ class StreamBackButton extends StatelessWidget { Widget build(BuildContext context) { final theme = StreamChatTheme.of(context); - Widget icon = StreamSvgIcon( + Widget icon = Icon( + context.streamIcons.chevronLeft, size: 24, - icon: StreamSvgIcons.left, color: theme.colorTheme.textHighEmphasis, ); diff --git a/packages/stream_chat_flutter/lib/src/misc/giphy_chip.dart b/packages/stream_chat_flutter/lib/src/misc/giphy_chip.dart index c956517ba5..b31659f274 100644 --- a/packages/stream_chat_flutter/lib/src/misc/giphy_chip.dart +++ b/packages/stream_chat_flutter/lib/src/misc/giphy_chip.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template giphy_chip} /// Simple widget which displays a Giphy attribution chip. @@ -21,9 +21,9 @@ class GiphyChip extends StatelessWidget { padding: const EdgeInsets.fromLTRB(4, 4, 8, 4), child: Row( children: [ - StreamSvgIcon( + Icon( + context.streamIcons.thunder, size: 16, - icon: StreamSvgIcons.lightning, color: colorTheme.barsBg, ), Text( diff --git a/packages/stream_chat_flutter/lib/src/misc/visible_footnote.dart b/packages/stream_chat_flutter/lib/src/misc/visible_footnote.dart index d2308f9d7d..99d4fcc6c0 100644 --- a/packages/stream_chat_flutter/lib/src/misc/visible_footnote.dart +++ b/packages/stream_chat_flutter/lib/src/misc/visible_footnote.dart @@ -17,9 +17,9 @@ class StreamVisibleFootnote extends StatelessWidget { return Row( mainAxisSize: MainAxisSize.min, children: [ - StreamSvgIcon( + Icon( + context.streamIcons.eyeOpen, size: 16, - icon: StreamSvgIcons.eye, color: chatThemeData.colorTheme.textLowEmphasis, ), const SizedBox(width: 8), diff --git a/packages/stream_chat_flutter/lib/src/poll/creator/poll_option_reorderable_list_view.dart b/packages/stream_chat_flutter/lib/src/poll/creator/poll_option_reorderable_list_view.dart index 8229d6ccd8..1e0b2013ec 100644 --- a/packages/stream_chat_flutter/lib/src/poll/creator/poll_option_reorderable_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/poll/creator/poll_option_reorderable_list_view.dart @@ -125,7 +125,7 @@ class PollOptionListItem extends StatelessWidget { ), IconButton( iconSize: 24, - icon: const StreamSvgIcon(icon: StreamSvgIcons.delete), + icon: Icon(context.streamIcons.trashBin), style: IconButton.styleFrom( foregroundColor: colorTheme.textLowEmphasis, ), diff --git a/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_dialog.dart b/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_dialog.dart index e4532c67e3..00864ed5a7 100644 --- a/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_dialog.dart +++ b/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_dialog.dart @@ -241,7 +241,7 @@ class _StreamPollCreatorFullScreenDialogState extends State[ - const StreamSvgIcon(size: 16, icon: StreamSvgIcons.mute), + Icon(context.streamIcons.mute, size: 16), Expanded( child: Text( ' ${context.translations.channelIsMutedText}', diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart index ecd2d03523..a2c3be15a3 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart @@ -324,9 +324,9 @@ class StreamChannelListView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.bubble3ChatMessage, size: 148, - icon: StreamSvgIcons.message, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/draft_scroll_view/stream_draft_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/draft_scroll_view/stream_draft_list_view.dart index a84fbc877c..8b37be7500 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/draft_scroll_view/stream_draft_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/draft_scroll_view/stream_draft_list_view.dart @@ -316,9 +316,9 @@ class StreamDraftListView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.editBig, size: 148, - icon: StreamSvgIcons.edit, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_grid_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_grid_view.dart index bab71fd799..b82ec32b99 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_grid_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_grid_view.dart @@ -350,9 +350,9 @@ class StreamMemberGridView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.people, size: 148, - icon: StreamSvgIcons.user, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_list_view.dart index 994f431b3f..aa18ec4d47 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_list_view.dart @@ -320,9 +320,9 @@ class StreamMemberListView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.people, size: 148, - icon: StreamSvgIcons.user, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart index 87067d5f34..1279f3a033 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart @@ -321,9 +321,9 @@ class StreamMessageSearchGridView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.bubble3ChatMessage, size: 148, - icon: StreamSvgIcons.message, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart index ee96feef28..e9d08d655d 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart @@ -323,9 +323,9 @@ class StreamMessageSearchListView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.bubble3ChatMessage, size: 148, - icon: StreamSvgIcons.message, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart index d9c092a1d3..c8245ee7c7 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart @@ -359,9 +359,9 @@ class StreamPhotoGallery extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.images1Alt, size: 148, - icon: StreamSvgIcons.pictures, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart index 4e124a6386..ecdaadd460 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart @@ -3,8 +3,8 @@ import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:photo_manager/photo_manager.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// Widget that displays a photo or video item from the gallery. class StreamPhotoGalleryTile extends StatelessWidget { @@ -112,9 +112,9 @@ class StreamPhotoGalleryTile extends StatelessWidget { child: CircleAvatar( radius: 12, backgroundColor: chatThemeData.colorTheme.barsBg, - child: StreamSvgIcon( + child: Icon( + context.streamIcons.checkmark2, size: 24, - icon: StreamSvgIcons.check, color: chatThemeData.colorTheme.textHighEmphasis, ), ), @@ -123,10 +123,10 @@ class StreamPhotoGalleryTile extends StatelessWidget { ), ), if (media.type == AssetType.video) ...[ - const Positioned( + Positioned( left: 8, bottom: 10, - child: StreamSvgIcon(icon: StreamSvgIcons.videoCall), + child: Icon(context.streamIcons.videoSolid), ), Positioned( right: 4, diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_view.dart index ff84097872..c7e99d6751 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_view.dart @@ -1,6 +1,5 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_tile.dart'; import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_empty_widget.dart'; import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_error_widget.dart'; @@ -11,6 +10,7 @@ import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_loading_w import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// Default separator builder for [StreamPollVoteListView]. Widget defaultPollVoteListViewSeparatorBuilder( @@ -326,9 +326,9 @@ class StreamPollVoteListView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.chart5, size: 148, - icon: StreamSvgIcons.polls, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_error.dart b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_error.dart index fc97e8f5d0..9cef925306 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_error.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_error.dart @@ -76,9 +76,9 @@ class StreamScrollViewLoadMoreError extends StatelessWidget { duration: kThemeChangeDuration, child: this.errorIcon ?? - const StreamSvgIcon( + Icon( + context.streamIcons.arrowRotateClockwise, color: Colors.white, - icon: StreamSvgIcons.retry, ), ); diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart index c30204ea54..6fc1fea02e 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart @@ -316,9 +316,9 @@ class StreamThreadListView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.bubbleAnnotation2ChatMessage, size: 148, - icon: StreamSvgIcons.threadReply, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart index 088203d4fd..5ba12dad9a 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template unreadThreadsBanner} /// A widget that shows a banner with the number of unread threads. @@ -74,8 +74,8 @@ class StreamUnreadThreadsBanner extends StatelessWidget { ), ), ), - StreamSvgIcon( - icon: StreamSvgIcons.reload, + Icon( + context.streamIcons.arrowRotateRightLeftRepeatRefresh, color: theme.colorTheme.barsBg, ), ], diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_view.dart index cb88b18703..0c5702e3ca 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_view.dart @@ -347,9 +347,9 @@ class StreamUserGridView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.people, size: 148, - icon: StreamSvgIcons.user, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_tile.dart index 93d2eae436..fafa1e6575 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_tile.dart @@ -141,8 +141,8 @@ class StreamUserListTile extends StatelessWidget { final selectedWidget = this.selectedWidget ?? - StreamSvgIcon( - icon: StreamSvgIcons.checkSend, + Icon( + context.streamIcons.circleCheck, color: chatThemeData.colorTheme.accentPrimary, ); diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_view.dart index 13a3a5203a..2a0ae29137 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_view.dart @@ -320,9 +320,9 @@ class StreamUserListView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.people, size: 148, - icon: StreamSvgIcons.user, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( diff --git a/packages/stream_chat_flutter/lib/src/user/user_mention_tile.dart b/packages/stream_chat_flutter/lib/src/user/user_mention_tile.dart index 6f024c23f1..02648fe2a7 100644 --- a/packages/stream_chat_flutter/lib/src/user/user_mention_tile.dart +++ b/packages/stream_chat_flutter/lib/src/user/user_mention_tile.dart @@ -79,8 +79,8 @@ class StreamUserMentionTile extends StatelessWidget { right: 18, left: 8, ), - child: StreamSvgIcon( - icon: StreamSvgIcons.mentions, + child: Icon( + context.streamIcons.at, color: chatThemeData.colorTheme.accentPrimary, ), ), diff --git a/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart b/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart index 820aab8fe6..16df41bef6 100644 --- a/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart @@ -4,9 +4,10 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/src/audio/audio_playlist_state.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/src/theme/primitives/stream_icons.g.dart'; +import 'package:svg_icon_widget/svg_icon_widget.dart'; import '../mocks.dart'; -import '../utils/finders.dart'; void main() { group( @@ -212,7 +213,7 @@ void main() { PlaybackSpeed speed, ValueChanged? onChangeSpeed, ) { - return const StreamSvgIcon(icon: StreamSvgIcons.closeSmall); + return const Icon(StreamIconData.iconCrossSmall); } await tester.pumpWidget( @@ -226,8 +227,13 @@ void main() { ); // Verify custom trailing widget is rendered - expect(find.bySvgIcon(StreamSvgIcons.closeSmall), findsOneWidget); - expect(find.bySvgIcon(StreamSvgIcons.filetypeAudioM4a), findsNothing); + expect(find.byIcon(StreamIconData.iconCrossSmall), findsOneWidget); + expect( + find.byWidgetPredicate( + (w) => w is SvgIcon && w.icon == StreamSvgIcons.filetypeAudioM4a, + ), + findsNothing, + ); }, ); diff --git a/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/edit_message_sheet_0.png b/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/edit_message_sheet_0.png index 2a09d8b397ad1ad5e5be545ea8031fbfe86da48d..b73cd7f1662a7a5888b4ae0a49bed08eb29826a6 100644 GIT binary patch literal 4175 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 R;QuKP*!a#(+zr=f{{y^mkhTB- diff --git a/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/error_alert_sheet_0.png b/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/error_alert_sheet_0.png index 7f2202ebe63b708bce719b1da297f239d7e26263..caad68ac9c584f4a1b7221971f6e2a139e1ad031 100644 GIT binary patch delta 306 zcmX@kyOnc-gCk44r;B4q#hkZyHu@?DinIl?Np63nbt1E6)wYReS01w6(d2EABAsL6 zG;N-}#qx&b_Pb8ii`n5K-Co{&p%gwW%-ew7Uo}}0aGll zH{au5zyGB}kBbr+C7W1NIK(C>MmV%3fH^;+51Ys6O}{GqgK^#U*RMD&VcK|6%r6i= z_OfjE+ItKIC(Mk0zY=6{|KZ6o@u8mZvCOXwRp;L8msh6$tGV}7wvJzXvLT}Z=j}6Z lR=YFEJUldcB8$G*WA6S+_02Bs@ofx1;OXk;vd$@?2>^9(gAo7# delta 897 zcmV-{1AhFq3C9hPKz{~tNklwin3;F^%P&Zy(E@?;gwj9~?&?`S2L~{@l1R(>%}1wwIu5 zdj9g*``Y9GjnwN~&Yc~HUwm{dAGqgD+l|vlusE^3r=A(B?>=+<8&=ae{Olv+=o1g! z%(Xbhe22w}>^=GPda)W~jDMqL?0x%*vHHOeZ`N9za)QO$ri+)x>U-Zm@%?*`e`id; z{>?^rVmAmD>v`psvH#U?T=$ZL=bjx0&prG47aT2P?<-#)K89;Y}Rz~^2TPd8w88>{Qkv_jenYc`@4wmKzynjPmu`2|NwH$r)!dTw+ z)*GI)yzAUJy70h8c7I}b`0+Dqn=b!q?0@0QCm*ny#; zdxab)lTiW|lRyF*3Z{9Umu)Wra2!sPF#&NE>|eclb=x}t9LqG%^Rn$F0FJ{)LX+SE zEEmB7GWNH<06k|ub=yYf%ugI_WDdK-36ntrCX+w{2@8yAp66xTO8^{)6O$nUFc$v- XqjtnLr3r7d00000NkvXXu0mjfso2Ox diff --git a/packages/stream_chat_flutter/test/src/channel/goldens/ci/channel_header_bottom_widget.png b/packages/stream_chat_flutter/test/src/channel/goldens/ci/channel_header_bottom_widget.png index 0e7a46ffdd6380eb4e73c5ac8f57872b9fff5f77..3e1309f68a24fed6260201f29dbfd994f65a24ad 100644 GIT binary patch delta 1719 zcmV;o21xnx4&x1wKz{~}Nkl5Af}jU6LXPaMQo?+1n%=&7fEF!1Gho_SuBD2gHw5#@l$x_TqD=s6RoaT7|BcP$(oDgav^Q*L7e|r`YS%&}~;z z4K4;2(T)oxFtU7HJsd*U4VbkW0A-~%qbS1En!t4(oV%-ymRW#Utw~@j0_%TY0RQ+W z@YMmZe*lapw|-u)0T=FDeEs@q;E{*3s+8!|i2(HabAKEUr)aj?5F*`ioRkuyVIOB( zCc2%{iZPgFV0j+!=f45(egH@r?-?P0-#!n#@*O}0)R%Vh3U-0VSm1K z8*2?LMNw}1`em&F`ObT*H~#MXi{K)nM8;De$HVE`hk-w&-e}_5(8AabmU@2UJ*?k% zx)3W*y+7ttRW)lk>Q55)vNj%APTVC7M3ky;u34y>=E{pg)1W8{ z%73LQddF6x=QbW#|CvqGYwY4f>9s|4%31atx^84$#tlWoz&1WhYD2JK{Pl zqF7ungdaqS_Xe($rfKke3D*l_KeO?``s;BIUX;SHuv7w`{Z$$z5uJJ?%h{^l)pvBm zz)6B$*2V+tk~6~}uI{hi&?_$j=k7_PB!8k)&z^fI>U!2?+(s|zuxDQE-_K;F`=5GO z@cAl3!;23A&p(%DNkpfb?}re|_8l;+2m#-ZKP1d#KpRF8p7`Q3ynW9Fy!P`ap~hY7 zBn0rg-vBSau=tsXV(|ls(mGDmTahXRW(n3dWHtlmNQpPTzKoAX$N2Y~Z!eCPxqs8~ zGWXvHJj3H^5GrP)cYC=GGWkL_45R5RhrCe@s~wc3rDxzHvDmk(ohgUw^PH|n!v2pGp)t4Cg^r*iFeqo^~-q`HjgeNB>oM{-?Yn7IIo;@t09kkajp;|6tG#YH|oulCZwTixUSb>2> zw7brqt)N^oFg_Wr{aq&~BY#xNC7e6E+BJV15)$pO2*6=~j%z~;%~mJ%iFhd`hJ!x# zIwrd9(#jW%F|ddTV>`h9)d6(fK-H|ReB*f(MX;<1T-U(^=h~=O(pf>pz#<|5Ko9~) z0~^-{6O=1e=(>S|rX_2`_u;w@X7&_kJ2mXJDoAkL)&Ir7BBEP_5`XCREeyvF!camt z4CqB2iV#qRfKV3+B*PG)l!&B+>$z|p2dXO2t{Lc_F|m##_)BI`a?) zA%ajM2p1=p7E~2#K|m8Ks+A%drh$^a-gDy_SVWW?T-HEDxxv69qTFC$5m9b1u!tx( z7+6G<8w@NW$_)k<5isQj1B-}qgMmdvxxv69qTEoXPd(19IYg8T-vYpwLl+JqE3p6o N002ovPDHLkV1f>yF;f5l delta 1863 zcmZ`(X;2c17VhdbOHmS+au04<8hS;_(%i$PRIDUL68CXI&0PsiuDQi^%h%>Ysin_M zRFqs$OVlFEEk(r*y5^pis6n_Sm-l|XxijzlI5XeOS-x}5oNrccL;kK6QCj07!WxAq zf93Q?c)Dvc*Vm?R(l>7;2Ku@5l;@D{*o6hE zrxSu@K(b^=QV~zDJ)gsO=h~*&u7E1*zeE}Rt~Ho#p0L1;@o)kaJ#5+}1Tm8>t|~bW zm@l5NCBzQU7s=`yw>}Z=5dpuTX$Dq^fXgya1&<^D*X4U0J_^~aUL>3@(wsaY&v{Zd z1^?~|{3H076BMVAg((LaI~JZBa=^dfr@f3qAuM{}RPT`C;3pqrZiSsk_BGYpsJM-H zHEfp!WM{4em}g@(rQaQEpJ$tRn4yB;h!hy$Kj8HWz$2Bd)DeEM?>0xV&Rgv!Ia6DvPynF;MJO ztGif`#n@i#m~pA={$}2i2voCo5hd%&t_%u9Nt-LY-5A;n6mf23J>}Z;cu`OHbE&TI_fz%+V>}wIEeFFqAK`G}~s%mpjHHRd$ zLiKB$8ta(8k5NWUcxY9<9fo$0lE6(b`6=;@li48(ZwcxUN_f%%mZA_*go0`jaC$0& zWg3t6dQX%ZUi7W8(+S^nf}PoL2sPfJa`7~6w`YV!PO>`0=wD)PUy*02R0;IJe6VJM zt~=81#&1FfVZ}RgrEz_V&c|4`AOcS2xaDOwj*V2Nhz5r@WPmtlweW=U#Z(}rqkX8v zEU#C{+p%)B1TlccwpOnJgpS)LXsa2InY~T8FVvpMZ4Ld(zUDF>VQ&l!8pPUxA_G&B4 zaqfPMoCYda>np9OCRV4{dOmpRiW@IQIZKLLafh)3EGmZ=)}KvB+MHB^6gtpER1@Mz zOsDkNXwH9=Lz`TvUxk!jJVYgr6&#mA(BUy~6crtIfxwppc(Y9S?nzvtrLdH8?3M)p zBwJlrXeAjM5s?@1Y4;44n8udWe(1nRcnu>pAds&JG`Rb->UXPnayQt{BSmVA%hf6_ zE)HAU%k+MtA;)`DiJw1CJc%tpgg`32cFG<5nu4%Q`*B#YAS&T6qPByNxqLN;gEclb z7Iu1z&T+R{qjLE5wtLVkbEp-*8Rg?H)=;AXN_f>-ndVyZ@nP?LWfv|nvDmNvK;53q zjXjmu-y3J-Ek(RU-bUWGWmbSF6fDf)1kfLRfKRk4NhYNH+L^MiLQSx@O{70ZRBbJ=2vgySlm> zIm^sB+IHB4=4KS75!4csXdP*)`H+>m#sDS}{h8a_rq{1uzklg?*UVO8zh1Q3M7`nA zo%9t!AohdejXHZM&iCE^!IVOBT6@x#(T_D3(T2IgR!K!HxoP$g2@!;q*_X@mHGxlW zF}J3+^&qZS%pR3-aL84`aFp=%;CfWnW_sV{!Tv$4Vhm1eFUD2}>}Oqnw_?Z)at})^ z3eoRhPOM*arm<`L?XH9OA8tM*GHfCs&ClY(EL98tDKj%}prrS%b8eGb1p{P~I<|3e z`27(+;SAMcrBQF(lPZxi0(`-3>qP_$FZO1}9NG@xJR$=B? z^?tSVst@VFzFKMNQDfi>`(v&lVxc@{I$euzW_L%KYBXP`s5DUKiB|zpoY6{gY>%s% zIPu-Oe6x~_k?WorA)eqm1}Hx- z?lXR*SQGOOYsT*818UhF^wS`D#auL^#&8*xJ?}(LGv~<4DWM?^hA(TZ5h@uHj{oDV o{(=7q?Vj_y4#%FhN2qiWe^KY-eb48(K=+C5qAe0pYvXhKZ;e-$zyJUM diff --git a/packages/stream_chat_flutter/test/src/full_screen_media/full_screen_media_test.dart b/packages/stream_chat_flutter/test/src/full_screen_media/full_screen_media_test.dart index c10b20f155..a9f0b6b5f0 100644 --- a/packages/stream_chat_flutter/test/src/full_screen_media/full_screen_media_test.dart +++ b/packages/stream_chat_flutter/test/src/full_screen_media/full_screen_media_test.dart @@ -104,7 +104,7 @@ void main() { await tester.pump(Duration.zero); expect(find.byType(PhotoView), findsOneWidget); - expect(find.byType(StreamSvgIcon), findsNWidgets(4)); + expect(find.byType(Icon), findsNWidgets(4)); }, ); } diff --git a/packages/stream_chat_flutter/test/src/gallery/gallery_footer_test.dart b/packages/stream_chat_flutter/test/src/gallery/gallery_footer_test.dart index 250890eb71..cfb4cd9efa 100644 --- a/packages/stream_chat_flutter/test/src/gallery/gallery_footer_test.dart +++ b/packages/stream_chat_flutter/test/src/gallery/gallery_footer_test.dart @@ -83,7 +83,7 @@ void main() { // wait for the initial state to be rendered. await tester.pumpAndSettle(); - expect(find.byType(StreamSvgIcon), findsNWidgets(2)); + expect(find.byType(Icon), findsNWidgets(2)); }, ); diff --git a/packages/stream_chat_flutter/test/src/gallery/gallery_header_test.dart b/packages/stream_chat_flutter/test/src/gallery/gallery_header_test.dart index d2106caa2f..d467151397 100644 --- a/packages/stream_chat_flutter/test/src/gallery/gallery_header_test.dart +++ b/packages/stream_chat_flutter/test/src/gallery/gallery_header_test.dart @@ -84,7 +84,7 @@ void main() { // wait for the initial state to be rendered. await tester.pumpAndSettle(); - expect(find.byType(StreamSvgIcon), findsNWidgets(2)); + expect(find.byType(Icon), findsNWidgets(2)); }, ); diff --git a/packages/stream_chat_flutter/test/src/gallery/goldens/ci/gallery_footer_0.png b/packages/stream_chat_flutter/test/src/gallery/goldens/ci/gallery_footer_0.png index 2db682a07a7de4d2314dc01f415ce94656d68715..1de647f17bbe9a226bbeb47c3742b051e044565e 100644 GIT binary patch literal 1220 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKNIvi|3k+<8Q9s*J<#ZI0f96(URkOfvFTIO%Yz$8qR-%&HkNolcEDThBZ54`~vLkym-elF{r5}E*R05iw{ literal 2220 zcmeHJ`%lwX6#o_wiU|cL&dvul=-h)xvrG#EqztP-Ho!*FtX8QT1SwQnR8WX8 zbOe?1=oZK8TWQf+%A-&UN@2#LE0sr~MOr}~r4+2?OKH2We}KuB{pw9lZtl6CoO?d^ z+|SJ^JQNe*wtmZc007*gA_+$T0C590U!*ha(TiSn!;@2bcoY!{kBi733*a>({YXSO zz>{yA0RUuN6yaMUy;47pKR0N?$c*O0W7{J)WxEXpd29`jB%&81U3d8taL$wR-+4`K zg7TKYa&)Har84|x7c%nMWyk#9@FV*j>MM4eGghS5mM;lOnd|PCMEw1xlfyJvAXI9# zRXuHc$h2F{wZOzTso5i+0bSgvFrWa51U4WLJG^%Q1ShB5f?U93g9qU4;1EXmu=(K< zKD>ngB8QuCq`g?nE`V-oad@Ak&e67D9T5{y>w6LZMv)I_h}Nv-Q$m%VU0uzqsi`rs zopW#hsl3-i8TH3v<3*~*;LHUgiPS#MOX9~@#ZbnMN^it*O%GyUmS4=HbRTLK&$BwB}5J`d@%iCJ;Ds4b6$NBj9Xe&Ru8e*%mh?vsAs-O(&Yx5kJ z-$`ZHo(WxB?D@k8XIy)RJ!Kk7#uP9Q#8qBNh=a=0Rl+FIf z>o2sEMYM5{CQ9UTxmxzeCwgYh7FN48ws&Zu6#ZS;i{2D{c0`?`0Vb-yz-S6hGpIX$ z`QEMRWa@ZZ#%TtXI*ZkA+_+Kr)0110qVuZeR2?LHP|%raP_MqchM`iapg*~dC=dwj z!7F#yJ(V3DYs=uiHE5DyM59^5l4N(N>yP$$Tz`FTmz61b2R41~IZhLSpjQa}P(1H= z0|@#hHV?|Hg1KJ%q%+2#{>1YgB!xn;^C|vT?z~FGUr=`>P7iSS<>lpe%*E(15Tt23 z$riJLKQc1XMmJ8rI{-&SLTjF7yL_{{ASM*OHp_0bhLWw1fKeA^hHrl&L|*whSzTd#S34&3RY`ymrE6uKDeirg9{(eU9K zMk~a}Vzrw*Y{gD_#kwb0$vv&C#JyQ!eX9ye(?#aIc^+YU8lWNWTdhbSlN(|Bqn$Yn z>l;NZiL^H$AmAaAGaNDr;T3Q>=7d8@i348%xO>;!F{o6Hge3t%CiLpIG(oY)G{={` z(CKu~Z0=qm=qy`?TVG)HZs#sd#hp9oQMEX0`I%X0+1F|xwd_8K>g(?Ao-hihGoYnc zhwEI1n^2h0;}m&iL$+t${%a@_NqIj-<{Xbc-w91kbhzRnU8yvYQ>4w$Gj8cCEvYF6 z6*Gf+l}MK6i^HAWu{imN?+C3e51f_#x_u|7i$`vsy^iffLxF%eVQ%SWGq7gP!utN7 c518d}pXli)KGyvS+-?9-2Vw}k@DrE+0WyOrumAu6 diff --git a/packages/stream_chat_flutter/test/src/gallery/goldens/ci/gallery_header_0.png b/packages/stream_chat_flutter/test/src/gallery/goldens/ci/gallery_header_0.png index 9e12b056b145b61ac09d58d5164bdba073bfda69..f1be7db1645e89f042b01d87bb1ec6b7fc36054b 100644 GIT binary patch literal 1166 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4mO}jWo=(6kYXuz@(kesf*OvL4j`YgILO_J zVcj{Imq0lSPZ!6KiaBrZ8ulGB5OFvtQTY1G&JwA7|JE&RuRCXQe~`Af6e~P7r&x}G zvHvbJ!yfMq4Lk>#j99o&21 zm0GNBn0LMO|H{&wcGeV#Oj`n@WD^07;j>583NiYZi{?FlCpc}_y6o>9_9wp@G3aA= zM$gmxs$Y)#|FFFu^}Q^@0b#?&Kik+E{5KxP=YF7(62}WGOxZmu?tR}|&DOBJLWKp7 uf3Z6Oe;AGGArJ#N($;9W4U%yC!JN2U%fhBO`YW(NVeoYIb6Mw<&;$T;iegs) literal 1716 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4mO}jWo=(6kYXuz@(kesf*OvL4j`YgILO_J zVcj{ImkbPSah@)YAr*7p-aY6Qnk{ks;q~BPcQ!V*sHkO*39ggWc3XdFk}79*62Ek0 zqxBj#N8hH1&Vyp+N*Mvp6LU)D%=yHz^UlAgm2T449#p&AU)#QKYoxO~5U>h7nD5BX zP!QF`dPG1+p)G;YkcGQLF~Wi8AX5^Dn1J(!1__ANv8#^!4s8n;E^J))UVr@*YaNA7 z?27+v4PPI3^~@Qcv**rTd-Wih4|(9+k}*50~vCnt4o z*!vB3H9rFG@2ma&`0L-le}Db>m^@pV;rWA8Q?-xpumAVw<}ItWtgkOG_y79g;o+)3 zKMLQzdGqIa)&BeIfByVg_2GeI?axn3*BUkyZ`iacsQlfXmA}8gzkYhUesuKmt-24t zzrUYdSGRB0vXv)Ko_tld+tkb~EHw1??b~19+}sRw!J3yZUsnD9SIf)Kzuk~6{m8Tl z-3zX!N9^9cd+UY`0rC6m)_(l>vFh_PUs*Z1u;rIm#;p&(eEIU#w`JKSC0o`CS|~Ri zb-nP)c+JI&7ps1M%e}j|dVAI0I9*-ctA`FbeXZI%Ywp~&U%!@aZ1R2(G*{@|+qYMN zK3SbPYt^b<*I6f$PpJ)-{84^%fEg5b}LTKg8k$2mapyY??OYnyStC`-@ShQdM+>X4WS~z zEn;ir?dz^AcJI$hPF}oh*|MzM+_yirw$<&ApX+x#n^*k6=jZ3yZ4b??`uOPR0`~>3 zYjZj6>;J__OG}sj{`R)&@2{_gYgVl~cI?=y+i$Nud6Hu4`}wxGccaYoM^irB-CbV# z`PtcBb$@^5uYdpk{q<|t!p@#OyVaEG|6y6N^oM)=IHUh&otv!g@3+U!$S6ozSvj|l zVNSDmu@^^7-Sbkf7rR>y#7D=)t$X_P>9y;H?YL9kK5vdA1$gA4DKjASXEvX}!&e6n z9$b*lU?CdqgfAiEN#mnZ$T@yAG)78j)GF0TLp`H{r;B4qM&sKXhF;E&0u2xA%N9>)NJw7Ty7tD+XL3o?Z#gw3J!aI| zCRIDG{1{e#O#zA3g?i zFx{3uT7L7V&V`+PXZ(u#+$TQIfA~qm-`4DJZ^wZ=^G{z?zvMH$?LJXZf9_`JMl;JZ zEA^Mo;|e0d4cFHSF20ndwKp>%Jy>#kvDE36%SF9787qDYhDo2g#br~&5&b&q zlbP0l+XkKa0ixb diff --git a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_1.png b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_1.png index de7cd95a6d5db02b058d65a5405322996de60611..31faaf2e8ab6adb7e9cbdeeb0d09f710b03ab20c 100644 GIT binary patch delta 144 zcmey$bb@h$Lp?*0r;B4qM&sL?8+i{X@Gv+!@XT$R)HL(1iu4kukDLBQc^^2fYN~W^ zlEd!54Y5264O@;0NI7S~8JAkV2)ElM|2ubG=JtD^&4Nf$tL}3dv|W&JO6KTQG=VcR co$Lz#aoflLe|MdMfq|33)78&qol`;+08>6X=Kufz delta 318 zcmX@X_?2mbLp`Isr;B4qM&sKX{$9+6A`KtcH#25$7hTdWaAa{u$kF}W;&G>)T6b*- z2@%lbI<;kM702zqb3R`fHXE$_!gBJ5#{1i;lc&o^vIJ>%9WrEh{a?!vujvxSHF28L zlBEKf=*+Op2`_aHS{CrBT|V;r-%^K1^$%xk_7i`;Z|95H;_LMv><(M2_!e(E^KUKt zV)1#81l5nT$Hd)DdaJ(s_UwJf*>+6inzPM%N#wZ?Kd0usZ=3s!?eA~*OFGXp>baA} z{`B*Hy!^v#=Ebb}!mfg^vsWK!Htapd)n4t}W4Q0QqXtLA*7%Us-B+@_Ro=_%Tz~DD o_h@^9`GifE%W=E++8u{FhRL0wd+nCJX=VTdPgg&ebxsLQ0QLWq*8l(j diff --git a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_2.png b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_2.png index 907fb75960dfb5f1fe068bb84887a769cbfa3777..31faaf2e8ab6adb7e9cbdeeb0d09f710b03ab20c 100644 GIT binary patch delta 143 zcmdnWbb@h$V||gQi(^PeC^b)3zoBl<4A2_XQs&sFX z!|uNgu{;Y6TaF1xIcLBbms-9Ex7#KEJ9k~?_Isbrf=E)U?sFNmU661}=IB*4fip6l a>uiZRYP z%(1RW?e}blPmlUcPQU)D>bHTjRN3daGt1w3M?IuFibc&5TfFBysIW@jZl7Q$ohWca zf#JaBUzvNJvWC4Dc=vWgjL(_8N3$ha80MW9XE^YEcbM`kA4T6~%Y589=f}?tJN6tp7})afp4^^$HVli(4VR@q%lvfx;=OB0QJHp6EQ(Iuahmb* zVxoxCyVewO*Zyl8k%}WD^2&iU%R&b#k*G?og98`3@1*VS+aWb+}^XXm!m!_ z_WPfY2y;BO@%N9z*KdR;3D$QX_02w;U$)))Z??=FohS)31kwhZ>m~eM$cJ=TX<6JuyI&wuddadH&ERuJ!r(_Rr#kIHw(2%@Xxz*;YQ= zy2Gnw@S!---YL diff --git a/packages/stream_chat_flutter/test/src/indicators/sending_indicator_test.dart b/packages/stream_chat_flutter/test/src/indicators/sending_indicator_test.dart index a686a966e3..d83bb09050 100644 --- a/packages/stream_chat_flutter/test/src/indicators/sending_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/indicators/sending_indicator_test.dart @@ -2,6 +2,7 @@ import 'package:alchemist/alchemist.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/src/theme/primitives/stream_icons.g.dart'; import '../material_app_wrapper.dart'; void main() { @@ -28,7 +29,7 @@ void main() { ); goldenTest( - 'golden test for StreamSendingIndicator with StreamSvgIcon.checkAll', + 'golden test for StreamSendingIndicator with Icon checkAll', fileName: 'sending_indicator_0', constraints: const BoxConstraints.tightFor(width: 50, height: 50), builder: () => MaterialAppWrapper( @@ -47,7 +48,7 @@ void main() { ); goldenTest( - 'golden test for StreamSendingIndicator with StreamSvgIcon.checkAll ' + 'golden test for StreamSendingIndicator with Icon checkAll ' '(delivered)', fileName: 'sending_indicator_1', constraints: const BoxConstraints.tightFor(width: 50, height: 50), @@ -69,7 +70,7 @@ void main() { ); goldenTest( - 'golden test for StreamSendingIndicator with StreamSvgIcon.check', + 'golden test for StreamSendingIndicator with Icon check', fileName: 'sending_indicator_2', constraints: const BoxConstraints.tightFor(width: 50, height: 50), builder: () => MaterialAppWrapper( @@ -89,7 +90,7 @@ void main() { ); goldenTest( - 'golden test for StreamSendingIndicator with StreamSvgIcons.time', + 'golden test for StreamSendingIndicator with clock icon', fileName: 'sending_indicator_3', constraints: const BoxConstraints.tightFor(width: 50, height: 50), builder: () => MaterialAppWrapper( @@ -129,13 +130,13 @@ void main() { ), ); - final streamSvgIcon = tester.widget( - find.byType(StreamSvgIcon), + final icon = tester.widget( + find.byType(Icon), ); - expect(streamSvgIcon.icon, StreamSvgIcons.checkAll); + expect(icon.icon, StreamIconData.iconDoupleCheckmark1Small); expect( - streamSvgIcon.color, + icon.color, StreamChatThemeData.light().colorTheme.textLowEmphasis, ); }, @@ -162,13 +163,13 @@ void main() { ), ); - final streamSvgIcon = tester.widget( - find.byType(StreamSvgIcon), + final icon = tester.widget( + find.byType(Icon), ); - expect(streamSvgIcon.icon, StreamSvgIcons.checkAll); + expect(icon.icon, StreamIconData.iconDoupleCheckmark1Small); expect( - streamSvgIcon.color, + icon.color, StreamChatThemeData.light().colorTheme.accentPrimary, ); }, @@ -196,14 +197,14 @@ void main() { ), ); - final streamSvgIcon = tester.widget( - find.byType(StreamSvgIcon), + final icon = tester.widget( + find.byType(Icon), ); - expect(streamSvgIcon.icon, StreamSvgIcons.checkAll); + expect(icon.icon, StreamIconData.iconDoupleCheckmark1Small); // Should use accentPrimary (read) not textLowEmphasis (delivered) expect( - streamSvgIcon.color, + icon.color, StreamChatThemeData.light().colorTheme.accentPrimary, ); }, diff --git a/packages/stream_chat_flutter/test/src/message_input/attachment_button_test.dart b/packages/stream_chat_flutter/test/src/message_input/attachment_button_test.dart index 21b9b050ef..e5c54d2ed7 100644 --- a/packages/stream_chat_flutter/test/src/message_input/attachment_button_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/attachment_button_test.dart @@ -26,7 +26,7 @@ void main() { final button = find.byType(IconButton); expect(button, findsOneWidget); - expect(find.byType(StreamSvgIcon), findsOneWidget); + expect(find.byType(Icon), findsOneWidget); await tester.tap(button); expect(count, 1); }); diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_idle_dark.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_idle_dark.png index 8d1f6af7c5ed2b30172e0cf518732f22171baec4..1d6da52c8b97174e8ab4f96e27062ce42ca579ba 100644 GIT binary patch literal 716 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCFW_JUl1iJbt^z5RVkgfK4j`!ENa+Cb8HW$1b2b~{$PF&V-{I2Z1 zT{@?CG8~wnx0s#5K%J3MK~R7}Xq2L15KPLof4A72!9Y=-gQ2s5VFCw!>ZDl}L(iv* zeR?}T|J|d%lX>&UMuK{XFqTLqYkn8nyZ19B*x3=4EGEg%7iQZjINePNm>d~AUHx3v IIVCg!0M%i0DF6Tf literal 1651 zcmeHIYgE!_82^J#S!tH*YMM>S>ZDS$Q+dhoQbrlNX7kd;S_lK3PLX}z}t<(9v~Yiug|4 zmBMPGDq7mt_r=}ym*j7K5Y36$9C=5_ai6lM?&OcqOOG$vx%GjYq8j}lIuIvNzT zt%aJXUkQhY1ai3;1_*+|At5_oi=RBHtgo*}N(W~02Y)Zd2Ch~1sZ<=~1aW+Zb)ow%F2LQXhft}+g`?ZS`@{k=oaY*`FkHtpninI65JB93H=ybYmQZh9pq@eDg=#}`1m{Em-aPd8scTB|i%-mj~ zRx^>(O;^fs6Sp5SIl04=^%cBgWee@nZdFEFT6fn%-NbnLq7ip$VWrJxJM!$SP)In^ zv@G-U2WAkI?nn8{CQwE!qk0!b(x3s)$kF;UnKVZ_|WVq3S^(H zhm$;~r>C<>Byz#{>|J&H$yCPh_SwLuJay zyPuiWLyU8soz9oD!c6+DP5zk$zma3=$V>c+yjT1J`*#cex7UE-1d8het_s|-`OvQc NV0L5Ud9la8{sUFwxyt|m diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_idle_light.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_idle_light.png index 8b4288a46b2fc614e072a8397fe7eb7c019b212d..c4251c8932261696a7cf2ead8cee22ba5d64a72d 100644 GIT binary patch literal 717 zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCFW_JUl1iJbt^z5RVkgfK4j`!ENa+Cb8HC*E;m+g+h6x-T44tDCxj}GpU+(QSc1#VwH##tQu&^|^;G+5@6V z6)kagrp|lqAACkg{X%oFGF?ZvzNjy2(sGIB#?K-$x`i%8M_7e; zU%seDO{U4=hFK#i6xE-@-2@OS+*&<>;_0el;Mle;z>g3J;InE~Sn2=rKdJ^Yg_hHD z9r*L(ZPpgsQ{keweV7{YM6k-95Y zSCKUOMx#c)p51pt#d|y!>d3xkjYXNb{xFziyRCY6DZ@yC2vaLHub%bO0Pq>+BpS zX>T(7N|q00KQ+_!&nB5t28@eUOB9=7#QQ^;2&I$L&jE)Cdyp?h1>uhc)}q3Yb85-n z)32D`FbcXM+Ym0;01^ieR(sd_XLh@qauQaQ-(_W89MP2EeE!~&vNp)^^<+uR` zESTTea_(K+u&Wq{8acFV(8}ksNlZRB9x0?nTf@;gl0lUU6_0*&ESzpnd^JOF^yKQ&i!dPq%Y=7tZQxl0oh<5q(#w09dw}yB^~`G)ePl-40LF#+Dra8JR59f$^T!q9COsyN+HxoyLKR!d& z2{f#4>8>MXAP@em(ytvxfivxxSM;@9u;hjF8Azf*Wgw26HQ23 z1$nXSGHJ32TIX>(A#94^;^LxcC#QT6E}G)wc%$=r0fFrX>9Pav)T$~UgYuea5=M#HM0dK=V2_D~Zd)ye76jfsf~(yaTN zcp7~9J^Lrp&jEvY>+ z%S8lla0(jt`XH*V6W~PCmLo;!9q3dHCP^nWxeOBmaxtqAVgdI3EQ~M}lgOlKtU;D; vca3Ubh?j2QH;nO19m7iLQ~z%8_ELdgcAR)M$lkOT+$aF@XksJ_kx}v&6AQ%u diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_dark.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_dark.png index 7a796a17d3fbf9f10341f6d81c85e5eac607a931..771ea6e712365d24c446b55e6974899eac081b39 100644 GIT binary patch literal 2703 zcmb_ec~p|=8h>rP$!Lz%lr@S=E-hALr6xIUIXZ?*xo1Q!&6K0KguAGul{Hxdjj5$& zE=0MaNotx>O$BK)5tB+TM2Un-Xl@`PIxlt3{qxSf=iKjb-tXgiA0D3N_xruyd7u66 z+M0Tr003?DK36{g)Gop2GQ@JYnl#$=z?*u43pxM+uMEUbzrgQm34ZP_fc6483V`Mw zwCmo0)aw$SXPmy!kTuE2N(PaCJvoo}@4loPY`pi7UqPcFri^Z9Ia*6fT^IfJhUUsL z6Wu^e|0SG3%T4@I_Utx~iiEK{0K)87 ztpr)CmMsH2T$};;0f7Jy5o&5+{RU$IOx4vv*qJN9NdeaB^;@#sli~e z`Jyy}e2QvULLzkBXyeh~YF6b}Rf(yq_N)4tK)}%HbdOg7Fhc+9*Dt3K!=$MS;ZM$u4XI~v~(r$Nxfmdri_K}VAUUhb!zu}O+dq!}d zR-ktF^eJTvU%1y9l(`~V9NztuCOX~fuhi>y_VxiaJ%2$1e?9~b**sp{yPXH@UKkw)BTO7KIIyxM- zTUwSn92R@SZhE|xN(LghhFvD?1j)pNm_l~6lO?J0I-#6TY8r?c_;Dt(WdIWr;wZC$ z=E9yR8(|tzx`wxFH2HqlG}g+>DzhFUwxPB!x-fngKlb#(x~SdPG6%jyfZ*LuPN8#h z|J_IGXvM!BJ<`;a z0c+f<11vrVe2>~+HNKSUUwljCSaRjR=D`dfg8=LGPcJa#4R@CP1DEdUK#;wB|NcEy zYQ$8U);j+WBn&RRRb|hnJ1}$X*)Yk^N%u;U*Q#f$3O-xj#*6)2aMJxFIpII2U)0J8 zue^NuGD8A~v2AO*^C{!7e=P&Yx?@{x_Uws-``+H(qi^4qeQpWFskNM7vLO*__=-bAiR&ZE8vy$9&0GZW2)eQN2eTUSK zZwJ47;NCfH&udL^=bv2aAauNGod}1Cn{y<|D!gXSy*tr+CkiJ|V5fKqs&uwaMmiCf4Dh_(p zb6@rMt9WM(0oOR)8)d_5PxHs&aJdyDbrT{L9lD2yZ!+e`wn`+DQTep!AOVUHjY>7I ztFJFrynQka6?5}W_VkSKN~p#2T@L9|uQbQOlZ^%}&Y#z8g(wnnVUm^2P`WvTuobnn zwIjWChohP>@N?y>^8yqUhT_svF2$WZqnLQ0LtFt-E7*WomboYwLA>|(_4(HHSR~dB zJ;(`9mEwDPdkcwj;ku{zXlosn(9s(j$__ZnT|GTL;b@{-22_HUXXaacE4N}FP245R zlrt>Z(@-1WfNxh-ZIO(RkG1fL{`Yx|Zlp^0g6VzT8b%pqn7UiWh&Aeu`pEunzBf^% zwX8df3)2T;Vq>Gy)6;(zZ8V1G#GuAMoF8*sUELTq0PDa1M}YIC0AxKvkxaR>i}UQ? z1oz^R?s@s5)+u}cLSa{9z@fsBdD$yV7ggh+7hh*Mje(U7>5xOtJt+{O{fyCFjb4$( zo6$q%%rjZQ`TQOd#TX$}WKZtIatp*~HBb4VS8pXz%Fn2FztlEGiYArL45Jn1W>++y zJsP`xrBC_C0`qY>QUff8wtqvW#@U{YAMBV2qCRjjLHl-FIqGhO!(GL~2UGO?PtKG|w zTbg9MOen_rOKnq!n0=#9W3GwVjb7;0>-Zv%O?ufCC(~jG2bWmioO9A|m(msuwrjEQ z7lXiK-Lq?{k$!Uo1PqgPZ12i`!*_oe(;?m(p5d`IMn^h2WvA(^igs-%e;2y$&Z7bo zH$wR6DtQTY(Oc%d8>YO!0u#S**_9T2r^jw03;rfhUEu11*=rq4&9rJyF~Wj z@ZB~+Ki?Y>xq@fX^$|m;AzO}G7}gAp&8?q#pB9ojy8%`t6{gWp<9&wT`*!PZ{^>-$f^-2vVU)g zH)oM5-cU`2Ix%|Sp^rJbcAF=}S%%a4{+*#lvMI9926*TnJyf_Qmyj#iw|Ax9S+j$A zW?%5kg4I?FuBS*>x5SrP%@g?8KTN^=Q2^*8-(x-9?z literal 4570 zcmb_gc|276`#+IxX)ITzvMVGzHR&>zWN8dpW{|bSko`tuY-6e$Q6XcCLYBc0N{lV0 z%as_6Y-3j<>&#?l#_~Jvz5n0W_x1Xn*O||md7XKld7k(4yx*VaOswT~6Fy!MUH}02 z;MWYT0pP%M@crwS1 z@`^ErdnSg(wJy;*x;k`6$HrIwpm|PGhhmQy{>3lA^L?F5tDPN8V8Y@HIbp7?2X;ItU0J zIlu+PJOT~?h6eY+jHLAeAW={XxPR@iAfPYv|NAxD{ogjE_w?juWl2<#$;Gv`_UUHE z;7O>gtjs48WtCM_y#4*JBaui4C#N?fReW&QF$ZqIw~op5=i}oe6&9Mr8X5!z1+~wt zCCrC(<@uW!*~4sZ5h$X7_k@MXmZypHAP%>V4ps+bA3AjC9&_|J zb2GC(VrOHcuHQtXvzwb+*y6j^V%==u!2MG}VkW=vi*+wAR~Va^EQ~jLWemS_drWA{ zmx$bS4b8G!ss&N}3xz}`@k_vpmX_{zc69j8SJlakvW6$fCnc@`R5a zJI32=dG+d5J6JrJ#f!H}hG zw5g|uprx%%_dTZ%y#BLiuF{F_cki@lptBtb<=q*)Z?BYMYE$)$UG*b0t7mTYUvsU@ z_-TNxK*yw*$^B~#X^!dNPB^)Y!;kfc9{C?A;ZrEg1(2V4kKc6(Sp82@HNgad!H1td zee%|#NUWI5KKP%g`1;R?3A;0icfcG?WrV<3|H<)g?s38))7x&x0x6q=TvX^0Z5V+xX_pa3pEjz&`W_4|CZnQQClmI6zMf&`Q zA2tVofonu-t%%LH@sfIt%np{%`NV5?z^2p+oQiqUGFjK!%?&oO{Bd5b@bx!klwG!Q zT5e8`w9ZP$1<~lo`@y7T?`_Xi_|@lBSFkx8w0&w;$YiXdgvmZHl1*jBwS&zX8XDR? z-qW|)rKP3wOy!jwug= z{V@v&B0(Li9t(acckUciN2h^A2VK|(1eC{%Ym=Exu?wK2NPQBDeKh_vT2uZoS6qC& z0%U=c=yxhQm^pgBS#9<6*#y+u>Pb+L9Y22k`Z|~~OmtEybW*VkdVVT82UI{zaPT~c zpjOZivo$|Zq~AgZ$)%3|wMIKTJ6ibN4}rB#Ms6q4g2ZNe7{#E_WL~~&kTqje{1_y- zazRN6B7A+(3XEuZ>w|}wQ3+^!uzf%TyaNL4K)C{Ca&;=n z!aF#aT%ZL)IM@UNw^otXU^e7yOZKCZ83@%vkW^VSZ7Z-&8qLSo*KDRMvt*>|Z=!T? z|JSAx+yAA%cUA-d90hg$lUg_8Q~{Ck(n+RV15i3PvG6h0M;1#m)D}tXl5DFZ12|J#~*$(Y_;z z=%K_BHFIRa+-I_QifI;%R*HBIVk>xc?Qs0sjZ|f7#e$FwB$rzs;%6M<)K%d-L`FJ_ zVoDAHmKU1UlFbUhc+4rO=pj%aTW(8RTeu^+rY!61CFf?p@LjhitR_45o%1}mmNdQX z;lvNPTrB1jDBO$l9_L)KFRhn>6SoShHYwRD+DpApx->f<5!#Y@qo#{}pjvfd^Xv$^ zw!`o$G3x*3{-RsYY~1>$XZx)q@yUon3ar-u z-Q;&0milws3ptdzVL5o16y>#}37)wB%G(0>OWM6PMYP%?7ob|HRpWfVxlMsnmjEX` z@k~&_PDoEPweu$j1!UpM<(!&lhTgqp(@e06s5mbdv9c6gMaKLDv!@P)8ddg6u z|EV`a)s+>c(;_f?u+gGiUnEM

LkaoQI4;IZf7~`lG))6#LUF77^53Cq8Z=pj*GH zt7Q<9GGKTg7(5>TeYO<4Lynxm;#p68G#mO_mOPPjZhtM6h)nhy^GKSuMe!P$ zMz11ga81#z&Am0u8Ri8gx*Hznh;qGsqP^x-$>1E|J2Jq)j)tCx#YeJ6>V9`6e?hm{ zLK_4MXZkf3pDmWXENP7IzncWnKI=KMFz?`KexO3jGN5dG#`OF8r}^=duZ~f=MDNQh zQF}l)Bqmd$DbFB9mr)t`vz^{=(W!`^EW=-)&aO%6v+bS1dJ)4b!_vJdGH`EAdN59l z8ld^*9%=PMkm<|S0wsVRkG5yWH3!JTirrD~G)zuymUzMm52s(rw3(4he;Y8vz*sL3 z*)=(oAA^c0Z2wtr-siO`0Y!f3zjpiD-T$jD!NH&VErB=rj3Lq21bkXT|e6u37EzC3~WH} zXtQtAf^p>foAjahz(}~{3yHLHOY;KKgttoJtbpw>@D{T()w)M)YdbO&ytOp8z8-I! zRw;`*Ce^a!M#%5`>gX;*u%7k#mMEYo-*rB*;rh@{w$=I?zSMAqpC`&*8Mf?teic1< zns{j93_LJfyv)ZB%JaEecqT=FYt24Ycle7UT+792VD}x8&8hVg@4VTET{+6$j`Cti zhC7^lIz8TcPP>|<=;}4FfzhMZb33|_P(1C*mV(?`$RaqmG3GKf!`xzZ5U;oWw2BEHb3Ij>|mhFUhU=b0OhWQR%)nn)B`g( zzJI7B+(BCwE}5O#P2)vasj9;3(5w<4tv!d!yZD4lb&sM4c`2gF{oeyF9_GqC9qKT` z{w2AS?ERDNEP@Zd*z@#>i-ORRl4X-nZVPLvLOf+L3ukRf(A>HZMU<_bk+gE1x^lv} zQE_7%pGI%eFoRC8XWB)SrgIQgRhBXg5!w2d|? zXR;*wN=cQksLVeKAjonH=-lkO^bXuHRH%b;^Hs0i%;`JaE@m)V??!^|>-6+jgfehaC$h4EDJOo|!3pwS=9rMi8U~w}|pM)e-uPI$g@}Sl?l?4;TQ}(Bp-!c_L z*J_mRzW`?j0dbt=5*y_i%ebkA>azDHKZUlr(j@6tlZa_E)~So@$_%+z89=HDbnc zg~T3h*3X);g$dujY>#%?RXP0$&04$$+bI6op~y-!rRi)5M>nQM-Yqu>c$d4bLn{r5 zGGGk;aQ3SXk4?YdJI3H z{w=iZ;h{H{OipC~H)(KcpCsN;LSaNnwyvkzF-H@-WcMF5o7DQ20~?F~Tr4t`wV+bB zK0h!52Wd#HzZFxiQI#aYpkw5;fY*`W-78e9^06gGE*A2@IZaw@zgpYYQ diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_light.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_light.png index f9b5242a39c1cacae319ef4b80bfa0e7697dd6d1..3a76c145e4bc76e2b4cf99974e72a85eb2ed1fab 100644 GIT binary patch literal 2830 zcmb_ec{r478-EogolwFgyF|&7gd+)MnIn^BW^Ca|vW1w**iFh&vLxZy3z0FyIl~My z8B1m67-c;}ku5ZqGh+*9Z1cVIUElflobS7?@A>0hp6B=6&wby&dwCu?z-$hQ$%z2~ za0qI9#R&j}o`B!aMfZVERiX(CdR=f#EIT~2>uJ9oNO$C+HUzd01#_}Ua^2j z6>&yCJU)R@U@Z64DN?Dp8g{r*u%XJBW%HvXdbx0q>h3XB(Icj*HV0Ib_$jl9B-t!^ zE5~*ZVqvG*`>Q?QE+a55tVOoZIjzF{Pn=!*9=QEVf`!f~Yt;Lred~scrv&|bwlL6E zkVM`O0H0yP!oWl_766(hB>}ALo;|>lr8xk2iHZVk2Ze-yC>3P@(Ea~`97py7z_xac zd;N{t4x$rc$Q?<-QL|2TIKJ|dKb4`X4#isxyXcO69%>2irlix30~5p2GnVDc@cIZH z|Kfda-hr)E#;GMDQ>(UcmYYLR&g#os9oJ9l$yLO2LT8x$(?6gI3C?op8acv zwN%JVeoG_2~1RfvZ8!;(!7z>b2H$$^ z@k5xNqCYXAY?N?aLg`}s3>oCe zWuR9m+pU^l8XTL3BClMX3XGmafjYXAU6EpY>iaumjkGh=^4kI^`0k4~-~oPB+4(CL z!|RYdxm(pRje1lj==cCPN1f1bX{&er0?Tf+7;hL031M^3+m~yKjVMkzIYMxu1^>bM z63TvgEA+_n{0E+t7^qeQKJ*V}oqx*t%nwnI>jpBNhoC7+t!uNEj0SEr;*q-B3 zRspPwJ6n-mNEiEuY?(9#V=s9uYL%l!OORq`{<7zPNTFVR-xI-}Bfi&}?i#+`mtX>? z&><}Q0njJ7LhE)B5il8hiRMn87$aqb=R%T*C}iOgV>7_u>iS}g zd~QAEwt-a14pfj;nIeh)^E_QAIfJ6;uUrv}J2(f3fWlyc{_*sM2trW>XLLOR0n zaZfrwxu=wKuT0-D$gL->SVT^WJ%}yaYu=Tl>>$rz#$<_SGKNPKPm6l(E^gU>9@zH7 zlT75&bJoiIUw_p3IYkcfwN`RYaC0R6fAp--ZqW zPd4rGrfy1tHiZ%gLAmpPXi+uA>`yvVlyj48G3(@pVu_>kO)d|W8nB!4czAosSm zZ=RfZ3X|wRMAfHj_N=;;q^LrzkAOlvrVGb6$q6e1rE?d?cS@%5+}7O!Vndyt=+S-m z$~2#$VD`SOkN$k=j_dm~OKng7Zj-oGlfZvHK3~fJb1UbovNCYzaXg=}!UT?GQRk~Rr7VJECAi96wY>tGhO95C3 zbc$)Z|8j%&(dXwYDm}QXfTPp)|QqaQ}+#z zW$11u+B%8c<8ui$JNs6+u`V$@lu~?C{)P}%S4ble`RsI8$0~zMYi-QhNZ4-wwFq^Z zNhvB?iBlebu%u=JpdXT8Ra1{}xR&*v(_P8))V}q>-eC1NKFvU{ijj2YIRs(>ZMqzz zEr{2juii3}6HEo`=M#2JB7KCKo|f{z}%L@0KMF);0(fk@=8HzC>pp;9=`f-4BgSCj(9!% zq@YGbM@o;b>7+$TA&Ujg&Q9hrj&oRt#Jnv)IU2{PnnpaxQf4mm_b&u|2Y_mJ|*JH6`= zIr06$$?uKXUh$nBUCmo18+P7HeJO>}N%JUqsvdD($emy?ux$9B8Z<@0ma zr8u0$C_XlkG;R(6LzQP_3$FSPt&F{$G!NtlWeE)Pu^)J)`L&=&cKBW1-PY28Cu>F# z2c$tO1z}!a{{+t?g`_zSUwh`alRow?6$`X!J0nOkk6+@bx%IF?3fu84{WnOtvR>+x rEwtY`&OR?4?BRf}e6q;jUkSG*pekW3>jIfI031*&*p*t#ACvwCZf^c) literal 4582 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 AZ2$lO literal 6313 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 diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_light.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_light.png index 53f35b1ba942d4fb564dd4337bf5c5f81c12d55c..368850d1d50cc404bb4ea8e0d370c82df88666c2 100644 GIT binary patch literal 4056 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 H>|g#Htzxb4 diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_dark.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_dark.png index 60900dc5c2acd38338dd5f7df8875a89dbed2d6c..074dfe94bbbe3244924288a5df44cb1fb459ac2f 100644 GIT binary patch literal 4238 zcmbtX3pA8#zkih?744GRVx}~eCR9SmEeVrqOPF$Pr<9qD+?l~BshvX6nC&`32{F0M zXk11mhNdCcVHlTWYcTHPHZI@eoVCt6-#KS}`&;Y0>s{|V^SuA(|NJk%|Lw8$MT@O6 ziZT!cZADp{*+7uQGw?fIdLwwM6zR8tm!z*L%2pa&A<{Sg0RJU?Z7fWof(9i41Z~qr znVq%`N)hs%@3;lJ)J_h~FDsq8nDV)BpyIqsS3y+Ci8eo)U#k#v&-2x}4F%z+B;`*( zPpJI;>ezb;+_4kvSv$s9~zAUZiY*}>VlZES3eWLR2L zFE7z2*dA@Jt*@V(mIh<+ah^CFGQR$ewW=zmG0$4nGHxlXYTdnX@HBn-jMvp=mt$K} zR+gKSb2vXg-x;GMDGeDM;PH4f3ynkE5&((p9WO6zXsC1G zSht?9%i8Co} z8Xci>3Bv|TW>Vn_-Xrbm3@45Jw{KI#$8&6lO&gCNJ9c+0cwqAVm%$>S26rNC)&xr5 z@W6DRXl@Sc?@#MIqgPT?l(Dk1QtgXPH4BGKHt&YRjSXhMA#>Yu?a^q_*AtCe6_hnI ze&cepqobqUfi11+Nz6x(&Ivms2ExBE#p?uA1@?fR*OiV!2$;z!;{N@KQ@2KrAM}g~(@H#ql zMGqqL_wL@JQ*rPV@b?A!yLai9i00AXQ*w)oFHcWT`?qXb(@ZGzyqeH9!NPOgyXZ(# zlxE_2e(0Ky!tME+ADgK-T)A>P2)>4xzK;=7!+?DDSd2HrDT+0al$3-CS*68FL)N!I zBw1Nmsqa*o403w%yB_Y=qc|rL}m#{tGv2><#JaoLZ zdUh{fS9?%%(k1B)`Zu+TO%T<-Ao`(-cm&2+4{>L``l)f!v0{qX|>Sgg^? z`~a(A&0(7lli6j2xq|D3e}3wppcxYhgz}80H)|MP+8w>->C39}TE~yS?Cg?uk-=3+lo5kZ*#Ft(~An1hrZWyc`V7{Hpr7@Xdg1v=m zR!z5Uot>TW6MgsGZ{0fT5&>k&j^K{8agtAxEDnhnJ zwqH87yLD=+C~UqOj&XAvuo>99#w5WKp{1+)=B}<9mAe*WLEgF73*Xq}WPv+BU;UNM zVRa1+mrmaudm(85lI-8+)KtD92euwC{tT0bK1c&(90O*8o^D&)4}kNpmx2n4Iwl;C zplxpbCF~%zN+_QM=c0%`UHzLuo`8Bg${Z#l?#=t41x9B>~05$HykFwU?XSV`ds@>eABE!sB!5 z27Nq2LY$P9g}mYsO+SDBai^<&YAPzOJ`0gkUhWtX5fPWgVCWf!%v;B; znK8)VwUbUZ$o4mV^@0sx(Xp{dlyH~v`}3vZ<3WnUV6iO|6VH}b!V?sBX;;15iy9UP zn)*J31O5O4q@L~Hz2K*V`Z)$7$*4=N1%b2uH_;L0p35FC)25agVYc zz`!ss$=%%@fqHIfcIG6mP8fjINNnZt9IQmz@-iMbZgjk*#>XS&WiE_kHA&6G!}yBX7QDo1z*5y_-GvkWdYfD6iV<}ER4x8LH z17BV7!V78W!GLn@AUA6x3fj}tGpgo$T$W|l89v{6D#&1XY|Q2sLIoAYx-RpmdJ12u zxceu$aVQ?MC(TppMwi4^*)5jM{RS>nbpPx-P~H)!)YR1B(Wi9-ld8NOb`}z zzRAzO0Juk-+z5_3fI4&L39O+fJc?w zad>7b<8q00-XAJa>iVGQQO`;TJv^q7A7p}Jv)uc@&ZIGpuSXif_Z2$0vEN=j|#7GqD2 zVf*Te2%$X=uIpK1MHyJ0|CP*kwAjow)jTxcP$%emPXtqcYx^U><⋙&5EvUZA8qV zX1B@egmLcEm@sE}H?9=)Ww~vb0cM(m`16c|YwG^Wsl^K5(<$*XYZ1?zU3o(lja1h& zVar|eguUFa-b%|gT|^C3Qc6mz6g(u|Pal|MEjw~J7eeRzA1(ff{|$j6&O9QfTMCI- z=-WBzXV}?duZoxV?uCH{F5@oO%0RKza+|1~!^62@BF;%sDsj;wIwl4Lz5w)TEnpdBGf;GEYinhR z7sSzCD`^Ut>}ycC{U?~^jtcP@0Y5Ml=CXP76PZX;e1GJw86K7@5OH0(!miS`rlyym z>@4$=s|o^p79P2=jEfj42vEvld5N5{-iE>H={}$O5!)JMI4eq|T+6(OSK!HS( z*~pz%{?qQ7iEi4OQjLkH@@F#zq;qF$u3w5YJxw|{SXX4s3|MKsNwh7uMdsM#sFK;* zqa*daPM*+8L~Cyxt>>ACdM+gmUKETprKRw=moe~eBTr{G{OYVlCL2!sV@HSFFEz+agIbf@})r`&juRk6Tr+8nP;;d0iJ$WK_W5LOQ3+ z+|)r=2hCq}bcuq(F1vhtJ2i#DjulIK{f)_LCvu5>YUBEG4qIg8>^g}Oy*WQD(mbF9_s=*q#}Fm>u%r8KR=D{9D}k~Gj&F>XvvlpK#L8iEAsoK*x8X|Bhnu}_o&Uu7@8{ZoZ~FcJQRNkvXY)o+Lr|L0 h|IwiRZO2!)x}Fv|>E--saytYr)Y*$>1*X^T{{!>01V{h? literal 5747 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 Co2-8T diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_light.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_light.png index fba3d2d9a90cfc6af85a9bb72d519ff5788ee2d4..ee4d6631778499a22b5c1ab4c669303c31e13d15 100644 GIT binary patch literal 3977 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 literal 5339 zcmbVQc|4TgzaLQ)S&J#ts7aDtmc%g86rsox(wOX7rpcDEjHNP2Oq3W)k|^29sKH2R z(qt*iV7`_i+4p5Imiy>;@9X|?|G2+<=a1)k&NH8L&inH@@AE#N&l78Y&G6uXqX$4B z(7`K4NDB~%>nZU3ifU$H_Zh?!?%YFW9 z&;Ad1J{HSpf*T+4NalIYsDSVuE%UT=3n-s(VPk@j9lP=uw1PI>x=dX8o>WP33#|0w zECoY#lib3W(zB@YGlmsvOOg#f5MB|H#Dk0KAs`sc_r66$OiYaY0bvmlpG#aY7_3r{ zd?}ZUw2)4hf2!&ShCaNbg>;8smWC91Kmb^Y707zIv#OcL)8*T{eqL4(g&h*`en-4T zz0mhVRVM`oIZ%r6dOKAs3LySwcRN&0|EzO)Zce=7u?5QVd9@T5nM4>guC%t=uVG3a zaP6jTGx^BTOc+GqV{jrmZbZw}^z^q;#f{+V1zdOd-DJSeH%%8s|YVj~*`HWqxVzIYh2qrAF<}(v$(oax!txn3o|KUXniaMh)tW z3-xRv?>a4>lxik6zdrtimZnK4PS6b1Q;u~B?z;c?KS!o!#Zo>Y15h4itStmoUH$R)jIB76d_zcmCNd`GL&O7X>156H zjqWaz_VD#253vHYfC>6GA(CpbE?u<400R_{CpU`{FQw@| zaUCw>YxXBkI`4v!p-U*hq0T6YIgDoAU^Gir_E4cK@eB~?G;h7+6XFNYg@ENf;~tUB zTwf^K0Q6BOky@~mhsPuj@8?zZTX&`J4u189T=M*SO$+qv<4_0XCJT>V-`yZTOZJw` zs*2)n9Lu%38K$4Ti?I3esgQolcjq3qO=VlT`d<2LcZ8ttWp=>H3iTdftjAfRewVXA z(Q8{dR({fnzbMrNWX0{zT_|)Cxd@Q_SFpg%P@y+>Z17?xyG<7;ce!`RbS&hmc)zh>2?!=?F{L|23B#WTvrC#M@#1Prpd z&KH#@Q9EnZ+B&aNM}Ch>$F5s7ROzOe`uX~cHheG7Ef6EpE~;w{0bQ(rL zy(%h-H+lnSBs+o|oyr1G(N3VwP}G813-~oSDkgKyMHlXsa7oR1I69qdV0F{|edkUS-Myuk9i0>A~=bI|`Sz zASXgSu`sW+)Vx^I6~FOw#Kj_=y87^)`P=>EqQ5%rf3)O((d>T-{F?f+Simq__y4T< z|Ly@nbrX8!FtAW8nWp^Md-(sfynP4 z4)WC=ORpTQh#t{;V{ryp_prLG+1a}w0)YTNa^%FJ17(484t;%nrI_^kdVSGY9)K_c z0s=!T!ObpbA$pSTTT*N;UHa6bc2T z<@=jYOiZlNNp*E@&`bsV##~lKOUp16yFkCpaWzVtShAm6e>&bdT!dI(%tq=}CNi zeLlLpx%r9$-3bUXiF5??_3PKv^z}HUFkkB(zndY%22qG-Qsf_RQ&m6;Mw+bK@gy?84m8;KK zEJp=8EYV0sRh8>)SfsnVdznJq&!5+U#md+iG0L_0y%dyiOoxDBY9%!6b{<;-h;#Y! zWss4~vnL4&PIlsIYHFreuh!>F5RkA)S36)z=d{uOA+gifo?d$kOvaB0k?#s{adBy( z2p&9WT{QLkw@XWaZqVZHHF$aaeL&HsU}r^f@?buRM7mls)%@%*~SPnnP(?NV-+Tt>Q^5(!Fs*P zu9K9H_}t>;oiMKn9Vk&bsP)EsuiUnHxO<|J%ay?>gKSTf4F^9yF_FKKf%#C}wNZS7 z;jL++1t~T`NmP||CR-{mS1uomMZWk@(y5U`SInRC#Iu)|t!8upltHbn-XoVSOSnIA zQtmBOvrL4}`5kKU8*D&~<=Jm%ik+r?OSSX;1V3#kv(|}0vvdZPrBn{=d&sjV(oM)3 z=|ajw8)>CSGPWge`SF|Prs<}&sA=mprBGLEPwiMM+-KBt)>pDmf(iA<lnfVK6U` z;0UYGsjr#Q;K58Gx{Vy{SjZ=eAoiLG7|sf zT5+$Nf8jqePy|s(G}egjX&V71Z~`oQ1Jt$YN2X+-jyyM;S;4`lJe6VPq@&u&&6fi% zvE;Fl=jz^xob*)G31R5%1iNE}2#!yUbGt*UO^_}Xl|SrlZ4li1un!QS_bUv`&lgh` ze`&DgfZN%3zG<&xjO6bGNBCc2@naw83<~yRTA(d|dIu_!5!*Q_tfin7T{rMP8&uE{ z-qqw1XwfZoY1l90H0?}|Nmg(e+Gs5#1s)&AtMq#|1bTaZ-baBx{P}a#t?e3x&g4&~mTTp{z zcR(eTVQ+J;&2DsZBzbaj^*n!cgYAn-PZVR4^Mj%k)+!fJ1TUE?ni{4`Ff;(l7EE2{ z+_waJ^pXM&Mj8AaCfVa^>vXjRL#=9#!2T+UtttEC@E+&SL*3{Yr7MS@g>2e?^he{X z*bhU(ZVd`b4tfL{Ko1~VKte*sIH$<~n%!R=D8|Rb z7i9n(+pc#0fq@lX3?+dE+i^)i@v)B7V&lPV^B6!=fdGfqNzu!{jrsyQNi@1L@ctW!d(dHe_O>!FY9zxTSr&dB{W*f zp(HSS&LSU)#m;?s6Pui`3IGDmxD(=WMYfFFDm|r?8yoLzP)sIsVs38mImPWPDS*+X z^5lI$%c!pg12`eddlfJkjLGE93FcMQ$;5Au1B#Zf6DdmD_U{5w5(sz6A}>XOxB73{ zn~a?=1&E*x4i2ohaTM3!8sC#PYa|OR?>)LI-raqPw-JrIaPGON$a#jk!UYE$=xz{d z9Y?Ed!*t#Nl6kwM!NOY@8};%)AbHKpL-PU!sQX*OF{Fe@qF?&zzgLO=yIBd4n~C`3 zW(P_`AP*PTyO`7C5s@?Tkf@k%p9)+W#{ZdM^v%q)Gr0z?_UPWRjo1tZE3JcE_zSAy zc!u2PqPKL8YKQ``=*@O(!x!%{PTt#b)LJ4(K)IEUMcBX(I;k6{`MhCkN*gLmU=VQ`@?`&CDQzRI!(o0=<#_Wgn+jHR14O%7JvI?|5P%`AluE((Jb%`$> z^qvtyr0fT_2WYo7D^@pPu(6z<=hwdV$4~9x(b1+rZ@~0)!$uo+?@aYS_;nu>uOh z52?++Cy6_=&xYE09p`0m%)_Iu+)O&Fte{|XD}0;BVXL{aDj>9POG{E)5=hRy|GB9) z@nX6IJ-A3&Jp3nodvmc&C_))-_3}ic1C49c;2`g0C9ns6IgzVMw%5#v_33k3_{E=y zTLIeeSsZBFC6mQf+0t3CTji?i=rHasEkvz9y2z9Zx`{=H9fXlGF}1>B*CJQ7vMl$i|uyJ z5`&Q4Qfizs_P|lSIGo7E(5w2YH>r`@gm`z|b$z(vfE-%c&OK`760+5hH`psY65W#S zU`y>O6+bx0{b{l4dlq{2LkHa<)U(&x47=P7*g5Ph8^=UX4bF9Ieq^Qfaq^j}Pm}+Y zQ#i-yOzQTpMOtvTgvfa`s(mjSpw?Q$6hbjjF-$p>T|#FhbiThEay*XsyDeQ39lp>x zo2iijl$E|pK|h=3o4x2X1S&E(F{uzcjNt`espAd-IUYueKm7wi;O-#(eyypH>SeXIK)3c0vbX(Hf4 VlDBgOzD|O!T)KuVHn{!ZKL7w~Dyjeg diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart index 8ce4bb198b..6b251ac9bf 100644 --- a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import '../../utils/finders.dart'; +import 'package:stream_core_flutter/src/theme/primitives/stream_icons.g.dart'; void main() { group('StreamAudioRecorderButton', () { @@ -31,7 +31,7 @@ void main() { ), ); - expect(find.bySvgIcon(StreamSvgIcons.mic), findsOneWidget); + expect(find.byIcon(StreamIconData.iconMicrophone), findsOneWidget); }, ); @@ -175,9 +175,9 @@ void main() { ); expect(find.byType(StreamAudioWaveform), findsOneWidget); - expect(find.bySvgIcon(StreamSvgIcons.delete), findsOneWidget); - expect(find.bySvgIcon(StreamSvgIcons.stop), findsOneWidget); - expect(find.bySvgIcon(StreamSvgIcons.checkSend), findsOneWidget); + expect(find.byIcon(StreamIconData.iconTrashBin), findsOneWidget); + expect(find.byIcon(StreamIconData.iconStop), findsOneWidget); + expect(find.byIcon(StreamIconData.iconCircleCheck), findsOneWidget); }, ); @@ -199,7 +199,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.delete)); + await tester.tap(find.byIcon(StreamIconData.iconTrashBin)); expect(onRecordCancelCalled, true); }, @@ -223,7 +223,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.stop)); + await tester.tap(find.byIcon(StreamIconData.iconStop)); expect(onRecordStopCalled, true); }, @@ -247,7 +247,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.checkSend)); + await tester.tap(find.byIcon(StreamIconData.iconCircleCheck)); expect(onRecordFinishCalled, true); }, @@ -269,8 +269,8 @@ void main() { expect(find.byType(PlaybackControlButton), findsOneWidget); expect(find.byType(PlaybackTimerText), findsOneWidget); expect(find.byType(StreamAudioWaveformSlider), findsOneWidget); - expect(find.bySvgIcon(StreamSvgIcons.delete), findsOneWidget); - expect(find.bySvgIcon(StreamSvgIcons.checkSend), findsOneWidget); + expect(find.byIcon(StreamIconData.iconTrashBin), findsOneWidget); + expect(find.byIcon(StreamIconData.iconCircleCheck), findsOneWidget); }, ); @@ -291,7 +291,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.checkSend)); + await tester.tap(find.byIcon(StreamIconData.iconCircleCheck)); expect(onRecordFinishCalled, true); }, @@ -314,7 +314,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.delete)); + await tester.tap(find.byIcon(StreamIconData.iconTrashBin)); expect(onRecordCancelCalled, true); }, @@ -454,7 +454,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.stop)); + await tester.tap(find.byIcon(StreamIconData.iconStop)); expect(feedbackCalled, isTrue); }, @@ -481,7 +481,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.delete)); + await tester.tap(find.byIcon(StreamIconData.iconTrashBin)); expect(feedbackCalled, isTrue); }, @@ -508,7 +508,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.checkSend)); + await tester.tap(find.byIcon(StreamIconData.iconCircleCheck)); expect(feedbackCalled, isTrue); }, @@ -534,7 +534,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.delete)); + await tester.tap(find.byIcon(StreamIconData.iconTrashBin)); expect(feedbackCalled, isTrue); }, @@ -560,7 +560,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.checkSend)); + await tester.tap(find.byIcon(StreamIconData.iconCircleCheck)); expect(feedbackCalled, isTrue); }, diff --git a/packages/stream_chat_flutter/test/src/message_input/clear_input_item_test.dart b/packages/stream_chat_flutter/test/src/message_input/clear_input_item_test.dart index 68d1f430fd..8f896a164d 100644 --- a/packages/stream_chat_flutter/test/src/message_input/clear_input_item_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/clear_input_item_test.dart @@ -28,7 +28,7 @@ void main() { final button = find.byType(RawMaterialButton); expect(button, findsOneWidget); - expect(find.byType(StreamSvgIcon), findsOneWidget); + expect(find.byType(Icon), findsOneWidget); await tester.tap(button); expect(count, 1); }); diff --git a/packages/stream_chat_flutter/test/src/message_input/goldens/ci/attachment_button_0.png b/packages/stream_chat_flutter/test/src/message_input/goldens/ci/attachment_button_0.png index 0f0cafd3de1d69b7c03857b4f9d94e0b228d84b4..ca37dc0d4a294676f35f477314816bffd01c3731 100644 GIT binary patch delta 143 zcmX@bb)0d6Lp?*Gr;B4qM&sLChFlDaJPZdvf4C?%O>CcKubtCD)i+-z>SXE)ZrsQ6 z?>hILB#vH1leP;>I9Hm>W!B1^J%6#k+@n~~IfG}RVaqWADO`+YKN1rCs^%AzcS6%~xAEl$BkR#{vi3raAAB$qw} zuVq?aycf+l`9A%&x#xV6-|w8?@2KYI=NEVhB)n(`lu{TZ)PEEP2{na5LQP?iP*WHr z)D-c&3{6craL>*X9UVnKj{_oU%(}dcBS^V==`=1qgyNn?kjm zx8I%{y&V7f_ewp2|(xNZrL^!2U%Evx`rmHZ?382 z!NVCo|MDw7`{D?blfgr;)oRGvlmWoZj2o}lE6c9pAb(e{{lUbYd$4$zb91)PaJmJ* zKY-sKz~k{qYp16lJaI&LMV4JfLA>61np=OD`g>|?xYa+%-?jk&45=xkq$C4iw>udg zxeY*4;#w&HJgXE1IeV@H_v|bH`P*|?``Q}n8(XCLcPwv9?Zz{$Qnacle^a(?#XuYm zCzm>{0Dq{}YHCbx(Ruj_Q+5XcMTL2+SrZSy@W?1PZT$eGZdgzD;#rX+#Xx?mZ$cD< zxwW#wh#&~Gww(t+qlv^+RStkCE^xB$Gyp0U?5#B`VqXyu+pPim2ZjKMjg6tCxR9on zcD&ws08CZotXQE1z}kIHid+Tx+et`>V|Zkg*?&imWceT)BoGj(YiO4GyLVMHIpx6G zeI0;=ggA-{^8xV8&2hG^1AxfLNKBPwT)W=G(XWqj_~>`CGQ4b%E^9Xqrwf3L^o?X? zrc>YeFEDd8f)KpwwzXf7e)-A@BWiU7XIk1}@k%3#foJwZ7KkW{oWIZ&{E#59+gwd= zpMQ;^;b2(J-jqRlngIZZ(?ypxn89OXqS?8lfY$a7-0onMD=*!NTCILDAQN})GBe`_ zAa~nV;^S9K*9}1su$Zf)wGE9e($?8!uEOPdKxfw#0Aiw}FqZ6)XVv&%}S8cn3s4v0ag9zT@q zb|-eb6YUDkl3-A4F>%w@&#nGJ05%#nusJ(ZjvF8sByrtZsoi(eMqcjgqcD&7xYZo{@fQG8D%Fx~FGsSrmR_&JkeUKOZ=a3viM!O8Do9RB{D05d zh&IaZuH`GP2=%+ z*pRZG%#1X0wq`3*`uRUCYhCMk>ZPguhCxD2VUSQ$7$npb1_?EVK|)Rc02Ef<(vgd~ R=qdmJ002ovPDHLkV1ms!et7@@ diff --git a/packages/stream_chat_flutter/test/src/message_input/goldens/ci/clear_input_item_0.png b/packages/stream_chat_flutter/test/src/message_input/goldens/ci/clear_input_item_0.png index e1ac9e1cfca5e3153a1424c77dda7baf1ee692e8..8122de5b8f9f7f03e893759569eab8338c942c3f 100644 GIT binary patch delta 260 zcmV+f0sH>12D}20Kz{({NklErci(#W<N`1 zT-QaHh{&KsYOPq7g)XtKD{AGw4k(dQ3if@c3IM>iZ78Ks8GW=!6n_=k@#lGBo@c7WaU4h~ zQ5m!d{2qzLB5H)Z*(2^aOEVj>h#D4A!y;-}L=B6mVG%W&5%~c|=T4Kuu=5W90000< KMNUMnLSTa6F>=)a delta 763 zcmVis7w)1qwV_auxHi$bt1I-@_SQ}=H%Pu+r!(iAugpwOBHrHK zBtHP{e-{uIu}IX2MWRM55;bCxs1b`qjaVdV{1}m`umegdv43qF*LCT3yL7umzR%_5B~q$w3?&iQbq5ai>gp;-M@Q`L?)DWdgkW!P zk4mLNCX?w~^Zfjb>$)nXsEho`!@~p3W|MqAPq|!12!W<)93CFBy}eDd*`(EKjTP%V zDE*Iob5$LoC$2?FWZ17LrR3t` z0$tZxUtb5{{{DWb#Hd)~qO`SwAQ<`m`}=#}=TQgxZ?XP{lE}is!k6Ph2uh_Axm*t0 zwy|xSTrNkcR2tfv42v}`N+PLLYG6zVLAhMUG)*2KAE{QWRI61S$6;e*wS*tx=tpO z;pypVpzqRkou#Fvf$KM!Oj4;-NF)-ImZIKk^?E%{PEPpv_?T$DrfD1>ACpWbRmxEB zwdUvNDHIA5uP+n|$`XXx#y1)bEXx|Zjh~*LrgT^=Vwz?c>BD`ndVPK6>go!|aYoKF z41Ds!b%;k$ZuX=UTC-5IF3WR-9}1@VHl*-X$-?)Zf-8@l>bKLfAC}8qQoLm tBNmAou}IX2MWRM55;bCxsPSV&egWrBBBW8h?(zTt002ovPDHLkV1mhyd$s@o diff --git a/packages/stream_chat_flutter/test/src/message_input/goldens/ci/command_button_0.png b/packages/stream_chat_flutter/test/src/message_input/goldens/ci/command_button_0.png index 755d3e5dabf601716fb1efed1b54e943f5b19928..0b2e32388a239710f8ddcc7b46ab7bb4c66c3395 100644 GIT binary patch delta 146 zcmbQna*A<+Lp?)@r;B4qM&sLC8+i{X@Gu+{Xni`PEpSHL!#PTlOe%5L;`5(OkM?q@ z31|BLlC{jR<(Pn!bH=3>o7MbV0=MfJ{oZXd|Cv(T1qr8Qj$TC*TnyijhZ%Yx(v#Jm buuE`q+dIr)5@g_JU|{fc^>bP0l+XkK|E4y> delta 480 zcmV<60U!R#0hR=iKz{+iNklA$4YQZ|Rq6p&Bf;b2wh^>kujygCA zf~bgsU%}5JI5_I2pp%n>ARYXNZsH(TaIr-w*xKeCTCp~^i}%)3?pXpM+?yxAT;5CK zA3729MAAS?2z#r=)A9xC$UP_6q zjC;8Cr_YZN6-lKXJOw=w#Z;eM8iyo8impS*iIZL4U8BDu6aEkh}Yv9hDJ;v zBu--2TudX4TC{+jbc$16>mF%amg;jQ$6>0fRsism7QF`mFi(>K227JM1|5F{1c1n@ z_kf3Ve7vsX^}3eJRZWYjrfE^rH1(bZ%v5WwTHDut-)g_Rt?hPOH`m+VvmVS;Rjv1k zztc2LwK+Yj^~pvofWKX9t+rQ}b@}C@S{wR!9XTLVooqI>IXx2&;K8-l>hjA)U0q%d zd&~|QkZGFgx5rQF=;*lj6ySfrU0-kO;?wz%w~^rjvRp3fx5xje<#O433P4p=?RLBR zeEzX^yIt=o_iCu8u%^XAJb+)$*AKSSVyGi`tbjav{6su}U(#~9tVfTY^qz6Ah6%{Y z=CoF;quw(B{?h8`s7^Mgy=UB;Ap$Zj7PUD&>pcVDyEdn1wHV|_Swnxkic^p30yFXT zyJ;hseL$vZs`bf9?->9;Y`s1i;zRH31Mp-`3K~9{%-Y)A}Bru2(&aT-9WR#~FfWtDv*){uDu$X555`Y6V!r3+dS1`bn48Q>z;Ov@z zKmh1MKmdpc2mlcQ0U(nL1q&6K|99!(j$Xcc8~wXCuV38z7MK$^li>wY7symqt@nt# zc>3&l?+E~r5AWago^U5_li>z67m-!(0l&K6|NXZ21fY|D{_92W3BNQqli>w63;z$~ zJN$nD0N>^Yli>uKlaK@+lfVWF7Raji03OcES8sby-~haN{i62-9*7%}@h%scs;c!K zaTiaYJ?}jMAoAh;yWSJ-#0`@%1{{+x1|5?y1|5?y1|1m$1c1n@_WwylQ0Gg7OUO^__mj? z-u9jVzl=ApU-X{9H@Jb5F$FCbk*TU$?-6(L^x5;?696I~-oNWT;ZEF>0R>inWc~qZ zt@i?epEtnSHT!_<+rIY%fS)$P*){)w?C1X`0EcIUvupMN+1=juo&fOEMmW1>ACQ~t z>)sOpe%kf*w)ccPn}0xVu7~(#6mUpd-CW=F9&u;04@hgRw%e=TGXQ?r_Ufvt=KsNW z_5rD?y8Loc%`F6&f!11Ge!1v>J>y;s5s>|UU)!t8-ZKEcYkPG$#1mLGN>K-L5{J|7WNlw+|DL`r}7gpUyuH^#%d+*Y^AR zeEzZ8U>|U&Asz+zlWAJiqsLEbb#&Bw3UJ_VZm#R|`FXVg{&dNo4H=MVuhu7<+MJ%% zVlmW33z&iZeqWbgE{1-kaqw@d&002ovPDHLkV1hKdNw5F_ literal 2496 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 N_C4}G=-s2A`~{X8WUv4L delta 1540 zcmXw(c~H`67{`Acp=PZbY15%LPG#-5+In;-3U*lLS!o)0)~0TnN2nmEV6E%6me~HL zG1t&G^N0jWB2Uty8FO_NW0bX0AvZ5HF!(8o=>FK_{pWe-dA{#6&ph+}ynR+OY$<_Z zR^WfkH;j63Zlu5X1I4y$esQ*U&(VC38x^w&`>1d17H09Gq1wx$uTS#MSiThCUF@va zS+%2S^AA6U`)YTn-`X8nlmrMo&NmJRNrM_SV(})c_rLT$mgc&S<=}$J)9Pyy6&bG? z_Y#qsQiIHaglBJQ08s2t0Ddwx1;8Sd6KJ{4?Y7*6vGtjq+BLeJ`qVkPHB78{g1PRy z5p$h*ejs$GTY2DR_icko?wl@`z09CrRLbS|p@yQl|s`3vw>mVPM3`_gAm>(ZBTp(GNCy;6j}k0A&ea+|2d zBDQY82%kP3pkX@6S5i_kCu$mk(ch8D(i2$)g%Z_bGMOxZZni|~jB^u7q-1ZDypYS~ zW_@Ih7FxVn(oA|mwVNe*q8L6l)&rJBHK+dup_zxuvlQ=yYQ$mkM4-|Pa8te#CM!^l zt*!59$j;u@^tnxzObi@|Iwlom8+k0cIs2Pj0+4hREI{pKWR6p;PbBs9V4j`gyUByy2qPFdD}vWF4vwL zTN(Hfc}oF~s_QGCRa_Z?ghaYU&P5nzyc}5!Z5KyeJyd+(7E!QOv~-7a@Oj8+*jjfL zkzbIVi)Gg=mL?j)GW}|$-z&}d?v9`Wd0_n* zD(9;X6F@zE4_>jW5fd;Sa{#nN@5REbboNNw255+$IGO-L#@HC2IGcY=Z4Y=IeWoDi zDiWMWWn*2FyqQZgBS|8_=e;&Txvm$)f*GdH<{1Y^OEQ|y$}eU7quXrMg~*Kk+C6Iz zl3e7{Lj~YG+S!~ko_D-OzgNv68-?MF+pvXhfDHSkV!fwE?4&io92h;x#-Cr5*JyJ`gI>{e0Gk{H!mzK z5c>gYJe^Kw>%P)z>t&IeBKZG_gCx2xG?#Y5-fI)bGMI83=$zG4Jc{S2TNp{~R zto(Kh+w^4kAlr*FGc{EY>-)7+1Jy9jSG+H#!m!O#qrAMlAnmkt$jDkZ=qMBjq>swp zdtpmCoVt*zp}xN0TV3ODbro(nNLPtu9e=`aF^Q&Lhg!=^AsAF+Iv>!Hm|PaLj+ zGM3j=iOYFQ`~Aa4qJ_@kreAeyo<19fZE#{Dfy?7PuS7eQ4XumIup>v&MUqt=) zA39@Drx6$6ZTMB6zU5abI4?e~_5_j!yzDOMq diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_dark.png index 1eeb82f5cb77eed7aa80a5c433937da18150af14..0f80e3136392d21bf628c85878342771cb8e7dc9 100644 GIT binary patch delta 1629 zcmV-j2BP`GCB_?&Kw1rONkl3Roeo9&(!aF008!r zvta=v0+T=ilz+(ZOPu83U{nXAQ4NO&)$jMK*Y8)a*ZUHu<_Xzux3%4DYO~qYYPG8S zyJg+q-*r6}e6@O2wSBCEk0*z9cr>kk-$I9OY&V;l&u?onUp(eh9iF5|3CL(Xsl%gb z4F-d@4-JvcdR_C6x3yd@x*i3-8m$LpG@jJ-_{2w4?0=wb&2Db$PDG#;tp;Q?9@p{7 zH*Fmb9>wkKrk0C&*JHrmYwhC1U^uMl@w2uL2ajfYd{V=MgRaMbz1Lbmj!&NX*owVz zeC+uOO|=q`@#L@ugD>)pU}!~uFsSk5unqSu4a#5SjruCWNDQLU`8I8x)bDsx$sMqV&Xguk94A@(Z-}#3Jqjn~N4!D>O zxyAt*^xZ7T{&MXOa*YGhANcr+{pDwNAlEz~Jug7^SFhLebzTTHfA{y|19*}wACut$ z7=O(JvTc1!bpZB2z^QqG!KZq6xd^%^}6d3U~e@J$a=MEXAG=z(#sRs%yR!;mFKyQAy1&2cdJNcGy{c+_1#vVQ*YUG& z+L;KxklWdHEfsI?mjXR~Wxhh%TeZmzo?0rp;N z0a>qBbvyH-WcS_9u4}cr?|KZ_d#zjp`K!@rT+@?hwh3hiZR>V6tL0+e^&s$7XgwfR zRfFNMj!(X+!C=t#p&_!_tZQ~{ zfB^9I@!1lSaRC>9WZ3orr_*V@eEG7b(`ij66F+AH{B7&?x)zH?&1SQD`}S?!-rjaS z6913ZZY1pY`*nJHTIc8I^~WE7)MBxy<#Ji;^}3D215dzUFsSi(T$9P9o$}TCM8cyLVOp_a4LUX&jL0bXq_C^iy43U9~e2z+YTlUDf&d zc^w`ec0CFYG!DqCSFh^k=Ek)S;8CpC>$S(QU@&mu9C%c#)vAWW)^A&*A$Ot!5D^dn zA_4+HL_h$D2nYa?o%&kC#l=PRxSxOi`EUR2<>h5`4xqWSv$L*8phZ@bu>nL6*{MCz zKWA_Als%aN+F=EgQ3699k)7HT{o>*xdfeyirG9yN8Jz=Y?(FQW>k(*?6_Wu1L=Sdq zPxQ~(+dO4YW`K5BfsxTe9b~8WM8CMWh#vPjd#PVuUPk8tnmao?>v{xQWCfE^0z(gx zo!S%qbM`h**^?Qd9afXE0Yn+usXfvE{jZ;*$G!T`n@2wf(A?*|R@r@4ld%Crf7z)$ z(Z9rg$@XLhXonRD01*KJAR-_DL<9tYh=2ePX&jK%YE^^5pq*&|K4CBz)M~ZrdJGRPZ*EKb$ff;^%y+RI3U;8*8&1Os_}SS*VorwkHG_t19EwJStlna z)$hBU0<^E+@7J?u&+50|e(QP^e;#Q3uD@I^>&=@t_2R{gb|wP&i|@bxzJC4n*IF)@ zU5~;8tt=a^uCA)r>(%M$sSD~rD~7{i{qVyNRaN!j!-uZN;S+jQwQb`-{eHhrPfzRo z{Jehu{r6ffm$h6jYrS5#ad_Yf7!HRu8jWf)nbh;=&+GN;*Y*DW``T!Ss_>tF`b;=n00000NkvXXu0mjfXU|G) literal 4802 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 diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_light.png index 87e6cfd76f0b36f33dde417a5c4c6cb4e2ae7e86..b72aca0f2b6ee66bbff043989481166889911492 100644 GIT binary patch literal 4306 zcmeHLYgkg*8b)o>E^0kwPAJ&a8J#m`O^4LRyf9_aRVzgdR6J$rfR`pk@`idkjXCB0 zl!>8^j;JVLB6!P7VEH$n}H|iXR7Z3w@s19&v+XFSG-ZsdS5T{ zGV(Zx*=s2Ufp)~7asSTy_LJFB&(MF6$h>*h;f{`u!=Hci;-kSA#<^zujx+w=`RA-` z0{W~7QT!+Tw^{9HZQoa4&Oh-Bvldl+Hf-0=uVwv00kg4PVsKja<0)-ESf_Q2t|)cI#?-AZSr{AkR=#8thv3z$KL9>|xv zX;ad@B+!nXJ3#yN^%3p}&^;p~&`v$Qz5Dip+&>L|+JyhpNDzpBvXRcsv67RMWpa5X zlgX6HWORE%?R^2p*SCClc$h+^GES9TGCvehC6!8}Gd`QNFdOvGihtSyNsj~|DqPu~=U#2_5PM!b(j&wSWvX8X@Zq8!gl zhjQxceRFbhW?_cm+w2l@CAHss&5m%+Is0ZgIXTIa4PoUBY#ldlSG`^B43)g5QE?)t zdVO3h59F^Xnph5@uU{z+2l5GL5Wwr9G1d6h=yNaRW|YBfNs%3v>+R|3X$#vLo=TJp z_`m|1cu6RRlsZkNQURAOC9NeTCBr-G+e=fX!X+)u9XuW(Je>I+{+wDQwaZMia1@Mi z>L%jdLn?ZEdm)yVq3X}8@%GS6tyU{IuOIeSwXK0>P!m4dxZVqtKqn|B_t-sGdg6Ob z)|)tl54*S5BSm?Ly|=7M1m9#6N=L0Fvk z@ZiR6^5sxWIu{pf89RJR^YZd21qI>j{&RxtM*92vV?R7MELm~V|1o+G6v{DxQeI^% z5i}Yt^lml1ySsaNc`Ie82D}A%>eQ+|k6sO&6OR0`lz|Jm+@%j#mdy;a8L>xw9ro|v zuNoyuIC0y3RTvG*+Weqjd7qo^%_Ae*f${OHX%8L@y?Ei?giID4?`qvf06cVETlC@y z*~Na%u^ga12(hw?P=5tP$jeK%o{_maG%LdE=Lsmd0<0 zoSIQ4?Z!Sz8d6)CKgh@sE-&9!snx=^HiSy0LZQ)UB9X|a*tZxx{ypLw8X6j7Rd{5u;~EP{<4zqbJ|7E^Zfd z&>L~kul{16i`p+OJ^kP)H&Lh6yq7$~)!ygcnlasO!W9_PWo)sE}0@9&*b)ieeOI(OD4xeF( z3luVG0Gdi&8Z>acK_>r9hDUP9k=JR~G@2YIS>eS>OLOtj(Of!xX=d0f;@SL<5jDPF zjO*?KM(lpOHYaXYS?NAXh#!XCScY@0Otz_Zta;d<+>ohx&#I5-@RLJ{A#C2ORGE?(+T z(SU3igug8cTwC-kyfU)&`y&w5b3?1zxB~-8@ylZ==W=x#G)sU;EX=4(y8e{QW^dN4 z$RczKRjG)XXj+_5Oj?5n_a5*U*AUBN;~KHwg4;3{nRCn@Ecc+lTY@GEJ2gi6YOj}< z*SXxLM!^=N>MDUc3Qg+Ml*)yzhW)OW5ahVi{wV&hy`@QNXS0DBXjvIYU>Zjo_@9gA|%UM;ghJW3R$t8V%9Z_As0)>H4Gc|ciPr0Lz5sg;`IAObC{%FolL|-O7)LXiXoGdJ zKqSB&0+2c_Uo*q*qto$$fkm*y)%oa*laS1HO{45{YmHiahz_W@KRBr3Psbb^N`CY> zKcB^Falz#t^k)hK;=-g$?J1^c91e#Ken(mUMjTuaLt95f6iTU7s*t9-U$j!HqgD@hgm)cjVbmEGbJ;g9QvbnjL16Ukshzy<;)v;i4GfyE92!5Rd?Tn2;BoiFX=#K#RsbboQB>hy4 f{%=b1PMuy^Z6=w&XZ!lU3ecI89`4NJfq(lKPiqZF literal 5575 zcmeI0X;f3$md9@bh*$y!nFItx1q7K9WC*4dL;;mKAVVmEf`-97X2KA(ln{haP7E>> zARt3PWE2P>Q$!^gWDbzRFpn}LL-MX&^i*EZUVrGd>VCL)oqO*&=bnB3d+*=A zv6fd&`431O004mh@+D(y0DvR{0MudsKJbpk{3`-@;r2g&*>*p8gzxwG-iG*Fo1OyF9u@9zKXRztiTogmy}C7d=C)N0S; z0aZ%ZAE2AH5q;=ymmjECUOHMKDwJ3#Qj`-kbnfSFrS$Gwrxmlgp(mnWJ@&XySs%x z=Us`@^Pg;OYC;4A2UDM^IJtXyy_|-Pz0{P4%*@UbLsutF%*{VI7#cowu(7c@?Odat z0N)r0*%vtdwl>EMiUKMB6@nvh-$Dk*Bf0y|nE2 z{B}g^X6vJq=B@Xv3vHFK&3p>2^MmDa(mV+V0&#hn`hmAkyVlpZ3i6_Fjy2!c@ENMe zrsDZZRUrkoO7_wg;%ddlyY7zut=1nYp^+?Yql^ptcfeV-6Y<-+R4ZGHfXD~OsF|++U7UIUlIu}~g012Y=UNiAX7TgP#zv#Z2*~Zq z>jwn{M8U}~L7QJx`7s-^P4Rq~9!PMac5?3uBhvTCEkZoB?5FkgQuYN=FD{^zW$tdQA){Nl);p3I6S-ma7YH@2w1qF_o!-k^FeUaqo=;-O0nb^+fx{N7E zppHBwb_%6By|54u_MZMPB=5&<5y3AY&<=`ro>8q<*n4|^psXcSTKz%$Y1x>eN(^!y zJQ+5K0IXI`051%I$eJL4ORz(L^=$27N`Ah=V%<_h$k(^L=#kn$`sdFq_-i~2gTchT z@tg^QY6I#R;cUkWBLDY)S98FLg7>|bYO12J5BWUd;! z*~rHajmvAH?sp#F}Sc-8~6liNF+7nh!OF8BAl9u_qG+VnMI zb+R+guE^d5i9}{;{moKe7F0EM;wiHIILS~51|yTn*uC|BsuPo4GMzdK^C7Fyhcr)0 zNG{~_Ud*$Xd^oke+2)ViL1UP-t{VJ{8`F?>clvQKn##O%q} z&q$GOe0bKz*_%(ob`UpPW2rJ?<#qZw6tbNmo_Pt4zF>jD#1FcpFB4f)ZMn*$l7j4A zxqp`8!?$;LVRC=%|T)xX3-r~ooQn4>_)(hY3Hdl+^*r+iHzbI*hkbIYx?n~7nRFLru?L?6#pSfvm=QO4M&%g`Xl7Q$mX=B2uS@n z1MTjwK3>u=$y#pW?4GE_>4?Dv08`6C@QW4*f)QJ_4vk|eAqyBKk{P4oFxa^UPW0CD9`8B9(B4t)n^Uvm!oZVN9lj7=^<4!eD;;ad$PCbF#z%i!H91U$82$ zB}yN0>sGQuT-6J`H5}5S@a4D+#Ymd{O^pRdI!okdybjo6^I!9OsKy>aqI=Vs4?QzN zbxQW)1dT6TC~&>cm8@;HIggt?WzJg7q^PDof6hvYABh`s>rQzE zi6xv}t)>yKlSLcDdn79$53Bi(Ci1M3;s}IzNgPp{2Yl-jWMmY$irRjxs7E%@vHU;-O<&)Y^H?}Np z;z|@Zp%SH|^j38~Xz-1y7++RBN@j7YQavAklxGU&4wYu~C&e+zeNv{&dV>a1}>~-d1=%7zptbjV( ztPxsZ8@Z=LGv*bK%0)%j&+PWMJ*P#v(9ytGW$-EF*R+55$l4BKw$oFSt0zX}U&HE} zGe(Ca*moZjr6Ka<_%1{Hwf#L-x42feh7{0U8B-T47L>1@JDHj)K@FD&1e9(dbbN+{ zmT(-?i}ibUOSs^A7K!a<;R*fiPVT{Xqu(P?$SO}MNf#BuhE+2vLd}QouvDzb_EfAs zkX;NpA|XK}5Wo!5x^g3523r?6mnlsx0M6Rpg05tqV=ry>cs3lWyzaAAWzeueu3=%A zVI2!dc1tJdQ0>_BfPh^pzSyby7zhPv;Va77mAMlH#=7^A)N>u5BmILwNLe@cI8VRp zYP3Uz*IU?9BMZX`aI@1C))2nH(w78y^?o^of3m{U>dTMk%L<1;+|C~YGoG2)p_oai z#lc*I1DF>^!Jk!3NPv35Q&m}h{^L% zp$2UlP;x;?yOirx`Hn=k?%=|7WNO@ye26Tw!-*h)5(r!K{JJEsosFt0jg0;HB%4&c z^9n8QONGrfX?m5GCyn4(UfT|uH=$S?0wP1mV6`vrug+la!LNeZ4{zS&0d%|#}nvqG8t7LrnO_N;f*dtNw zEUze*BsK6VU?!T#HkFQ@B`t9(84I|8E5^mK1j2?cN>LWzQM5zU8m)QKL%$ITW+x>) z>vw63LsdS zzC)?XLWCkHO!Q-L08R;Dacl5qYOwB9;goT3D_NDv_}{L0*Wq(n=YK#Y0gZS`v=o=8?PB>)EtG%OE|4HojgrrW@3q_ z6f(pW4A&kH?0jue+g2|S(+;qR>qAnMvfpX|fXOKrgq&P<89iSB`ovj}OY=dvycQxN zgn$2kAUbeyPizs_oty-X5V=BJmDk~((6b*AWKsT@`puD^0-GPwW4Nrp*D?|f&Lic@ z8yicdQx-tXAfr*O5EsOc%GC3}nYDTcp(Jabq)Z*GG%XvHN!s_ zQ<@pjeff7JNDCefZAmJ9vwg2Dg1RU!B~?7-48UMkl2Q+SBI>@R7M(jA&UiDG;WTX_ zm3ZLYeKA*Ys+7M4$VBWd7Ip{e8yo+;su!n&9~k&7Ssl5Y=TH~qV{9A{6x7Zm=4`p< z;K8K{RzEdgh{_L2nIWwBGLY^EK2h2P0)JKAx9@h@*q}-orhl?5!{6c{tpJPzgeA zwm>CaV%MS!I%z zE*U4LmY~&Ts6{)++5Npf#BvAn?*|^o=-P1qXFB^|<=#K})c3mLujZettiP=Be{YR% aQIdBT_xg+<^@D$6fy*XWjZ4nE-Tgb!7SoCV diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_dark.png index a53003596e22ed78103b5ad89ed615cae414b71f..e3bc386511f56402c3a3dec279ec5256267a2be2 100644 GIT binary patch delta 1648 zcmZvcZB)`%9LE74qAitX(~~B+cCxdx)NN{}q}U=`Cm(v!NDZx(GzB$lg%tdCcC^ME zb^d2LHPjxa1_~JgKBL2-9y4_YmIRWPAc**oAU@#!zSuc?vHRxtJLjJ7z4v_X=icW# zw4S#kPCMN>gx`09de5lp&+r`HDtpV?mhdPl@vgD+7GjI`p`o3N9N{h`!bzVj^VtVdID-Vaq#var)44xCI$tY;n@gq z0Db`It}!{ZpWa>ahTmhpLZK9;zxsvqL#f9mN4h@~n<`9*Q~Gf!$|l0Dv~V2%uj^OF zhrLe7^)f!9;&w;Mk)aJ(;rs=0fwao>e&$x1QWV3WCzGl%nNwXYxe+ACAo|RcE9fv@ z)n@12((Ig55(f38U|#To^fB`Cxh2gRBM@$TUQzRE4_D4nug+cSOI1bUBP#8nbr%+U zNOXTYTlH1RN|IJv&F-7`+eH{sP>(gUD$K2$7EiM(7(|Om#_n@P5u_sm%*{V>%Y|GDWwe(-=T59Z{F_aMhr^edmR}B_CStZ&Hee zB_)2|sHWf%{k4rKPgUZY^-FJ*$I`P9F=41SZSRK{<;0FxOrb`*gyB*>e^nb-YpB%Tqj zx45gjVYB)OzTHH#n6*tUEjqHgMsR3mBNiL>n8Q(Z#j@kOWvA-m7tF=u zrTtBGZDpBm;C|igqjHA1wp3lZ(Fhbo9u_cU`=`0wfMNW!otF!vOY!mB)Z#?HuvsOT|Aezx+J8+*aW}g1-+ch zSzGyK@~2$#@6ahn8Osze493#BMdKVA9?r|p&mU2%iP!Ez(|n1oDV(vQBW}q0d_+{o z!r&Caqt^xIUGUdm4NX3qm@ixNNYxBMo9S(<%O}(OH{W&&K(id|8;;IfTYSKtkxTaH zq=C`nvXHEYcYDtlMH+xjM<8Oze-(t`?d@}KW{$T?on2fWcF`@WtH9zY+RN*}Fiatq zNJb3{M({HP9yJ#A0Xr2+Dyv=!2e;~2USK^O?|JVQqtFYDj$}2zIHf;$@Zexj?t;-| zve4*Q@FEo26Sm-_h1DN%#T>;P3#-#Y6K7aO4W59!r--}&;{Ko9xJ zWO`g&T&B8Z^4vrvC!QVk;JEbd6SLW@*Jvu$5+IrR*8?}^0U*sz+?f%&S%ZTP*F|M(#*1OlICLNe&-*szr+)xjT4>Jz literal 4812 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 literal 5522 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 literal 9814 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 Y-Z=WRoZ&HW%MQ74-qxb*+>Lwx1Q7xNx&QzG literal 7296 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 Lt}0U5=JkI7w`rjM literal 9908 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 E8<7Fa%>V!Z diff --git a/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart index 27dce547e1..27a49d0c93 100644 --- a/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/src/theme/primitives/stream_icons.g.dart'; void main() { final message = Message( @@ -17,22 +18,22 @@ void main() { final messageActions = >[ StreamContextMenuAction( label: const Text('Reply'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.reply), + leading: const Icon(StreamIconData.iconArrowShareLeft), value: QuotedReply(message: message), ), StreamContextMenuAction( label: const Text('Thread Reply'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.threadReply), + leading: const Icon(StreamIconData.iconBubbleAnnotation2ChatMessage), value: ThreadReply(message: message), ), StreamContextMenuAction( label: const Text('Copy Message'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.copy), + leading: const Icon(StreamIconData.iconSquareBehindSquare2Copy), value: CopyMessage(message: message), ), StreamContextMenuAction.destructive( label: const Text('Delete Message'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.delete), + leading: const Icon(StreamIconData.iconTrashBin), value: DeleteMessage(message: message), ), ]; diff --git a/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart index ca8c5467ea..28d36b8295 100644 --- a/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart @@ -4,6 +4,7 @@ import 'package:alchemist/alchemist.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/src/theme/primitives/stream_icons.g.dart'; void main() { final message = Message( @@ -21,17 +22,17 @@ void main() { final messageActions = >[ StreamContextMenuAction( label: const Text('Send Anyway'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.send), + leading: const Icon(StreamIconData.iconPaperPlane), value: ResendMessage(message: message), ), StreamContextMenuAction( label: const Text('Edit Message'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.edit), + leading: const Icon(StreamIconData.iconEditBig), value: EditMessage(message: message), ), StreamContextMenuAction.destructive( label: const Text('Delete Message'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.delete), + leading: const Icon(StreamIconData.iconTrashBin), value: HardDeleteMessage(message: message), ), ]; @@ -51,7 +52,7 @@ void main() { await tester.pumpAndSettle(const Duration(seconds: 1)); // Check for icon, title and content - expect(find.byType(StreamSvgIcon), findsWidgets); + expect(find.byType(Icon), findsWidgets); expect(find.byType(Text), findsWidgets); // Check for actions diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_dark.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_dark.png index fcab3abfa60c8820f665f465e0c2b3eb29593df0..7d50ee1a0b2d44a684bc19b57a606dda693e3f0c 100644 GIT binary patch literal 5708 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 n!(c!8o6^&-^ksi~TMMS&1YA1PgJM3nG literal 6852 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 literal 6932 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 B8gl>u diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_error_light.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_error_light.png index b3c801869c3257ac3f565f4005322b1afe5e9042..629bb9ce23c13a5bf759e27ea7c843c68e6647cd 100644 GIT binary patch literal 5805 zcmcgwdpy)x|34_&3SALmx7s8k(YQ6NHk6X3rWtaX

W6J{`EpS30R>_*BZyIIs2 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 P48S4VqX&xiV=w*#`FyZI literal 7018 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 St1#CAIBj66pLf#f+TQ`Q{Aket diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_light.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_light.png index 4b3accffd16d704fefc54d8356c0a75f72ee9969..24c03f908d0d6931b3e7c93b49566f186547e244 100644 GIT binary patch literal 5680 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 m|JBclzvsgLcfk>^_HbE|W0M zhe?jZ4%2oFF(EUUGBKDYhG`h{tr`0}T-R>D-*>(5U+*{ntZUY9&9k20TF-Oe_w!8b zIa{kOQoE!809&m8YHkOBC<*|P#f=hhX5!rWw{X}Hc+%Q_Bm9JKymB9Y7YVepItfa9 zl%@gLm1Avw!agK(Zmc)nnV$BEMb8`=GYWP+@olT}0o2@vm&Qcl+ z&G_0mE6%HBJKP>z;Pa@ow`oVL4%03nsjE}^L__9ZKvWVAy%z4gI!a6kB(8d{PSLP5 zB9~Waieb>O9u1AkMMRFpDi*UwN4dC}RXTe8SPr5*$i^7cg9iexe5VE658kpI!thC$ z2LxB<>#JF*&7xpx--Ro|k!uT_3=B8_UaZz#anOZKd~L&9J82<&?mN8al)DJnbSZ6p z^l)4sJ$J5;`Ew5i``mjw_4<1hX#VxOk8rD}1f9wa@}T_lh`gidCg6SVsL-4f?tpN) z!?m9T%bo z?G^6cpMF8Od*R0i#YApBF+Ra7CyTQaTGVQEen?na$RW8kawc1ai(`M;TRGG61R?mv zc&kC_`s6Jop+#AG!Ct|8tM$8H$>NVkbIMkhtAsw;7E``1u*lj?3SN<4J2}MV`8MY5 z+t0bH145V7M%t@-+uewq%b6=!{(b)PM9lh$sZj(EFe@v>UU^3g7c%?dnN8r$9@RB2 znbqS{*2a%yHgei{E9Wt5Zl=q%oE+_j-Q@seFZN;9yjI8E`0R56&%l!5jVmcuhqr^{ z@mVVw#MleOg)B}Dh7-g2b-3z0#uYjp9{0*zaC{+gj>4Bva!(b!9FRS&C^+ROcdrRu z)qSfgIQ(Y0bm8j9{m+D}ju@y5rypbsuWo#+>4D0I*_~i_w3^%21JHxR!|{Sk`lT<1 zxJH26yzbtI22owm)bXp({O%)yhiQTbr=c@f_6dF>3%5^$2mQY+yO>t}L_Lm{b|rD9 zE_-<+X#PGx@}IN#f81EOtmL%QIWn7dS`?K3@V@2m`7evT=6zXrSDSa{o;` z?HbCVTS1h?xfIXh;0qoejmCjm^6X@_*yqWUmoQtSTlU8MA_`6wsmGz1rETr)i%<2C z?s>_V87;Rr$KV{NHURG|TdyB&{g$4`6}R6_u~R1ULp0kxuwo)nNqKm@!NTm-WY0eD z2%KCI!|25e(`^?(bF8ik)`QWamQZ7*Csmx)f>TI5CcgpLmQMJ~MYs6k2(n^DrHoI@ zl7>^Fz?%@m%d$jQ?2{P6ja~YIbO-=*8;#rO7+La}&BgoNM1jBEmG6olHeLV#t2g|} zRHwZE`zEdgaCbN}QgFh37$XkK^KGw9X2gyzAGHKghWNdg`vY*_2$L6wnIiJt7yTLy zncJqGYjuS1xQYuZV!)}aK&{~%eZFxehAZwD$rz2{uKqxMx#l#)tPf&24>5g`@c2o6 z^n6d)W`GLWp`B^+nLZB#uLQj?G{^b0mQR&4!pu86H-33v(X$s~)lk?d|t;^4eKdei&h38w}%>#xEbW zO@ey|f7sEZ%ENfW&^gXHhGE}&-wLlGIn7v$b zW4d9Ig@70ALJBz+8Qy62WPP$>za&6Ot%%JSL|-@iy9tyT4dLuwrIt*CsVzK5lvG*M ztpgpEAv5d%nB>PbQ#^NST2L!nmR2gk&!DafT-9``3(}c?|MlkT z_?T+@aFv&|^U6fb^4>j@RXMdmT2%sAPH}BovI}1hqhd2^eD$5EjI6jvWsN}}0`I}B z9+~;|YaokI7Wt#;OI}gPJErQ(r8KY5fRDAkRyCt0sl$Tzb5c}fu-bMs9b)RN{oFi4 z=PJ78EN=`vFP~^37NrzdSIUT%f8U7abDoO;@4N9kdQ>=pHCo>WimKSVdcFDxF`$*c z@1|~m1)__70CIeMftFfLUu(0|oI$)Ca6jwic}ww(bn&2V=^X%>`yXewaT`PLca$z7Cr;bEZEZKMj07X=w<2834W4-P*$FFuq#Hy62x zmKccK44xp54%PQD^hVL_XKA9EdjwFiv}8Lg9a9yq1l%u`+&1cCl#pzqkCd5qcim+_ zgSkgb7C?#7BKbgOvfZ<~M593j=?gCc_@gZ35XlzF7JVJ$+3gTXN;?i4?UZ)x6(IVm z^~(3Mz`fz;?T!gznt4W%e3rull8U4#P&Dz;B{S> zSuswSzYHm=!AvtQ{|%(s zsG&P+Gap~xVceFp=;QdlrEoPLVI$3iR;K6BzUGdpSTP@r{~VD*TvHb&`b=FSD`b@u ziaEYMc?T&C%H@_M-v&qwMt%dO|C`JWAu*$*JRMa+A~Ipdm&>_Ed~{0oWW8n3et@nx z?C(4bIngezQj7Mr=n;RGe%6(#Y~X|(tmqQB>QEOneffRu!YHSu-t0k@SFh5`rB^ac zEAv(_v%u-!p{g-_E{`+6+@z}SH|AU2L^j^8?G`runl1$avU>2|0)5iQjb{_?zjA?9 zPBrtNJJP#2*%6q7^{ok82QtFZqeHZK?eI$!k5%!zgkH9Olc|til;S0TprFpmfiScu&UVKJL9(AOhqOj<&wI*t>+00C^42 zoZZv$;b^)Nswm5O0f1`C$jD5qUV>;5e)g3pD8N_tb>~}#mD}Ddrl&(CMzGBq@vbiI zD=G9vcjHciar28VbLKTIw3f$HO2q>ijRp9bLxCco+76~sXcajm0u~TsK>;=zFH~-j z{0-8BP2o{a2RTeGUiHIQoyeCeHW?k|L4EO@rvL%!Mll*k_Ibgau|6X1VBYGj8#gzX z^-fvZ>#G6O>P~Y(x#Wx36bUcai1aiuhBeH+b`1*~jhUcNWfu(~iv%kN<8g@}S60Ay zd7OH#dc1mkZc5p(W`|jqONwXEVWrA!fhOf7E&aF;H2?f(VrzIpCX_>aio;a|mcvwa zN)0S`z~7pBWs#%1p#Lm6mw4p9RrHube$b~A0LY&}rdABOBqXrsyDALWCvX~hA@5XM z-9M{mBBX#;QLg&s#(H^0#etY1d#HKhN+d^hloGF|$-;8NC@Te`b;kb6U6Kwq!K-Vu z5(TV@G*maKP$P6zWgt>5aO?!T;!?)D5w5wjK;J{!FOkgArKk+5m*1AaUN-oG+6oM6 z|Mm*8S)x8+$aZsaT`7*L`r$FWu0fS0^sceCbOjXlcFaSiR{(6)$a^=YZ0oGAQ=T6F z9cZ?O8r^cqRRkOxy5h!dZMHeZnNb8$j(XQ{5_diM+hKqh<;D8#^QDd|h=8er#QWBy z1gSsj;^B@4NtCW%BYUdB>TyP;I*rZn>w*HuyD>NW^U19G<$2Ya9SvVg{8VQalEP_R zA4ejyzb(*_Sat@FkE6b?HD>An_X(E<|E1=fdZ-wxDcu)qXtC(37OEQ83^wX-U2swCwDf^_St*N>FLNoc!J7APxb z5v$BWTLFsAE_Xa4F1|InkmPKDS>!)GBUDaMcmzQ8r5=~d8Y^gIUpT1Bixd`i{iDqH z_batjC5P6=M=*^T9zv*D27{GpEDnpk7PUV41rKRbV^*eswi(a+yM;xdv;ccLDvy2U z8bCyI*J$8&5Y#&-3&|vFfh+c@p3el#>gm|Na6y|t+?&%tG4`*<<2}-Zt_Dr_x4jIc z*RZ0|7zMB1Y(xLK7*z`P{{7y_ss7)gD)j5fDb^S>!pxUaH|8BthTaeLQUAlT|hxCevE3q1(bWISNFAUx6sL{IR~{- zZe`|sO9)W}nwA}iGRaE$?WV3_xr1@-6jWG^1j1mf&n|HXe|a?ct; z_3G^a$9=Ml2z}lzxQpk<=F_e2!6M;B-AJr&G$~rA+YptnABI28e!m{ri31a-*Zh6~;)1}ei3ULHXS|k2N&PNG##ow1z)ZB|c^g)_+5MJr zgaJ+tpd2g^t)b`xM~^<2DJc}y{4ZU2SPfPKzw1Ke6+P+Vn`(4KK^H=KTT-Ip_CMC>RVjC5 z%DpL}jP5(wL;9YGb_SXH7z&&>p%x|+_l(fYF|)VYAqy9&rf=zLU)$N_%1CNnJiRi~ zEyEtUomd-)rg%W2sSlZ$y=#Vsiov@PRum+wWi#IMH(vwklQSvw#9$QxzEA|nkZ+P) zgtviLdAgFHIC#Q1yPX!W%Hic4YcfqVb`qpp7TRxB-Exa-T?4&w8N=mJi?R|SioIi| zXk(@e!%Fwn`mcTB|CAv>HaZj@G^oB1au&3DL>QyY*_6ve39RJRP;0k&I+{us^I8b} zP(BwY)GT<0Q9F%AWAH@QCqEz!L9^XE)pMks_A7s;v4u^JDJ22+BzxW&7gk}-b|LH( zA4F=~`7&&hz?DL+P8eV|8Xc7n=w+^JRXJvN8qwq4NF?d7(ADYwy!Zfmj~`8vxpt^0 z+az#&M8>Q^b=$TaURU1lP__7l@MyB0x)KQs&H6sBE7QIK>Z83mnu3DMVMk9+d|3or zlhrck*7hor-%RK|HW07b8(~^EHop#JLnH4{Sna0I_wHa>J2niNlBpc7kY4h~0CBXdu~+5`wu z*GZ1g00=YEOd9pRfM51JG1`}k*B(Rbm>4D|2&5?`8zY>%KHZYA~jYea`K3ecV8|2{7 za^+VO-a}tu#g;eV7z>qCVUI-C{_>?8e#R2`k6TF%DltAc2&AqO^|!}avFIB15b&*6Ocs)gWnb=8l^Is zVo|U5=iblN%GHW(Z9ML za*YrJpHWFiuWv;yxk33*cs|ZLR&R9EZ2)WwjP~qP*5ChU=$(RF6Mu9t+jz!JV+u=i*<9pX?j> z!3t(fwdMbpZiEO>DiQ>GXTkq$JH*PGi+qz}|B2r3pA`@P*lqsHTASsIRn|29#Nl0_ R!vC#+wS}#D=}GsW{{z|7Ac6n@ diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_dialog_dark.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_dialog_dark.png index a385614ea1120934dfbedeecd2f997e190176c47..52ca2ca4c9b27a45d1f0effee48dd5d4cd3cd2a9 100644 GIT binary patch literal 18426 zcmeIa3pkW}`!D_smCh0&a)^o&lAqaJa?BxzF^pEPavGy0shlcFa%vKi z<0_1DCg)=&#%XAr$6;pQNDy|2Bm{r>j#{(k@e{$KxfU6$#2zR&YL-S_AI+@Je9 zLe3fJZCWp~9zl>zr%vizK#-Lj$NDXEdH9&u47H@fU>)DiND%kC8qzI22*pO3%vTtRU6?@}kX z>{2K|W;=ME|JuH1w>7!#<1&48CbD##`TbLEyK3$Dr|y|yMK5j`6QX-A$4f=rXP}Pi z+N-&po#%DEio|XkkEJHoiW6yC(^=0hzZ$Eopzo_*`jEo%Xpu%AlH5=;c{MJ6L0jW( z{+22tZoi6Z?)1E>YKwJYcd%vq5q`Vb09m!5#t3J6G_n>RC_U-;K!!Tz`JV3_vI_of z^=(F(tGH+}!d{5tq_lV4gz5cXK{Fv3+=(TsxB=~NmcNadDdct`V zGV#ZiRRRbGZuQwc7x>vK^wQ7!t&zum-p6kA`}xs-qsPyWY8xH@`nbvF$49FTcjUR& z2ckW`KJoGAYlr@Pt)q?Ef?mZP+W2GafzIz5q<;*i(^FdSM}vhvU1A`YB-(cR6H)lb zz=o1Uf4;ZgPW8gi-gvrXf4)u3UHWGqUgui=y87VS&$h^$&>!#Jcv4Hcue0fH(yy-y zCu~+}qkRcK2{=)@s4_^c&g|?}{V|0DR?ESG$p)kEe=f>);Y&YWCn>1rWkCNq@KrEkWe%IE~)kw|S_H)gDd3!qA$FG&n zu%bGd4PE@vLEoCjeZTgPWlH;p{phF5NL>%68n4?sWO<)p_x2Wf3m)nJq&<$9Z5EF68F|-OJGZ zu^o4+jebu4_z>=AzsE;#QdVw#X~>;RHqJX$j0cl)@-k@?)rSjJzpDisPu9){F;cOCR>F$^&YnXm7|s6I)$Hq3F@f z4g~4BhM#-dc5}3dyqHTm;WoA~{*QH$ujWDn^9wYW%UxcKA}-qTExP*?=5c06n463j zFi2P5WI3aa{nW6mIEkcNe6AL^S^Gsrt72JJ!u;@BlZVV8!)03fT}+qk`))5Y{BkFw zWdnP6ORYG`B`n%O=g8;i8R9XMX^xCF5r7mx*d4C-Bd5z46gh!voY|D z^O-RrQVWb6P9U-}Yl-rQjlF_YYy4dW@@Y-)+aUJ;Ff!9{Mz zY!SXJXUEzp_5N}lf>=>*+LVjky2ZB#;gWV-{@{4o5H4!REn7qt>p3o4!@LpU$d-lj zA+Jv8rSc!h+VJOQnSHCaAXfkIETC5otp9Ntb%J&L988n^Z3RMhLC0`4@E>vF8&spDp=j66!iz5fp-Sf>__+h+#{`p*-DD^MeNBYAVB=G*P zucFQ?ko3qV;buL(W&kA_ox?h5&7rzB(jz%J00g`AkyS(47gBfl&5n~M+k9FsBFLRG z&#`V*eqs&r-MhEKHMY{DzaXdh5X{2!OZzB2{>ta~#h|cq;QnVDs*+#TpY|#$Hp3r$ zcW!xNh!@}Vg!lA-dv$ef2mwL-&+Q|XSC5(9k2l3k2O(=;pDnlEYir9pAEAW=YJMJ= za%&p9B`c_Z*$^X)>&f79IWYB$}is{rt}9?^D(*;n`AB_~<+>tD-ToWSJ%HkYv6 zF9EI&50m}sSb#Dc4`a4G(PQE@_ceb)L1$Oig?P_xlBFs>t5**-OlKCMt}eX&VpBnO z0&?K(D(ygVqT!jVSPWuy^~{+|Z+>3fw!R#-knr%|3ozW%;G9GEVqp#&BRmuXd%#Dr zjLCJ#0ehEKT=k+^2?%!hDi$E*o2Odsp9pT}zhqSCrXUK7V-qY3bW;~bK4sze0-n8F z+1kecOxOQ+n`Y@X!+-(UIj~L1U;e{5g8M@Eg6%*K9E|hBOdG&rpD$>x#zC;EyfMDI zULD=i(b1We4Z&qRm{}G0bT`5^ZO?4{gY*n`MLVyP7#$r3@G5+T3pV?UBL~t?S~xo& zh7~=27FpGHCrqD~)Z~JO63NT)Ug`?wX4SYgrRM;LbBDV=6q2~Ok+DFRJW-n9AOw&D z(_kCU4mnrc0)EqcA8&yA!XFKE&;G6t__tRw(o>L)SQrF+ln;u?8+78;fjh zratHYDm}UvIVEe0MI?7`u%jhih!5}fo7K~taNUm}rVYcM{f*@(RIhLkDZ?x3!1=rl zuB0`m%0^S3ne5j!*s=B44ixLY&x`zXMG` zkbe;X{vU`g|85gIkp8~grG?)M;#C!x27+{cTd$2S!M`^s{yT^LBbL9mZsA7m{Q7g; zg^K^Npo2nx}7+<_4AFqjq1P>H>3c829)Eb80CUt|2 z+f}{qHh61Fx6ggt;N)bB<&nJ1C8?dbZGF(gxQ=bnBUc61Ti9ZdOud5cP@J=Ti&4zo zgSK2-2(WSq;{0ZL%OxUMXbBHnJLFKFYz&RQhkL?S@RM9p7w8zc!AoY3!WX8MeUJ8@U zWAMnG1c(Bg^34=x>$-M8FaW1PKJ69MC$nE%w#?#seHQiQORp}TS16d|MXXZpQ!2of zKL7Aq-2JKnYg}sr2j*RIt>VTQE~neb+WMrB5C?(v#ZhbE5ty;CMAlU-x0OAL+9HV$ zEB||3N&aJk#f=ExclXsmkphi?=X)pwpm`PfLfR7m0tw%qo%_CZJ#s*niSHq5Be-wf za@vOP`MyGyVs#GIAC?}CJiGrj%7(J8@K<%L@d^6Slom5uab||;g;PZ83921T?UFg< zhQApG&7uF_KoS2nZ$K%s@UJTopzV*}IyWHGiBp)sum8G(Vjc22S0Zz55rUHe<9bQm z1lZi0)( zs1>CvSbm0$*Pb*|kiUk-SiJ;m=scH}QBm=}L>GYnK5+*3Wz-RkAP#7IdGjMe886V7 zFm0OF9-h2`26@CvOc?Q0b7OTqvUhr~EQV;898yuGF6Ao&sJ}_z;~pJG_ex587V#2-ep44k9qK{ zy{#IzUi$*ro~EOU3cNi}TI!jZc$hU=Ajo>Bd;YY-dj)~edv!GFm^wWaRyHHyaS*v6 zWT?d(yzR_Lr#W5D>kRQq6U#7C7I`L78o`MkN#tQp@Hms~eH2x)knfxSb(?*jen~v+ z!paDa5%0tMzn?j5GPL}{SjqL4)-=Q^3WcHQkxTchGMBiK zw!HvSFE6E_8~z3MKzzVG|6xy#%;R(FJIQformHF5c@1x#d*T6mu{MUy| zqiP&*xX%M`EWp@G*WOqyiCW$NKGzKt&*`Ld=-x(}1yTeQ4fn+jh2R5!!9zpa{{ZBo zqoXSbGCG3;K79u@#-y24&@bh;G!I6i9$P5AYPvJs_G5}Q)Z#(Hn@^0*bD9cHerr`q>rI9el6lB*^PI6}U z-e;#nPEK2qpKCbL@G8e4oLtf=$@==72)&WlaC~M3ny{xdXK}?az?-0Wc&1 zk5-s)RX~u57P{xNW``3=WWJe>E5MchKvRdysh1+KescgR8XQQmR+}A<4XGH^H8Z;s zZkv5?Ml$d@ETR8}ebF`(#gAm8t6(<~;nM&O4~qt@0Px73UkkUat(Vaxt*Pfpc0X!s zODjVYSs?M&wq6RKYcr6AV~}^e0Mu5_=D|}W5N(NpN#?ujv)Te-$(#gY!U&VQGqH{` zCeOn`!PSW5QJ_P%u9{nX3HGbo1hbwYov;1s0vJGlc;Ml-B0B=;Om`lm#*NpM049gZ zPJbAu8oXZ*W?lF@z!I*tXC+li9qtB~KvSZBLZm%=_WgBneULSpdg|)_*&T?%LBcs* zS)&CgMM0^LSus~VKS2wFxZu6TkBa0;6l3?KMV(K*^ej?wdWLCAH%iZHwYljmAFxzG zqJDGCf{O$r$k`22vXdyt$$KzHewhTMlz4r7Fn^xNBN350aw$DMy>M!x+e`Hd-KdwO zs$>)rLOxwR78^p+nrE0~WK>+N+Yy$%!EP~NKZEy)DoQ}Kxn88apenN`n;KklfZkF* ze?AS|?_qpA@pSUmsAdO&(B&eo*WC`3D;a__IIN&xZ88523Bo*iNJoY>t;pIxGJ~@u ztt)Ne5c1>c5KdgR7^!W_Md~fBs!~$@34Y@d(s~hPxAOCExg7Oh@GM;DaAY*MnT!sxT<(AdT-wO0%4*XT3p9~={m}*Md6kk-aA{vT zVLq=!qAEs#FgDwuCmy0cq4C~^(ixhXn(8xsk3@04tGUHc%unrex%*NIfh|Gxuw23) zimgefVZwLhkwL{1K4z%F`eai4cI$xq-T+#5V*YRay#@Z?Z%c&N1q?Qw-xqCt^V(r? z|M`0$e~0hl%%rkw?yH~8n}gfI@-!KyiNTK^JxZ4`ZyFnO(qOeE-2;}*PFQB7N#rYz zovRjzQ)T59a3SS=5!txq>xF)D)wTN(9iK6vIUE`FLMmF*SiX z!gLZ40ct|Qc!+gF-g(T%Ka9;GkKH+`7%&?0vbB{u7;8s`eJCEBXm-e%Z&9wRNWGYn zxiEG7?&k83yjbC8hnqaDEJDe&K6}4GoW^!qT_Q;)U@D4{Q?ew%eN+MA$PU8$@~XSG z^6|}e`%N4s&<0`*%Ou#JVzRcgrKAkv)wU6qWGo|$EycQspT-L&CI|D|P1krqcB~C! zd8*_+Wd`=ClXE4+)U{~lRY|tb*JBz}W7*{Vd}eW7UVi>|Los#1u&^-o*R!;4uc337 z(wa>yEJCIp>iJabR{jFL(MO{Rn>k zhK`QLjoxMMqv8l^<)!s)1%8G)Iy;K#xoNDWFR2&FK3_jPNc47dV_>xux(x5~TJ{us z*Y(`m!HaM<&i>nEOz#JJXWqmsEiEkJ1&yZZ=k=6EaVclXbx!%Z>S5ND%K-wGM%o`X zg@~~)8kRFC+s-dwJbOctmX}<>n`Ifc5aI=C7;u$U>w}J^XJzRX>LCz}$TXT3A>e>2 z*LG3Su;5@^k$BLt-y|ukgl}>Ny9KzMlBTA(+wKJOnjWklE`UYXK;P(($K$P3#W6qH zMao8pLf0kkyZh=o2u-YnL_c*>cW6g6;**gkj2Y76%;e-ZcbJL1g2giRy()bI+wV*x zo05G7k}8;Z7fA{?_zT#aNG!1?IM`>ZMm1|;DoKx2F+KeS)(3~%7a@*)Pxo)7eu<`w z#>W1=qDNRH#S=PZqONi8N9rgQ75!vA38LBfLRMxKZHm!mLn`v{h=}(Z`7%H!u#-w8 zDjCQYRF=O}F~|P8cs|3(#=}F|!gr=Qja3WofS>s~jX4tF1&5db&KWxB<#iUUy>3MpZ?5Wn9U5ocfWr{sF zjq#(8?0WP2$L++_JY~fLhW>nMl*Vl$j>z_xQYP53 z1R*)OG{({f#WL=>UP5zkno$~~Oh$_jpzpzh2iDhzt#x$9x(j4x$J#}k9j-koC|T^9 zI-k~T?clJF)wh9t^9g0m&uzS(*U2c7z-x`Of(od2wU*BBc&NVRE^n__O>fqveE|0T zeShBO?q<5U7P{@Tz%$Ba1;ZF!a|vMvDN*f$f#<)ebJnYx*JA0n{qpEyt0v<1)yAtQ z&OCR#a>QGqm{H@Zt5cSr?7`^Qr(`FrI^fQ6ww=>4gehYs6-~h!Tjk<~Z&7ajWg+P@ z%W*N5ntXqw2=jJ+z6Sfl{TnP7KBn7Y!uR)L)<)hltk-Q)`)pFV*C5mYRm*Y2cSR4l#{lysAl zB?KKoA?2I}Xb2Au2Bl?y?7lRkS|V=OquJDI!}XfuUfNuPy$Pf`f5w#Cjd5*_X-D4j ztJe-185>hG@_Izt?hFs7LZ~diSSKE)Y!{>zC<<~qf<#6(*$q-j#Y?@?QvJ2kdeqrY z7ZQvFbnRy*LldT@UMXTQk`@O?#sbNb6qQxf*h=GhqQ#k&PCTBz?fhoM8?$EU*SyXe zSh0UnU+G55Xr)d1;}1Jl&Mg>;{!;UwCYo(pR3TQVHt4c)G$Z2QJbQm!Jn8h! zgI*Ku63oI9GJ-grNNEbu7ZU2W-}IyovH;Dr)|Q^5*E5V#or8sl5$VFM?oV4Bta2J*`qwUP+RFN!1Wk&h|L(<+8e7 z2+d@+g(W|HgzZyjK{Y})rjBwpn+NphYuMFRL%w@!{gXRR7PLPpr@EGFUg=~*~r4vHyhCex^J7n6TPN_ zS=*J;%h`$ApK0+SO{Ep7M@i{CIPOZWd`VVdMeSKXzGh=RDx2%MK%#%(2$~SmCbtD9 z%rCA(Zcq!RDjVJ{f2S*5jt>WN7=(YCRoCl~XYF4kp62MtpsEJ!kg@bj&Z=W)PcreI zYVl&&HQJ%D?FmO1s}Pqn9;$Wat{U?<&itBUeeK#nJYgcYpur(;A)pXw>Y4Cgt!;bH z;Fta+Q5>8PC*TH6;>?e9&e=eDs|bV&kmCz>O<`V=PaLnJTPEq%17xE=BSB%L4Q1c^DU0eYNV(+dNbNct#z88AD|Xv5ez1 zd=I`zi^)2(G}4+-eU%@miMDNz+6?OCfnr{xPo>poyOJ;LWqmAJUN$NAWZLABG$&7X z`O+H1)O(E1oIP8fM8a#hs88SW8EI=euke7XwYBZcf5} z*WPjDdyneE%jOZch3VlV0ZYl8AOD5v(ur!nq57xq*hvyZuL7~TuA7DeVa3GtR*{7x zkUej_M_`y5>HwOn_G*a2KFl2^WErKE=Oy^60JFT=oG9^k7*~Hc%U(>jT(EQN|Muw! zfrZy##3qJkEf27TE8Q?09i;SJ{WU?JDb~DUF}`MAh|g{0OX@sT zi|JmtFw*C8#;eyqMzyY{=iqA_m=Zb`Vty~r_4=F?rc(G26|`0@=x{g3Pti8VDf zbN$n zfEs>&a<<5CcJntM1d?b_?4)B#mIPgTi~Wt4nZrzFtkzt6_696a&{@m&ggQU<^#^1v z+)(1Blhz&@9DE_wvpG>Q4%<29k!U`&Og3di(Y2T*$m^+<2z(C2Lw|p4KbEVnu{;c` z#b!?3&gZLaYctW&`7$~qE3h7T6M7^BeWi9mcLamM2$o}vIn0i?H%(2s0=tAaQmIt# z0>;{(?Q~KvK}cSRmctNQSh2HnOI@A5V@6*xSCu-Nm#@`kub7}zOaaWqjWHt8T6CQ- z!RLz7*>X9F!?iSzzlmoX#0j0USl41-Fbgd1(2JB5Sf8e*i*D0H4bBq<;whOxPMDVFg$Ez4rY8+C_|60GRafZD|gwtaPN^|aeSPP_*5^dS-A!-o%p zCBz7BK;O zd25Z|esHjfg}Hd866PJJ&L}KJnAKuy1h9LT3QR6v zCekc@c8b4av(i`=Iai1>noP*|M#*pA3h3(#NlRHz^kB($bv-YQTEfh(z`dD49XFSBYgb%Or;L%NPLc&`|p5Gx&?eTr=MGHd93I%1Z&xx!KjK>@$A~3qh>MPj zGWr=*^yj=njE$F<%D(8+XAB@H$xhF_0~$UuGqVzuR78ljIy%5$Wl7Q0tP$bHeHv)#>=eo@B^HUy=BJby}%eUe`DTC?ga`slsC5NmQBs=Zw{KM;7 zhL9{E*pFe(A5 zZvYx{^N70Np2tMpMo{%Gq_x|ZjNzR+^ z6(=0?yCj0F4Zlq>E7$&xASJ&gw}*o`P?eMX@{-1|kW~@@zUa#fk4QI^Q04jet`+5Z z?~l_gp&S%NBCXNb3`6~KpzW{Q15r@ywoP`o6bLIq3tUk%>Ox4Ms_Au=B#SJ)46mK9 zqJy5(kmsnn59~;3&D~rba*FCOl!e5sCGTX4MRYU<9Z^^3ud2#<&VzX#3hh9vQbO+> zBR`ym{g1dbHcY&iK_#1+hPJoXt-XcF_fOfw63GhM_*}gN6(IXt$H+2w^mLPkI@Ukg zjV|zK+6EOa<}HTDXI={y8j6di1a;pZijdtg1Q0`9=!kAA9ffyS#%-4&O8tq z?NgU671?B_e1971otX_8)d#uLzm#w>S9@#7lZ! zQu?hA4+;tP$H)Y1*CUmDX{54qom50?Y(0+GVIpny=DY zycDP9&9KSi)%>pAg`djYq~7Z=JvT?W&5M`JJ=-O`IIG29j#tuL79wm*WG>m+`1&3l z_C?GoHv1uJsMXu>YKhS5YM{0gthG=^H9ouKmu>2jzgv?rPXVAVTk67V_^`Q88FCGs z=FH-5yI-eRgYih%nLYd@{LnpNad87&<2GV=co{02K031}ccV+lTu-s;@F~Qcquvfl z!s5+#y;lbh2Oit4q%zyWyYTYTE4N8tt3Kb)fqE?qE7(3OGi7k*OqscN&vv4&G&~_Q z=}RE4e-e*xh0{kvk`kE@gD zl;*Gp%qJFZA90_yk2~tIM|`m_iSbR9l`2nLT`Cp`P#|_~^m|KiaGM`R%otzy^`l)2&Ok-6Ue>abvT`=Yk#@T*j(3MG!wBk1=p4H_3FQz1+lce? zlU69tuG%iL!)%JtI+c^g7^N2#6*>ETU#p$B@=~3rk)+_@kz{oYq4U9LZaTAVl-bez z(rkU8@%u2oKp-ic0q*f&=)HUNsES}V_g>zSZH>2U<&hNdy+dY=;#zq zewKp;YRxoPI#6FSLfyP^nv!W=&{8x(e^~f-`9tFc(^?)5rL9-m%%VLF4nE>$J^3bU zZhE@xIIpJgqw3C*0)^O`isQW4>57j>RXV*Yo z_LER*_>qvT6j6gVtUt+JIpAVIxIn4|$_N+=f2TX*@ln#cHC=9@)O#Qk@@ zYIXq0uwrbiJvdlcL?ryrKc7NA?A*Qlk!BeT&a}L|%ur2S?95FNebUK^onp@$?8`_2 zv(&cb+HS^FB3r6Lmtws*>uiiCq+SYzycxjDxtnP#V{oS87B`O+)EB3;aGK!j)~3k& zdnG6huKz8-tBF08fHT~6!7pI%}_}p-_o;!|s9{3wGw0q}g zRg+TkWFExCP}2-G`1B%noWeCu00lp(!6<9B1|XX($Kyu}dJS+}wT)Ni|HZE4yZ8CD zRw*fPK|5=a|>!{vglJ zCCru2isnHUs@MPht2EheZoJL6sa9B2%z5+y6vmEtjrr`*@RXI3g7QV-(&sA1vc!_3 zDX6uv9HZDg*Tq{bp_f-IAc61u`tG&Q&dobCgn2fLhyPRX$=Z=`mYGa5x3GwCxTyqt za>g(*^osLQDJeMKCEypz_|nuH+8Qu(MZa!c=?G^V_8yo?OP#N#X^B#kY}|U;g^CNO z{gt$|B(jPZ96D7tf_dYw?VQEtX<4Z6Uc#8kb4DX@l-Y3!|5c6+1tQgnu5IeM^x)!R zcc+d^q4>-kEiRSEL?{A~&s`3VAu!!{U|ARRNxa&QoFsbh&FOjlXFHGtu8psacK$`P1=+q9rX$LDI$6E;G;9)W?F)m zOYB#h2=$)8wEQ;JCx{*>;J)^z9bMw#?T8r2?j}j_ATV!E>XCJ6+(QUbj`_{&U+Z$W zZUnZ8JaL@nbuz$ltz<7Ry_;Tup&*{bv$3cDvabPm)Oz4zTndIe{Sg?7t~ltl^_r-i z*3#t=$No7Pq_lZrFuuT=G7H3IJPaT!-zhVhlBGIAerG~t1_jLzgjiNIP!)5g=S$>c zYjm^QyXrlV=WwFn^&SNcOki5m&`c78VGDsw47Ry)P0jIF0Nr z|DN-aam!C@%TzS8ZElQp6Po`3+^-tNrQw=p)6eX;(SnpEp#ql2QlaZ*7 zQ~#&t?2ukkUcD$h-#^#Gh-fRAix?Um)xTObKkpMxB2oN$mdAa*mJ`Cz(x*1FKRyHn zNF5b4%|LoM3?C$8Pf5Y~raxOuwmBdLPv;cQkAt1a8-zyLY1!JWoIv z=BP${qC(_voAwvwO`L@g(aMp`)lS0VVe`f#z{BUd{Jq18MKiZG_eDFPQuJnV4FQmK z&;t~?%byBSLfIPct((*k3{QmMdn}W<>}7gyqGY7niySr<~v z<`VoB_Q=R6E-sXn>2wi#i$M8My+J4Vwid7?BX(d#>}umeD4O&38cZU=p~@F?gGUk& zLa$w|@UWEBY>yjdTd&9b_j~h=F(vNhAk8WrJ-Xd;VbZw2jXaBe=rqw27OSV1n@j&m zBW zysF4r>rDqXIy)0}F)M@FmSS)=i|J@=Qi?Ck+kZU<0@e==Lzj zpCnTowy~$DCs>~`=6+`lPb+?D22Xcb!uh$nx^{|daMjaSUV2{k8=QZ|4I7m9<_-YQ z)SSf?dmY{v!_^l(ZoTTs%u1_!pUjr{TxX7b18`&!Oe>oCc^Y$YH(MYy| ztOU@d%d1nOm~PW^vo&7nphnk+Xk)MGA$_zq-}K@vBkVR$2T(_;$y2-te94P``SRtE zY+9WzxTuhY&rYxLHoo?;iJn$B3O$c9(Tj7$Pqfo|-ciT%&O(;94CjyFsOqLFX`p&b z-VCiq@7!5qbK{0AxV=bNr}rxyh=R0 zmzHQH!Q=*VTd{xVwq8rf>fuNefS0LvaeD}Vlv`ivtx7m{u|!HB*=9d#6AX0u=E_On z9wv&-`&K6;5LUXI31|tW6ECR12(Yr977nQK8K6+*TGwq5?=3ho~z(exb zkJ{MP%H-FtBd60JCMJ@WV`iatFp@LhCB)VozKobx^&+cIRsx!$;>z<<<3zmFcoEH? zyj($d13*A&?^t`0x>wIz5gafusd4BkflAS z1oGq~cJB5Br6EJHoJS&+sQ@qL3FrJ>fFla~E#M3lZ^f{^p`;T#^Gyhx zHgf$qO7)OH0S-w+B+F400Pd%RT0F?>KfVWv$mrl^ zZM4__c<;9|qbG{?pbqmk#8K->?7Beh*3WbA)~-T^1T&WdT|ikkuX2Tx>F@9Uygky5 zw`qifz4rXA0otnY&G^j22WAscFRb6w+Vxr$UKYX!S97l0>wWW6l|jE=zp16Eq**Lb z+ZdWFJO~->VSatC2Y(&o&pTe&$mvyqI?%7NXcwz^XuGgJZViHnK2ISyZge4_3By)M zfz0KjK2^-q+3Iidts5^w=VYf$VX8M??cjE;1+MNI?_Zvb&baglCQUFcQNjC3d4&pFk`d%><;{tulN-Z(6Q(x?ru`0on&49w(Dr@uS3SywyWCuz@&|6;o9K?4 z-VvAQBqOFjgP&sE2g|>#$#CMfu z!522y$Ki~}U=sPzM6EUws8Tf;dQWclL~)81ZcW9rHq}3-cLhfDK?E#3TVBj3^azZ@ oa|h}Df4#~6r~1SHMK51u*AgC{c4=tCp^kd$gn>@xF{|JI8*z!o<^TWy literal 18739 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 RhDU&$J$+Fx`=sq({~x|8P?rDz diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_dialog_light.png index cf9d67caee11ec55804228c2ee54303446e630d9..180d152692e474095472af62ff7cb2a40b378e8e 100644 GIT binary patch literal 17954 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 literal 18266 zcmd^n2UJw+n&kmhyeOigpkzTsiDCdGKS5AX5JW(t7bIMwMD9L~iF6vNN*Gf=7_%~3;&^&sb-bzZ59_L>IU_pgA2Rj%!yj&ao0FTY zivD9>RI6|OcK%d$DO2{u*q(y{j%#Mgrs>(A>NF#Z{YMk2y%lQcLrOf83@H-w9@l*> zSKN#Z(rZgsznqib2uoc#WSd=MqTy8E`=sQe-kZ#x4v$}O=o6WCOLtqI@n}iWGi`mw z^oQc+?E4Nk&3}F4fY+nvL+^5vpr1AJ5gzDyi)m6(qT7p}8)yyNDczNslfLr=LJyy- z175PxZC64sQ39XxCm}o0!{|rq+YTV;h`uuE1md<{=NsxD@$>L^|L`A&#`pchejcX! zhyFag=O6O(FvUOk$6?_Mv!5+EOa}buff8ef&yU8ElJ1f+# z`ladEBs(%RBz4kBi)#GC^^X5T=7Bht2_;M;Xl^(VKu zIh`LpHkW4$w?=4pGwo+L&t*UPxdyy8KTYYgU{BDGhCQ@v&PJM?s>vvd~`<~drsP3&rj~M7jMR7{VmT=9P8i#jk z;yInJ5Ju#GxpkGoM_*@FA2BGykt1qK&FbB*gp0`YCf=bE#RvDZ^n1GYc`V!>a0R1$ zI7r2wA=SL-Bq}Pn^IM6DjL0+c>GQJF?MhtlsB#)9+jEPV<8XG3;(Gn2GL{$%9lsk({#&2@DT2Ueqo)6Y>U8+pP|Gs(zo+q3>B=wYog;tnbuQ zG8ckmzl(`6q>hly0_!(tH^VYVv`lWTui9)AaaeCuTq*I6pf?cSsLz=W-dsSi>mDD!Yrh1JOEqD{?K3q-F37cRpsbLUqb@q4GM79M6zJ3})a@ttg zphOQ2aHg6cnLCdlh3UE?HPN$uY_U>%>R$*rsYR9U-)oSlz(p709YJTXi)UphZ3w$M znO$LxAfJy7`rq;N9hv1^NT7R|p<%XrUJXVv&~G#}NDW!{Ccx@9Y!I-1*gogp4tQeN z_BXErWM`&Rt@)9SA#Azgr(@y)2$Fu7>qPA)DQ7lB=c8H$G8J3pb=Q5SWpGp1K9-{a z0|7W=_|;V15T_Ycci;@-atxuL>M7x^I5+nT#Gcb*zSTa0aH?Z8>^-S8J{CHIR12;3 zH8e()Tu5t?BQjUp%uF`Su^D1w5rc zwke@*a_QNYKm5WosE#r7GJW{l;kxGbus;|Ek&0 z2Cq|GSi!aAb0$CY@Xd}-m_11Ka~%=3WCM|lsXD?9$GW znopodJ~K|{y*=bC!IrFZvc7Bl($j|23N^Z;7vaXoa3lNJXiAICsQh*$NxXe3>{$$c z?}*FEx_Z_7N;omnVH{%e+2*YIa(~DWFQUYLmZOQ>oK=xRpTxOM3Ag85?9IkT(@?zz zYbqV$rjgMe9Mc`rVq@j4MK_M2=TlOCuy)Zh)ZnDCN zN97rka==9#KIMxfHq%(jID1j%*G6p4>VR#w<`@R&S;)7TgqT(=xxMUb*d!sfIVG>#MAa(0LR250|gc$&fa zIXc96ehj;uMspv&zh(bt!&8s$i39VrX_tmcPlQQ-rPHEu0#VALeb&E*lVsklw{^$v zJcY)SHVQ1nVI+a-}zGZwva1KmF9EDchV!i2GAw*yaV7P4+2yaaqavF zeI~zPKDhF5llA}cjPTDelE0S7{#7{squ$H9!pSCBMxc`=1`42OLdUTRmkzp2! z6Od{_m+gG;7x|0nn&w9*?ap#Qtb1g6voK$8z_m=QA^(G6mW^eW%|)LoUL@1~{t1;A zU@R~8)|zjc3~HY+?J*HMm}0y}W&2!E4dlFJD{=4YMCGAaB{4L_!Q5PO`B_TX@-=dUNB*-XtUN z8?g`hB4WmI5kB}w7&<6k2WBq`tQ2=4Y255^*hiEfS* z#P!k-`>9-(I8$g!=Cf_l&>#0pZ6y6VAlR3ji*0&)$GN#a197_q*`oT(7xgyIY$^j{ zAiCru5ETVpPDgWDNtj3IBLs1T&wE$V#XqC=yt;gTqY)N7*(k~~FJFg%*`YMJAEE0R zK6(I2@{GgK6>33rzy98OlMY!7C7A5kiYOur=g&gm9@+}rO0m8h(4V=&e37F{xaFNV zSg1_ztU!q1^#7R%eha@Ef+a_1_)rx$3lwK2NqQJ@vWjbvs-{khemKl@6ZN(Q(p=15EmU=LXlvRqiDjo5=jubL~8vEv(uiY>GQ^ zyv)DzuCJt!8I#B~6VEn;ENoS<@{YjE?ECXdFaSls@>svK-+%E(>}VMfLH;66M3D>p ztY;XA_)im%VOTMEmqUb{FJEx&9|y5J_^`C}r=MjYq8eoh{%|0qma^zjj-6U&+3d5I z6yQ(R6Q#p@&w-fms+Hcvn&IJ^`T=rZX-r@s1wZx5r*IFhVQUg> z&1Anv3^6KFSxB)d^*NIpDJcS^B8E+yc2BxHmRoH)z`T+A6*tU?T_k;r7kX=5YvEyG zby?*eb)P?9BP`Q>2dn_S&%RtF!{ymmDF}4J_}Cy}4?3h@T(|QnX>L#n8)xT!>3li3 zjIK%0kDUG=1Ng#pgT?(!$Y%!vjEnC@4kA&c^Kdy{qr*P36k=Zcv8er@17f~=ald!0Bg8LpN$ zuBC@_yi>}g#2yx|XJKQ*2dK>=)s#R>n1^5Mn!CA4f3n2Bp?7@5R5XbFi6cz)?DLlX`qeg#K8~=>9c#=zr8rYhQG<26pO~xkNF~O zwilp>t){X#n3AMekbK5u@I)wmqcd?J9m^uxkNFxUV;%iZ{ zi};p6W?~9IpAx&*vF+p6qKf_m(kRsS^AtOsn6mpbu-GI9U~yu`&qAzPt=9oc8$l?6 z_xV;ZDFvXINJKPC1^?}nB7$qzfw2ACZ^yun{jQ?16WslNk2!7OORgEY9RzR0Y$H&_nq@yhS*A=^jU6M?5ER-$_zhj z8c2EJ$1yZCYYI19_z9h>pLYfaUKkKSR-gVQ%1c8E9T}VRiJKY&tVvGtt4(UCStEY$ z58}}E{pXV6e@OKIU^+B0U}>_&N#QxF86X{WAOOJMzd++{uIG&(gS4fO`Z7WIgi>s9 z?FEz$bTE&ZA~VoXkM2pJE+{4W6C2;Xt4_Nm3|t(b#?}hlyT_m{weYC+c*^=9S!7mk4UUQ8W|odBwm3aqcDE)Vj^Pg2GX;rUsYG!IxhL;z$F4}Ooj zzj;eyCer`aekGZP?J=K7Kss4Pv4v7HvVS z%L5&%le48UAK(YL_}p&@VyBM6p1ec59$S=dhT_xCX#5D=Tr8QsZD!p7xQwNgBR8Al97a zfY?G#rZ6I(w}+`N;9Z61UL)f>P}^v-Vmnj7p3**l7!Y9p7uzCpt7UTtmk{@9x#F%! z1Dv@)izeq}F-P*RY}-cTUUyekF6wX4A(_c~83z{@LV(hb+W||8g}|3OGD0&%r{BE0 zZvZRX3!YGT&FB*_yXrD+Be&V@pT(f@t7s4cw4KQ*+<<0GPyq`+_Fe7>_u{(fz@et^Y7)YZIS%`NmJ zHnjl|W=@_#US38)#-$H`_==jIr@F*>EvdwvcP&mM;N7R9x{5IRz8(bY6dA*rRZ^|u z4>bA9>zp=XCt;mTSz4$Hw<x#KAosSK(Y7`ey$c8?Kf{XA>q*XBZ9x)b9O?*;YUpgoRh;y0|QGlO#biVQ{h`Il$= zSxfp{H2UtWFX8*0Cw1ezW{SKfQlp76%7nT{@5O814|+*EzD+NYMJO~qL5UYb>J+aN z_uuf`w~sW@>lm`WR<$=JC8gB9yV$|VzPDI(mP#EXem+B}BN6L~0oe8TUSBiJ!ttK7 zO_r<(%%|h4Vn=itazAM%p09dSk6mRC7}6(f3i0#rk)zz29>Ck(NSw0`3k$2g%ecws zIN*O>&ii{Bsg2;jZ`VT6Te1duSf2ToiO{G#tOd@_b8#evlre@q$iNUuNpafz_B;s7 z>oJv8QdhG1-AFrKcW7lSAtEJ*s{ymWy6h+#Sr=nUwD8d5le zDoajJPp9-KGbRIRe18<8(H*i9#Z3sbsWIPAo8FdMYpg0#AzI@>`LT zkdk8WG?6-pALPv-~QZNB67?Bl5q=Zz?^ zCe*b=N7SV$k|z@6#1R^`9T!$J^*ss8iT$=qzk<+`Qa^;jF0BjKRRsn<5!9+9Z%nQ^ z57pRZ@ACp5F7 zJ=0`$W{-mRspn0jH+T!W?d#qpZcIPtX$|6*^-xi%39=CMnfLwCJTbmxFeosPTWZj^ zyH_hg?(kT1V$~=y0bXP=DAspX*2B1GV`GlKdXknkS8MwEd{tEJdkQD=OA>viW{M-^ z<^9TL>PMLP#m-N_3JG;x35Y7p)(Y$09pp{_>A8Z zA2VX}WGr8Z>WJzMKd~V=bPgzljaLzOZsEeiL zvAViC^MRy`|N$`#l|zv zn|@@IWq$zy0n)e>Pmp02+nFTeSc~%jJ#)ch+hj*y$M_UW{2lsoefMYyF-zHN&^5<0Rf%)c>LV zJWoQxTgx#~=l4sUWVZ$*C;UM{%?dU(Qy&Nwz|%+{qOxe=-)%^L^~RhAek zNpVe64m-XgO&$6s?K^ylOTp#njvYJ9ZEc0#wA+S;hpVLPPEIa#Of9``XG_t`sGVqS z1sx?KFiKHV@vn$K2;hLFagSPT<-UK7wiv{F)(Z!i1rh5_!(7Lj zYh#@%y9=0&itU5?OJ;)!(<}C&p-foB?mq3YhmTqwb$Jd}qQ73AR+7z$@j^nV?U68Qu2!Z^dyOHd=EMlK5b4xL384;3ZRA$Hzgo#ufMql z+6*f8-hte&fadZ2n)2@Q)X*0f*ckbc zZ*@C{ZzuBoSLjl3tqyFo9OBu(j_8FTJ9C5MK7e68luG<{zoR4RB0?;NuG^eK&DTxR($CO}^NiB@XrZLYHZ9Gt~#b4?rQPuC`&kV1Sl%< z@p(Lc@6F@%^lleW36V-o7T!bBN<$qqY!iOJXRB!9pK$*M7bPrxwp?s`Bn>)*6oifC$grT99ZEgCKq^YqD40UzI zObr-(VBp=mcb`3f9&B60ks=_VEfih08rP(HW|W-`U^ME>D1wxB9krJuPcJx=E6azP z*F9BBrwN+43h!e(Ae*_SnPSguEITCxLXh6K5AaKnkz?sGh`6f*v(oNDniY^97r!)+ z8edowKYWWG`sO6ZCDqr~`)8nuz*DB!iR_A-S`f&2C` z^7x+em>Vd1L+~tu^fgp{54%^&sp@2c9HrH3(z+_}{)%DWS}JBfVMngm!37>zi|QiT z)%g}m`$APPkYj2~$>yD=nkts^r4A!vFP4(^T33ifbtTld=!c`h--M>Ut~U0j4TK9O zN*@At%d@g7kmo+tQ6Xs3($=QyJM)4;`^15vudIv~t1}Z7f^?a(wEW2n>Wx2@!U~F{ zFKJ_q_<&PkW0xcwo2@e-M`@SgRNAlmWE-Oy+ZSQLfe~x7c&X=2Rw(pLZns@pT%qfb zrFraUweU)6?6cU|kg4q0U_6FwO!D8gYZs{?QSqRlRsz5<^zj`3k-LWuAHG>zwD~n$ z)-l4OKBB(6z?wMTphj#>TyxV(U`J?j!(ya>c;$h8`_`(y-xnSjLo*n@YIWlv(M+Z0 z;}bz53Mr+Xhl*}=d!Jt;oAvk(*9ypE0N*G9IIhe)Wfn3q(JFCTCydjnf|O4_@44Yi zrKYEudwZ|2MwC=kaB1<#9U5znH@aU`H8ncZYjOMb(E)F38MaY&pZT9)lt>vgTV?By zm-RGVUIxy?m?(Fs$!g(EM7O;p@v9cr<7&i2O*pqKz@(ZyR(WrQAI+1Rd*>iQheJY{ zo`J!Kx{eXdpbjQpmYnCrKKqJWR?Gpr=}Gq|q;qss;OCcg=y3zkiC^;Qtxrf$=wG(T zsU0O36HMZ!J}%Jl(WpXLX{km_rcS!?@^Wiys{=1))om>T&o<{&1;u5Hw34dNf}>;N zCUjn@i1ZoR-sL<(!b}tU-Q9cksQR9;qR(C3q7M>sa$Ng>cg^INXlY(4^qbb?LBXhL zmLBHsV0hg|&&U{7E*4PY7S0rbm?IBK16WIt?b4TnxUpuFjhQkd$r4_Y44_PD@_eF{|^A}kfoGa zvkHn~6Vub*uFaNbuRa4T>2$@Rzo#}*--AlY$ji^)Z((7PXKAaUl>mtnZ8OJrqZwVp z&o7i8S$12_E!_888!E@n$9GlwtsRUg04zzFP4pFX94z;2FZMTxvLrR6C58tEmM`~r zKN;Kh^)Xs_0eccYn7J}}Q?GA#jJ?PgxO_%q-fmW|iD9Z9J$mG|$Qgn1;+UA2XE8Bh zfq@@C20eTBY*4$lsaV;=MarF-MD<-0Lmw3e? zDPyeibeHqO!8x-my%fW&I*3g^Wee|3vFo?F6nv#Wjkx%($FhtSGu*<@v)w{zV!={b zmh)FTMi`!;mcn7{=C~%zmxJ*NuE!H7AGP4@ivX6ISE8}=3EOgKVR0de+mNk2YAe^* zl1+2!S$HraVBk^YmxIoW<9S}wdAzCsF4xJk5%DE6jVOm5-h!)>1A|q9Z{91DLee(u zXcW0F)~QZ@ja)xIZj)U;IyySkZJijBGI#N#ICezCdCjC`tn8Ot@~{RjE~||zsP6I0 z+x1ylb90x|{)?0d6g8r4c9mOe_2n-_z5TX2g((}!Dj5i76*LVI5XVD!0G91F92-${ zRn~EGVh`dUi$b-}W5t8{@*9*yKfwgWMPDv|(j62v?%_SZ|6gksYOv?xzT`c%Wabm*YX1baRAj)8Q47sMC+;^~@2itoJse+;rP)w>n*qr0>`reQ?H&9NDe4kT8 zvdF0`bsZaOk{X=uejsX8Tr+FrS!=!QE2*k_UlCUX866c`K8#_-(NjDq3wejDbb=McVx?0$3H7)o|u{$%`A zeFSty?ev|$BZ$-|t22+9Bvr&Z_hyME^H!^ar|Lc2brbyN@u(CkJs^CwXoQ7jhfNFg zug(vZ^Z-pU`1|EN<1Y-zm+4P^#*KvN6rGc5sre5VK}XJsw3lSiWeM z(E;mwov)M5b-neBgrb?c4-=xq{VbxpJFr25H7u;u#z`1D^ln>)>jUh=>TVxsel*!tlP=P_$HiT309%Uiox!H-c+>9#?T7GI;0CTrTv= z6)nQ*FqDHb85$S_PE2&av_{$hzupQ0BR7tT)$rMYC{3Eg#4enKhCq^tvU%loYy#Lz zhN^Q%5qq@`;B*bQrAgl}ux+3hmf@O>N21h`Z3g^iP`LCuKHp9pcMN7S>O+$vg4ou> zBVR@y(@@i=x6%@a+3Sv*dw57%g%oHDMZWQ&0_3v!%^|46*DiC$l-1L1;AT5<^VR*p z51HctVYh_PM==xTzNhZ+Yht}*RMqNjeP-RK%2MVkE4^$Qlpi@Y=%Wa>`U@>Oo;-zh zR~W#I@ebi3ltJ}OVBo7)uf``Pf_xQt9qwN^JIwLMr`aW)tM$wo*nTEbR~{YW<_>9T z$@3cf(9)IXim~r45_LJh=|O+ANmc?h3_us7_S7abUUWlX-@f&ABlFf*SMxSz%k9dE zeWlXtEo#nGN|4vu_eY1gq_bk~I z78M=Kw|xJAIq%o^54x5=2sl$_1W-)p^-bI~P_Sx2WzlI$>o!dRHTUVrx(tq$`$uzx zsX+^|YN_s<7kRvV-@ZI@dfHIs+N$27Z^;`xUVd3&Y0cSbPTu+ZXJOK6hEq+Wn}i2; zD(__)U8Z{XOPY|f_CRxYZMoWD*sE7{lzY6J<94i@j&7TvP)1-E-`m*Rho;PNg}q?W zJPye91`WARHl~d!gxzD@bMg3-AnQbL$&M_e=*5?@lQ5}sv%Q=#SB~%DmOdbm7=9F< zX{Y7J$xEp^L1vX+d|+18W@1GB>XMn?y|9=V^Eg>gu^o&N^KjH}xMaLed>WY-t_V&h$qy+&14Ta?oE2&k> z9>#MJ}co*=#4m*uvjO@s_74xS$|Jp|Th=^Ed(@*EtXlEhLR@xyd5}+6sj-WY#%j zTD}-qf?g*Ps%(uSc|~{{G*Sz9ao+KL0F#YCD%5c*X#NIy=bO$lMT^>QUe6J zCg|gzAt)o$W8vs1rXc6%LmFFL1QOBfwDJtIJRP3kKK179{HSL9>Zxo~Jjcw;OiO>O z$rD$Yc|hH5k!r>A0Nevuc*E6G^T%Cz`b*FFuRa+|E-4|us-Kvd2}|4UnG$)m0W7j&oHrMaIbtCC;`LE4*`4SQ?Ch7x6TpPql zqSX#puGG}j+*VO>b9Yy@w$^WQ`hzrT6kuUxb-b*sEE#L$kSu_7(n6y9(I+2k&J1E@ z{7KXec&P=%t|cgQz)Si0Dz5@ zg>`O28)RPk3{7w%0^$w7Jdr?cL1rp&OzQvPZWyxnrvSQ479Pa2^57iQE$;JU`8xwX82Ak^-*Fv&@)dO zzOGf%+%G$H?=H(w+ea1)UthWA1bL$Z=KlVE=fQP9Qj;jl#TQp843sk3UV+Zb&w7FJ z7>~MUd0FC6&28gDv4P#)-OoguRnv9fOqOzRa;Bsf9Wz|W$}7fP?Q8M2z7fHq* z-L3Vl`?}j>00``ly6S@{;XmCzeHkt1IcJQa6Nv{|PlfrW(+dPC8tU0~Yog*Fy@>X8 zSBtIWJ{tAvr6AG>J_MLuq_O2#)yi6c?U01#efH_JYT(t?ME*+obch-?P0>?Ju(;<+Dvq#We~ z9CQ10%=qMVU}pIyy-2L54o(P=VGF+aj7zEU7bxnPOt2HiP0l+qUAqVmIa@l4y-Ew# zzG-mD2Z~=!K3bh_=}3r(vXU}nczqnA$|u9;u~lzv;_;{1Q+3V@MY`w;{mzF}hvj!) zxn02gsNtN7UC20Nt_}dIgToitfQ!9cVOBz&?>_>DKXIc-^df>gTYg%KePLk>fr=cJ zXGFf*9gA&Jk?h?Y7RKE2r7v!g3)cpD^i3wfzP80H045(pP5U**Q40^rA(uuK3k$KH zUMA?!QF$i58z{6r8TrZC+g3(B)rB_hFI3-?$B&C&1We04<8XmbDU!krcb$k-m+&U# z!yfsy?WsB|3wFYTuaVcVM}8~j(}?<&E2iQ1w^xziswl`jC|*<_QHS?ULX^wh9jSf{ zUU>{HcPu(CD1I@OpPpU>7)|c(*46?A*uu{J9kv+FeS9P*4kPYRrDKq+p1NOubo4P= z*X{!czD%&#(A#6rPnx3er236^ocql7TwOq!`%UmNiHks7ZV)Hq01&3a_@h2_UIRdw zHVt<65q-{Ofd#?!IKv}ur8I1kdscf6xO!61Ws6<1S=M31J`lI48Gny?2$h zdm9ef`S4kSyB+FSnYjxo?0Sn7C%m)tfc&bXipsR#Wcpjc$p_^JvuvH5BJf)4{(Zd| zEiSo5^@@#2P88Gh5Bi8q{Dx%cNKT;asIT$`t1@Y}#WMvW>s%n8k%=2H@AG~E@^$HdJLf*Y2N=SRj!7ehS=nJ-~ zB_&eJ(*xYF)8W4|bl1SZ;Pu1`ot|tJx)pIv?SUrM;>y*lwI-fR=N40Je<+v{yY}vR96OSTN&&Bh@70H^ zsnlev@!}_3(PXutxVCZ?6*U2M$!!)S)=#wZW_@U`o$hgWSooUv?s@!AXxDg!-QxDJ z?*XP2N}W_?-neU>xN%^oDOMh&m5o{=x=r^^IRI2J-|Zd#-FQ46ReTEws^(Tcua1ww zq@e9{runjDF%{pQ`SHZy_& zz!XR+tSuG<26lPP;X@P@KsqF{i}fu#*Vorq%}@Q&Ug%8xw-5&?kpRDW)AMs73jVWC zw4!~d0orQ#&&(LCtt_WET>m>}d-lAHtqE40-H%HB@Bz@Di~mFpp1ZTej18f)Ri$;B zwY9ZVfeyY_CoOTlbl4&ytbJ6K55~Jss^-d{sLIgD2wcrTL~PYd-+Jr^{F~}sSn>V) zD6-WGhNh>JBXR%$4qJ$i0bqp|UZX_=NRsC80<~QY9AksO!#ig^R1}o1E zV6l8+CKU$)2K{Au1`DiP#tt&wr)-cD!hor=vZA}-PzGdT65rC@ePbc^PQ^;cHxM_2 zva_?t2lZmdcUGUHg+%U;w@;HA$%)}+a#e~0*iD1XDvY0fpV!>g#6-fFKq8_30W+s< z*@CKd^X~fUYNcV9Qm;`)EkTemi{=mBg48KfFq`e3M=r+zaT&SCXaUi3;M2Z%n8#ci$`%c)Mz%NO!g}f_n7TD4ETuzWkJN@j}TE-AHR6NoS%G%4!%zQ(B4xsJA?hPOWMu#>>S!C|fe zX@UgQzmwwl9`TK#iHY?0i+$x*+F0{k$>Ee+U++n~&%Zy!ORMAQuvhl<^z8G*n)$jm zC~MoWZXlInv`Bj3WZ^;cv4r?MS3>hYDlA{*?yMllj%oD&0)X0*{XbJ%7_M>CtU$fL zyZx}Ihx>7P2Hd|`KH)*G<(!VWLPLVgou-He7{7?Na)%LoQ7Y^0GQEM!ToR(u*4Foo zmgrMTQ%{0SsZ8$ng4^o)gYfRxC~C_7Qy2&yNYjBQfYw^qA-TH%+SfN4N< zD3g)ppPH>g8b;#}BryR>bjz6uH5Q9rgzt`&jrA}kl~u&j>uSS8AZ&$9q?_xqu|0+o z#%ucdF;@zU#!^zd!#a>RpfSD9j~X2n7c;cZX}p-O3&f2pGMH~Cawla==^J)oZt3A) zrOP}3;A(s6L-?-@e82glrCI;JdiWD0{yXC0|96*NF0(+K$SG)ay)16f&$c1gy-?-$ zHS#uNrVC+u4G(Mm@^>wiK_g1RG#5i2nxke3Ch+6iTql2gTlt@R*+2}1J>T7nzmHuO z=ezu)FV*=g+y0A$Kpbmqq&6{f}ot#OOgo-__|~Z3_d-==dBH z^bfam4#>xXZy~nB(N6IIv6al>>)h=e)H@<|RJPq9)5fnX=EIz2z?UjA4R`I@w>cxu zh~o2VrnN!wbs_Mwp#8|*8#Cf&E2k7>JWm3YM=~E`SBG_|Bf`Ak=X~1qe7SFK2<;-x zZ8i+nN0#cKgyVI7X~qb}b8}@bMd#)vA10+_wB5D#&Dg+A%HW_TNW{pEW!{acSpPK^ zfGuxFQ>8XfZq%E_U!q-_PZQr=)8ND+D9`ZK?r+F8N?)SfCYBhnIUSf22rNHO-SWr= zg%DnoIO|y;g_}Z6gCf(7{jM9w9TN=yQ$S0_$j$uB`Yrz8igMd~}((+epP`4?D zALSO`Z;qshAyqHR3(h61Z77p+63Y~y$8w;qU9JDD+%SASK(ls}swik~?;;7i2WGIB zUafD?851$*H)U2^*-x&!$5~Fho%uoYm72RH%{69tl6I)sfepkF0LR;l`Xz{&ev;*sFWJ t396a=eIw!jJ6}iB(qE_1|J(?C*+7nxk*I*=C^>^{HUa`l&P@=>L847YL?kw; zC1;^&nhZ^o8t!a7=heP-?%U_w`>I~uABXCK>b2HfYtFg8FupN9LY^qgke{bL4?z&Q zoUEi81f5EOAd;A0NWqb~@E{-X=Zw=MIgMYykM}R8ufg|IPHHlbpyD1n3uz3HVWM{|9D_;fu_#Wy}Mpf6nT!$QH2zncu zQ7W#X(fG}DJ6aN%-*_yS_r87e)#N%h^kBk+@u35hu0Zwpd8$!c-%UZNS4v+xC#N&> zF*w}$DYPepD~zJ~lt0&xTf*A49KvVFpqfF?pNFc_eqL1+zxXR13DuM#Cs5NFO-1611H@&a+ zus~b|9jzw!QAgiC^5Rk_(_0vqke6uc^69QHx)qIU{B9<~yfc0KjC(`4w{ z+Nn>8?U%Z~D(MM}orSQ9)iS{%bdZirjSz9d-xKY^i4S(ky+NGNv(%ULsp-E^K!qkM z(#&e1OI!V)^Bay$or`xH@-upq(4WKLP-zcmJ>CAoSEbRuvFfEYzZCe;mu0sABMK(4 zGLk13gK+c5?o3adU#-eM0Ba=6oE*E#BK)wrnYeP2{&wO{>;_;ooWXMrA;cTylC{ch z;;JQ{tY!{auoCCx+oUgvKCZZSa@awOxM@%{aiG8Huf${SHmt&pquZ~xh{u|avnI!K ztR;M}r7|+97aSeQ6R-C_R=J(nU&+{*IaPOei99k+|5_WlvhT}*HwftoHo zDp|w0loUsXdQ9Y^vFZV*?aQ;s@0ecFCcz zgB^mn=m&bD^z^P_q)-K_pKIWz9JsJoi!gJy`4luf?FY8&3OMrY6?mv3aof&;k#~VT zLwCS)o6dl%5`O!Oi!vXQt@|hoSV*x@Ep1al=~g}c>9+suqRUY~+&n2Ee_SMZ z1%El`Tch8bvsAMW$WYOWtaKFYROc(VUPAtwn?6Qq=saCk%)@&n(ph~~)|K66&R&s*^;1xgZ_jS7!)7I z{4^kNAX@Qk+8HRl7O`7&i7v$dGM8{U}0YBq-&dvAN2K_(v)c@X_`aeE@i`E&M!cTO)4-0yfp{#NWO7j|@ z`Jcs#|K1FMt%!)7gPQi+=>JI|x@?P{o^j9-Yd2(4XKl@Nl)7~}=rb<`R1>h~Va1=E zFJP4)qmZxFu_;<&J7nD%27f0j37ikNg9Fh3!N36Zk~XXrR6H3BG&bxu8`#_JAvzhW zNX5hFtESL2;)h4t!WuGPja7(#$kJh-CvXXQv4)lc>V4Syxh^!w!+cJtmRt_EbNL2H zm&DU6N+LIqat@SYaxa3V^)H@<7H@%YfZK+pzL|#!`e2S^RM5Z!}#glbpisf zgh<9H?g%GdgwhXSz*Z!01+<-`OJN{}khHL={ibGc|3ACE4hMrfh<~dKv(@4b4#U6= zzDrzf#DP%0USD$y7*fT5VJD-Wv*zr|b%e312*0I?<9X*gSLD^E{XW}9iDGoQ?*E_n zdSY2O<$T|slCFM(NG6BUttXBaq|?`&g8%Ex6A(Ce5I}~>d!gf2zEq;9E_n+1a;gkYeBdZkhFPq#o+iR?=ojGN|=gL%d*%CRx1u+WCHQiiio z&GrC{7_jCA%-3oVn~$QY)G4xX{OGf$Rdo_2W?V`BI%mJGa$0u~EKD;kiOP#H?jxp@ zM>ZZLkU=w^p<(x_{h$VGyhc&Xyocdf@z94N+gtMZOS#o0Mj21O;npF4{&gRt?4V4L z=fDhl+T0jY(oE$?GRSQv&p}9524LZmAhu^ONc!pmJ1< zAwBTm77qq!J+SHj61UD!M64aynVp6fb^q7ea}Z3(hbB&O$I8J%a^`OGHK=Lp+m`73 z>XdrrtgC9m*0ih|j1u+C6$IUwlavQ+i4UIav~z7m&h}hSlw_t%NZR|^jo&l-5^nXq zespuy8%n>i+mK^co(J4Y*H$$Q6-sk;3~%>BFAxA_N;`qS1~30jSo#+)=wG^1h=m?m zq;%{mqlU`;Y~V*vSO$AKPeEGkcps0n9INd2WUY0y;nHKp)KNcb8F*>J*g>}V=su3? z1-_F%L=#&pQQ%p>;BA9$Cc1zG&*Dp2$p0f41tP}4y;M?j)42=sFi(AQGH5Zq)k>-y zZTDH3>&8%30kNnI$?728+xsDVaKnL;Yj%S~Y%6?K;Y@qLph;(#3W* zkMdWLD*$$th0UJ$49F%ag~AkcblTOrz=h^#<+P@!w_)l(!{ko~qBC;?)!iEK67>s^ ze@NA8HYp^bVPEI^#Du$%WA7#(Ejh}Hk1ts%XXv%`?a;O>m#*3cy~nB#xy&H1rW&?n zH;ItHvAgaha07CWcv1e8o`Ac|hwZMid$q^$QAhEeg^u|V0iOhiy9w{C*xQ}i7YEg& zzwyTeWjcCol|q@y(H13c85tQ|e;ns%okzJ^-uwOi{glQ>{WkoSrKRaF5t)gJmJ@X@ z3T~^43Q?3Hzq5r=r|!2{qOdN^L$^VZ{g5Q_jjz|NktPcjHAQfxQyay~NV#6SY1!>9 z*4U1ugoIvnbo8dE3dUJ?EyY7-@6pZmVWf{kL%oYwFaFdsChmrcqN2Iu?KX)%q!9rj zpB?qbA7#Sci*HFuYvv}tc#D@v`0Qq^P9gKEtF;-AaV9<+sQ7@d7isCV@=i6;zPT8= z*mOj~7ArqmWvj^iwXOCJe-!WCyXjZv=4ie4Uc|~X(YxdP`QLmRd+o#t46;y7lwm03 z_8eP$=V*;f0R^M*dy9$#8iI6|Dq%A|n2(?T!%IZr#?%}3$>zX&piC_6A8VUb`}kyV zz4t9ey<_RXz@8zQ(Qy!%CL2wt=fSWZeX*Z@UtCIxf z=;-KeyyUa^<} z10nDkb;RM)YufNX3_V`t+)tdZUa{v--v2S6AndeLSnRu#_#r1pvq%ek_{7AD3bZ8# z63fkTn4EY3liKUbM+#k6$1^fTGqy^@Bv#%e+E&(GNaec&>v)1VV_g zI^pKBxA|i4s3XkZXKKjLu%R$IN7>rWt~k8>$o^nwRg;%ivr=EvZ}g~F5d?3}(MEH) z_ta38t%TaGcRD*fL&ZMXdwbrUCOzHVn9<4-x}KLW+D+BQs%+KEM+T4T4-|{{U?&4N zXKCd6FC+_a9^exbYiqpcJM!D=z2`$m40`&%79%rOoLYij8PRiIVMp-41mpBbQ2#e$Yp_m;f@y5o^b;XUfSYT9NZY>K@ zJBg^F1`tfQ;?pMlksme}WezNFpUS*U zJkJ&jlY94?FmVky7Y$#1aAbdj-gCVKdXcIU%-c#Gjt{9KZfC&c;}#~@msMbZcA+GR9M!yBL{{D9V6ilvNV{?PjUSXo~k4sR^ zH!?I5V?RwoyG(WF8tuenZUr&hKW`8(_*9LD__HJ z0LbjQHhy3>}Ycpwp3+nS36}ixBl|BbHmWyg{!fh^D)XfE}NHfMEEa(S5TnA zLW67DVq}Sxx3aR5NB0mg8{*az@B1tkBNSlH+&tkDtlqxA=H?Qzwb3Hj`@J9qAE@BMfWu%_Q}OPhrgJ`i5* zbdWqhC1FKPjJKa>N+TLPBuOvWz^Eadmi3ujY(j`5qK$aWl9f^&%Covb-~hUlSf;T?%oU{(&W_XBIGni)C{ z!u;SlT+Zv2Lwr>(iZ_D>EqFBwP$`U_G~v?Jk!{o=(y*x^T(W@k401(}PlgDwUXccC zT0#D6^RrD=P0g4uh+zoQs|kLC+PO&6E^6ZH9wz#*U*Sb1#=~npu&DXLH>>xbyzF)`C#hvL8C6LP!sKL1NYRH zhW(%>E4RhW!}WP2yQMW)vCd~htH0*iBrA#nWwHPU&u30lOSmd=Djf^%h1>$LYCAna zJFCxPta5a7)|`Vz+tnh_c;nMg&{e|8=H{n`^X%Rm1~uzXAU^*Vf=n)jdQfi)<(LSQ z_k8<$HAZ&((@DJwK$qECx8M!rNM=C5^4*Vs&xQ3x4?c7KGUBfMYG4SiYGl+HmI1h# z_to4h%Wrr9Y|jyLAcwveG&p~47X*bWSny0baF=Q#Nu}8q5ZL0_50xGv+@q)k1;yGm z{)EHN1&`1J=2!&OrGuxTXPHG_Aa7nm-6)|S9PZ>+@Rbdheq5=m1WIlUUuNIy*^MtP zL3%ea`-3h9yIWE@Lk_I#$|PBOU&p&ip)`{b8!Z_>AIWad@}lHt;tCuz>M+{xiGIiR z3uts|gwH|pjerIf2k5tSV4hcG+E7=pszo~v1D$<+s`V8w!uahc3pLpC8XCmWC2x6? zl{h&#a`lTX%*=EJ>?itDMFyhYOWw-pFqXWN?n)3gRJOOY z{fYs|$;c=qX8wSHRd<1Va3ofR(Kq)1@3geCf@};-PqWqJ$PC5O$#1Xn&+12p~h3e1JNtv@5C)q?GvkJTC+RD7bm?;qRK zorJySA-@bT&CsCCN%0-6wuL?)IZ}VFD#sR5sYjRj9xKs{A1;d|*Sa+h{P^+y<PZnRD1iHXz+fyj3pBw;Nks*_ghDzmEI;+x$1r)M zncrWt;V%GbE!VIFyI2yLpuc5FOr9|qWUf9NJ42-9)MS09ubG{la-$dCz@WkVX+z`Y zt!E&S!{fQJKk^2Wl9R3H3XjJc>bfEGeb6nXHO6 ztzjCowulvLVN*_;FiUg~J#q5c2m#T9D{=t1RxB=53oywIm2mrUbu~5l?<`N&FFE2@ zC(38}g^DNKdGRI1V|6zBC-AIh8{f!Eb>7m`bAQ7uMP;lK%g9$2>SQkcM= zjy_AxzZA+IuuMH{!9;Wi0+El6I#i0VaYzQ-J}D`wAK~QZS%=gWu;iuPpCd!_^6_PB zuw@Ud!TSQqE(!B9g4b=mzLIK>YyZLo?lX~))SowC9)lcP4*nhrsu%#Pu?yYF;JN#q zeuq-!PUZBe>auF-t{2z3&RBD!?9(B20o^*N)KCZZUS%YI%hrA?JL= zg#hU@ICZ>!Z*-h6jb0f)GTXyP>PivgEhvw(>>2tVcusG;oFhit?S2~D$%?0W8fBc^ zuHUgPv7IksJE^<7x)#3+R68H+&;xtBm2vp!q50Hty(rS@r5IR(!L8`_#ogq#AAI!@ z1QlkQFjl&Am~Ag}O%|Gz2qj>87wElLT*7VX)n)%yo1%l$q_R|7=NzRnFSp}e2n*x} zyG{pM-Js&|4@qx+9!m>Ndm#i%IUO8?9l7|Srns;mvVcH!y~3aLAg5Nm#Jn@#X|f!R zrE$Fi2)dCSWITNjeAi{|+;T4@g|M2SlE@6qd8$l9{$th6we}43;9wl?QeImTJ*~n1 zB9_Lz0$kPo`H^a6QgX{^3TIdvvRfE2e;RTQwyG@j=GtYjQ8?YWyI0ZmKnqaL@sCeK zZ>TS95qYu*jHqA~RU|X7XqnuM?3WYfU^7XnyL-~*?<`7#b7NGyVXLuY(C6oXeRnlD z50wI{x)U#oy5XYPaY5JpSMR+o`B7RrAhDOm)Bd$yci9#@-Iv=>2wal;K()}S0xb%I zdu@qovNMUXETAlSM_r4m>UOV(wZ_tqrt65VdRjmaZlJ8gIws(n^@dL3Ra?{B=APN(00LS~v)6-v! z9i;><001c>k{c+;>1l@)UZ~KIE6o(}gev!`|HJ}*e(u_K${*L$YA;p^K?xREfV$Qn zGOEvrQMG*1274y~Ng38k74nD6!jxYLD0p&ArcNkpKrsHoLKBAh(&vc#;;B&} zHopDR*;#|zb|kYqS%5uOKEI>{xyKFz^3#P27ue$2lM)lZck5L|JMVd|M6_iNg29i$BI2Y2|73 z2WVfiXn9>5E;s}Ph9)-PeyM`@ive*_q*uMPp7A(Ty4JqWo|hKI?dPFmVqyZ=4%*}H zPqXx**FcJGVypLA=A^zLMSh7K(|d#LBm}PGj4Z%3jWcqqBkNHgJ)DiAPNj$0j+Z`s z_e8S$iFz_qu58gyeaDUf52+$v|DMMjxwK>s2gyel zWu*x|0|Ns!o;aRVAulbsucurZIgl==n}zkEM!*g@sW;>~WR-njwf+nw^@soRv%6 zFk*@)c-`0eKh(MiOdZDIMiN5g8b<=cYUkZ=5~XI6LYws=x4 zHnl*H*xn))78VxuzM-Mv#c|q&U&B#{Q_Qckb+~NrTm3{0%Juq~)$|)~H{5kusG=Ce zzLuZh1qJqjj@XVeKSl-y!>!?m4YjoMFP}yY4wN6V)w-ULF4~bdaDK|)Xo=?m zrj4Qs8J-G1VU7m8c3Dal2jw2otnKBCAoea zwiAAV2QYAAwQePz8_Uz0i6-_Z{WHeFVUerS0qUTl@zZ zmA61e10Z&d$JPh%+@noKzrN*h8yPfJ6qUu})ILs=RWs1ElsJ>|yxwK|cTiz~J$79k z&X}k@_GqQg9xKvdvoFzCA{D8yGiVNh*UQ|JtAajfo=GO$TTe`%kaKs zaIYJ8Cq~=)3aMlPTP?O&o6fLQoTmY>u=$r#JUl!#xNgpg1|ONI!Dv~Q#wWcact226 zr|3rl$R7XA^uF-X!aWbA5|iVBkXGaKGLM7_lTWEb&*qqbxW4#^%*e~YM6ZoX?Ps0Z zy_aDQ%BT%@{dX3Un?44O9*?gcjW=jIf$HPgEei)7K%nm$a#7e^pi5Cs2_uI}=Z$9` z0(p|A;3w}MhMHFqG6*O3DG1}gvq4SLmKoQGeG7R+X_^6ukDe${RV+?h#zjk!AP!O| zxK2U6;&}6kWv&&NAW#gm&oUH&tIA8CXcR3hl=7Q*W}=S?sv;$W{F#=)djGOfNODZr zoj2e4INdXfHOOP*3RDAhFTls6nUfk2shJ(XgWQBW34a)veq-bPFhnx40Q4cyR{&vjXYvhbu;HY= z_Ko5SQG*Apb!lp%soUFZY_>6pnOEc{<+w;8PgsO#`a{l@6S@cP5DAHhHAFHjw(8s4T2 zXJJqg)&E2tRjgt#6eMBf>`5MfdX4vQ4KUL}aouJv+e0#gg#u$W=N8Jp7~hl1e4e_8 zpR)G#ox}`RHzF=C9su(?9olLv?C7j7_K^W)bxEL}j-DQ+pjTzFwa^V}1i&*p9i5j0 zQ1$cYR~_@AW@aB;w!U*tAb2?C(kG9~=7ocDmY^>iBk!n%39WH$cYFZw|PBdtG^VbyIEVymOOY1ow zo7!O-H&xX*VzU&j!j0(~DX-ZdF$W1pt5R39QWv$oT~TaEP&DsJL*>8?qtG6AwtCFU zL0Su5!qr3^?Z0m4gP$bN#G^MI*=4gE2|FtjwaexJNNm@-O`+yH(}nYE}*wKBZUM6 za;%3MUJ7=`GDXYTj#U97PW=GC%JVl0-=Z^24<^TLGg+}bWp1{*v>D4@z<>w8&D?|( zoTm0?wW6_NC%OMtMzG$1Y5B;D}Ojuq?iTy|;`n048-!cB2F>9Pc z@*CZsNI{)jyGCU3>jY^U&H14BGLajFM2yha-%;^0yKH$XI5>Dn)b;IJu{1(I4u;$2 zXzVCX{VBMJCU;~puH=NDc`Z#mpQi!X|64n5CPe0FaGOC~{=hEC_POiwaKnek-+@R8 zG_1>D@;XUgE#(sLVu%!vov)XAlrv#}6aD$(#fuim)rkTIH$Z8z+eOcfhRZk|FV%E+ ze)tK`6#R~pA7#7+N*Sa5*S!jZ+9TE51%hs7rtUh&>*a8vl=%+8TJnkUWP#$j(QyCE z-*MfekLjjttbnV+hX6cy<$MsY?_t7`UM7R@XK*28>#-!ff zrclS}a!e}@dW;xTBy#+O){`2J_G?}u#>;9cByU|_nQBx4Fg)qdSPe0*-gf08;OJay z=gw_gAggW&*s)NCEY>@%B^%(0jiFjIJzexnQRg(}sSWCO-$gJ=RLvd;B=g(yhIhyV zJiw)YC>uo;F6E4`t))*SiyVZW#VV(Xow)jZYD1m&U!4gw{@YQlu0>tgQf=_XsCE4P$% zYI08tuBfjD?h_7%2>VFy)ph$?6n#T)5n;96WF7IjjfZVUydnZ)eL_L7v!hSUojVh1 zM?j;rnY)FKJ-t{4s4Bo7r;4~*;8*<&9c3`C?0*)A34}|GS=|$td?$NW?Bo27b2W+a z7O)_~`wN%J-;*(L3osC)Ew^pof>JWjbjLQ0&Fc-d5&BvP{l2cQH+DWw3f1;g?_f9> zWd}Ev^A2GO)YLx}YNq>%Zz38!6b%Qk!^h6RtUkYctK8_O6=d9u83xa(>1`e|92)+9 zFNo@AZR+H^_!P*Vq72$F+IQYYL-v!sK@!#WC^}*r?k^|8Gt>|tMluFSiYsVi637Oj z4OGuqPiL{ z_s?6G|9#QF*5du|efsx4{fqw%@NZN3Ut%hUMy%QP?$?q^^daygC#5V|{OH-M{{T*p B3Zei2 literal 14138 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 B@)-pPY`yWSglWadI!IvY;C_2BUfR z{0^H_mdNhJ7dOVMOPBC|4y|fQk}mhVNn>P_&Wj?t7GbyV9k!CE{Pt38rA|USpOjtQ z5dDXgOU(5d!PN$;PXzM!4uhOac`(ZPi__Gj>vRp7`T_>!)kSBUD4vT)EUjqkQ7QyY zLb9ySE+i>XHRNV#=OLU;_+C&#t}Qw)^nUx5quU*&t_-a3k#CAFlEpoQ z{3{Pxjnt2B>ne<0_=N?shL$ghr=>a^s0g=WI_H(Yw$Iv19fxQZU|Xn@r_~95RPN3T zA=Jr(mE=xx7EOYPM3T|)qBZn9e)M42d~XnSU`D`_Cq%YFi;8so$XlVy;zC==6MHj$ zA9jr@6T3pes|FY_CuK`W=#fZ>d+@7M8ourFt8_@&b{MZ(!s}guADnq$zLl+ig8d-( z&|2NWc-Cw>3DBF$cGQrRPQJLm> zV9D~3knc>(n-pxVhgRj`3~Go@fxl2(yvEOK^=s7rR-qpOXLg>|K9`~~_I(K2R%U;U zNJu#xp>rWt@&>7bH~xLJLN%P+FVictSC2K?bP$ie8-#U)CY5>UgBN&>Di-p`Dz3%d zUl>dezxJsGZJR>M+U)lIIK3)V1YeaU7B^sSwjZC&dcFt#j0}D`YfTvaQUtq$x1x)i z%R+90G!2VnPO&p?}aEF#R#mNgj&o>u7DizA>XHqqOc5s-`7EU0oR2XEg9d5rm z#Iqj+9}-5$dh7+y6kbs46`x6D2IgMEV6YQCaWcE+q%mg)iD0$KNeE(&ZHHZ`@eZc` znTeI}4=(?7;9-#2x8`Wzd5|`TwTu2a8Yt&JZku(l3w0>6mraS)HdAQLL#c!7&Yq@+ z9aTTN9ClDh*NzX~*yln9cJK>4?9@GqfD;*g@FKDgfpNf9i8~+Qug`DMg}`fwmTSTn zH&)Mm_%fdaJqeq;?IkIJG^IS{Xe%H>V%@dvH!3{Ms2Yz`Z)NGEhQRHDMnsB|JV;smO;)BZB z4fNU|eOOMTfOHr`<;&89GOY!ty`SQm6s;sP`;*%*Q9v#62@Fc*f)cF#Hjb8-I34bt zqF0pAfQBTSmX}d}>3Sljz)1hwFBc)}Uw6H4pFVA4H6wOXt)=~YyQRs`$Dy1nro!1O zEuGyQ_AjC?2}3p`u&n+(> z8FBYFQ~#0bwfI&buHb0H4Gkj^HfrLm{T_}Sxs&_x2}T;Zv)Ta$c3tjnzZTowPV|`ub&7|D_8NUKckjULlif|FJZ2UMI#uCLmBagm+_Pi-hGaTF%=H?HlBpeGv^_FF1fVdBZE1G zM7S7fYf%*QlA_h#w>LM5CupDotu&4M7Y&UD1{jeNMeUOr{mFe7y3vl%b7qhk@-oF1 zpGsN{sf;{L9tIU!F32sWvvv)V%fsM0r$ub@jkc&;UQ|(@kyjRY_D(XjgN!w4byHW^n7oA+8F<^7ou|q5q{dYh>nFtm=Sv3f}1w(w(tE<^YH_;h_CK+_u&Cn$MwMP zFu-rcr4G@}Bs=5ib7+7++R>a{keVOb9Zm=N<>uzrw)<98*DCvMnUP}WojuQkP<)tV zcuhFcWqF&mzgqhRFAFf-;TJl;3<)i*vt&{qHgS3CO%QxoWRPh3*mw%yr*-T>uS@}f z@uw-UgM$&Mohh@WAo5Un{x%W)6)ab9h3*F9P6+#9=H;m$R;X^t9W0%Z{zCwN8pXFC zYAjv*jcz)I`R@lrlQRHxVXk&d+M+C`>!{_EFo0WWBB7T&Q003azd{cU`1m>_GbGNg zaDUuUOB~eBTiVP!2CBlxuq~?BP`3AA;DCQg?{I{(`qjmg71Q3 z&u%h8sbOVVnq9hAB#inK?+fMv7e; zAe@oNf6AGSMUf{)siyLG-5X|(Qi4$GoA~LQnTAG;xu5TlKyv#a*QD2=p^-M3uYWyP zo9Eda$Z?k$PhDf7<)uH#doGlREnJ5%@;#4=8hU>3=}W4=k0rDTt2T2^1|Tp%G&Y#c zHmQTk7((YO|F>Y)ZZ0I}Z%kU{YHNx|=?EuW`3?FANBmU=RfN5o=-8z_WDByv!>S>5 zch?InH<+uzok3H`hd?-0YLyvNbAf{2<^Gr2tgPI(Xls>da?7IB<1r;j_269Rd}1h$ zLv&Z}_U37#@lj>oJZ&?_T90T^bd>Bip`d^W&CV{-45Ff$nu^uN6Kzi*h`<%wp0CYw zoQ=!T(MfD#>EvI7_mh}?Hic|DsK_8*vHVoY@CH_k4oT<>Hc}>oGX5e3F55K6q11J7 zxYS7SKlOWULTF0#&hD~IF@f>9>vAo57YWjGDjaYBZIJfw$vVxc+L%_LaaE#!%@U*~ zBa8{Fe?#}5V76nlYTUDymq9{0>*V-}jwR9YeFz6+?Q)}w;IHsnKHZ;-0?KjdwdVM7 z-amjLEnJobz-(gps-)=R+iaInXE6?_QQz5FE;q(9+gzNct7E0^)N$;Qo!_*Y zBOGj9g_BDYTu-0Aa1<;l{ML^ytv;3HYHD*g@E9ow0c*Q{ZJ&SFR<#@VRn<}8D;317 z)6y<>0Yrs2>RY50Wc?7tOauD?S5#xu)Dk#t#>sHN0|(8n_oN~OEhU6FgOsQ}Rs8^x zO?vhRjt$OR%cO$LboKbn&=fOtzXB@xvZ^jGVeaT8z`5?>HNCY-17teBtfRX*j;OUarnsL z0n=*qpK5xPBtr~ys#e!?wi~NZm8ftS8Z0zIk0%0(pvf`qcBkWUNsbqB4O9m~F|o8| z5&MOia4rtT;Jb2^*p;D%Y@(P18z?2r-*FpzySAvFPE>8Kqi8{WeZ9ELLTY5&6V>@B zH7h|;(H!OUbe?FX(Gv68Wb(wOT9MnJkE1XWNwp^p70eE<*6OhO*?a01*LxZ|J3B9} ztn`$mE1)MPOws1L2sbxf)B(EKg}7EWIy&YuJ^RdVc=()kM{3ifXlAJ3c31nNRq;Xe z{QUetf2k3FlB9u1Nkqo>wp+c+Mur=HG_!g{EXi|MXpY71L8&hAS6z_17q@cz<5ND1-SfQ!B?+)qFpwCMAjQjYk-& z<4&{gc2%NBM$+2a6zh?P_3@qQhmk)+t{uu=HNWi?fshPi_1cN<y*`K^VJ&nbg_=6TolghMe` z8!^(IO?lT`2(^E#xLqIi#2AV!be-YOFCpE={8|Ocj2<5D8f;-74|nGMoPR zR@w~=TE9-oXtCm<5J>GgJyw) z9dG*STu5^n(!pVJSLgAwH4Mgtv(@Mg(6<8J)<&->*0|X{9qAq$8*^QlY|;rI!}?h_ z+c_Cutd$`qN!6-^Rl?xxlpVI*1mF^|zbSStyQ!4a+xhwV8~H&+stINIHZImKCyQIR zZpFV()aIb?-8L~35}VhA-j$>J8QvPAe zJ72_|=lSY)ri0I>phiAfl-g+PPVYBNNN&b(EasB-2NFM-rE$VS*b?g@iNbDlAQ#~5 zJwk#NLMn-WYgwe*+SAhzSdZDgLRe1(So))9iMvXxndj|XZKau+8PAzev+qQbq5873 zH+G2saE~T5JUqX=T&LAcyvVx1O{4+<%7WXCQEE$)%;LaHgxY^v}*_*dJ`avY$B6uR-C* zrmUz2DpB1d>)+nW`H33$RYux7I zZiMEq?TcK;ZJ(N8RdFZjFpu&`NEK~ZZHVnX_%isr_CeA&J1bXn{vEwXIJ<} ztR}QVTjxdTw@s(-bDJAJDLbHS0=WbDpOT=En89IPfCUH4Qi z?rlPf;`p&3rXmX2FW=q8xMq&ghT)kEHhzX^3bc9Jhymm$^rE817)W-e{mBcnIRHy< zq|iyi@bWKU+w}Y?D73q|I=OxTP_(?7SZ2uEz@*+=hux$hZ?P6R=V9QHoc*|W)y=He zS{sz_wkMiNNvxh*>k8;i_wau6Dn|Eg|1o41rW25elEm14;IFo`0cXyh3XJc##1_67 z+`u(JRzXdxKFc;oB3+N|NjFhI)UT+6To1=3zRyXUbe7wV39k+}m>5;s>o_|DaH$mW zdW_~~Nb`p^Of69RfPcR%!d9h)%OwBKah3igjd!Wpe)0rv#T~dJf;**s8COh_6Ctl< z0k_)T-d^fS3jl5_6sP_lrYx=9ZS;X$@o3@$&N02{*_c88IMj=%M;BwfzWNhyLKG*78T@MyWRgsDgA5&_^mU z{_K?c4ZZk;1TFP$djK1rV7|&nj*g8H`r4vK>uqD_EpaYOb3^Z(%kFg=!f7rO_)mYf znys&|sRVLu0HBq#p@Y~DSZl#NT#8HCy;yNdbhT+ zQAnhVOHR6Z`nI=Ec*WC^rvf;5%`leUaNh$4lk?tsKKuLc?sKYykt8;Mler8Pm^+Z? z3%s#^MCnfgTB!M0K?4Vi{#r$)%=G>*WsG4!5J070X7?~M*B-B`e2RW z>*MQE;oxSB;rOaYCUJCN#-5T6Y?hFOF#KYCYfzHG)Y8NLELB@JiEl_f^K~A(%&`~f zyD+{;!GzWL_4#V(J-0r#yN1ZhiHu`$xCSGm(Q4alj>XaP5(^mz2Zy1w0BSucdp~m8*~+$Ob}NVD$n6yYv56xj zQ06n_2I{@4ii&r|1zZ3v%W!c~(f4Au8ezM>2Cftr0MUTKtsgsSM%=$UhV((tzK08V zj>dJi{^W^bfbW)*nwl)goQ$UyXG6HQw)`pSOwOD;73ebANRcGD`(fmJNhz*NInt9@ zv^r>}K|sBt15)6W72Wce-u4<#LVQ+sb}YvASJ1ZlF-5UDQE!ya9)|GT^B-b(I-Vot zwPLECy6;sH8X5{TV(ZB`iqaVvBtAT=c~NteI3P_!N3+}!SGhGE!lmndXxSdqnf1UR z7{Bki* zTFfAtTH%~A@m~C7X=ynNmyF73Tbe;0=1`u9``mFV2=i8X zsRrClg5)3|ov^b!5B>`WYrH!-`SOe4X^1%q=Co%X3wggj0X@9J7IK{Jilbv>@T6@m zkdpWoC&iNg=u+5K3{pYEr-QzztyzlgQdr)x{oec=#O#~a0eS?=@GQV{@qvl?c@{i} z=(_t`BU`BFd1Ua{H^pZ9eE)mH0*y&_^k+Zt?x}Z?k5d_(`yGPJ`S=veMJG%~|L^xJ z9-uqBl`sI zLJF}!sjon?9UU`F3dOBhVm`~gf~EL2+-XDo`w|zs6&(dkbxmWCAd#f6e-g70u=nMB z#=v4)MFr9D&depg1?pXc*^{2rD_c=?Z2+-DaC2Z*Uxa{U-;U}VT7IPlSZ|+r&`P@r?07^Na zdaiD63%#xEI^8o;YT;9wlSx#6bh`9golMnW-;JP?*XCwK2m}FS#I={KLB2(LBhs7{ zhQXlEEbIdez;d3AWpLLS&mFt(A~Xbma+&$Qn;oV`r?XQL#OuAw_99=ep9`r$;**bj zouS5e=9ToJyIkbE`y7HlI(=8YJX1<%;2f6beWN%9wS5|MP@8Zn=nE+1`FDb7d4O*` zk^Wy|4nXncUAz>27GZ^uGt9GfiN4$4&1~%L)ug0qVF=;M za8VHf^0hLTI)Yea5}%ONpBrnY)zy1W$QDj{(ZGMyQp5{)cRg0etF>U{B53Fr;+x)^ zlk9i+-VHwip8_<*Q?>Fkh~|$g48G=X>Ez{ zni5WTW%kz9T^{`~A*|xHF30^QwvF%ja_|~-6zWL{r5iqGK0fL>g?78_Qvl#~p>E|& zO-*&%oxFAPW?FFY$>Y~GUe+A@E zHt?XX7(fG{xiK%`&9)r?XxNHMIn=&$=MEjluK-k``m{^+2jBDKYZdJz=XAUP~j%&3FpEG7P_1 zSrw&=Wng}QYJzZ~$I7o*lhEe;vS|C$x(qPZ@Ntt^I#k@hZ9?(k%xrCbgjzN%pAZW3 z_xriWYka&$y1l%J1&dd0OY~K|h}%@jW>?SQCNLNar@G7|l!Plvip;PbYKbA$)z_N@ z(Xts6s(^^3JI>i!Ub63u9Vu;}bw5G{J=-Q@fD8!UytzJNnMfz@_z+c7!-Mi%7%fxW zPxkHua83But@PkF#e0{}*kK7ee^q<{W%u$#!B`JTMGep*Xi!A)C-?MI(@MJB)8}UF z^QWYC*_aKn$?fC_9ky}wE@4}dCNgDGI5n}WMU`N8(T~$lWryYkfw{V zJv%#9ptJ6JkU8_F1%WgV0SJV-EF<1^dY?b1uu#<;h!}Hdt@xz!5xPS9($Z4T-Do{m z(w-}5s4spchVzaj7Az*p&6TfY)_+}On_MATeurr`c+B!LDPC4oWg5&yQQUtNV zf#S&Qzd%DvyVSRk1JVhb$Ltv&VxWw+meVd%OQAe+XrtI72UrB6p76u_Bzf)EeHtm? zuoAJad1)fz{Vi7Ep^sRv7#y zeY0>D-xtBTEKXnjD@U|(z%nqqXPK>MRrQyg1foTntzOaMMrM0_TwDZPGA7;>3+d+r zDscC5+7p4?P}Ylto#fVmO?^N>uY)uQalSa>V-N#qrqK3Z|68L4{o@ko!G!*8=;1~2 zDis)l$Oyo>|3nSO8LqKZj2K>o9`du$N>nGz%~HRoDZIw?1xV}~UI_`#by1UFXKvaT zQpi#&ankcP_&zF6Gk=z?~l#f#J&p_0tT^neuCbXmvNw{ z-H|r9rihFecw2k>+nJG@Y4B7GZP38rVBY)mF>7U=tD+&2{Mp9R{8(Zz9W(&s?+oC7 zceSFT=DK-v5#X)#zcvW|r!DKh%^BU*5s?8SFG|@`ptlcd0fOxm^idw{uIl=GFnFQk z6XLAHcCbbIFweBsMhqzmqJp#^2r~D+8hi&@8=ECbpwYMfPR;8^&@t$|t-TsIdfGnQ zx|;UC*3>YV@CT5f<==Z6?o&X*DR>*}EC2?(c6=kQc0)alz_q+(cGNlj1HPbXAi)L| z&K(_adUj*?Xfj3QsHZ;_mb?Hr-wS)87x#8%PAY0%j_7F=*)PX0`gDaU0H*e8qq@%baFpL=6K@ZuSZo~} zifLKJbDjtoMD1q$$2fzbI1WXui^@-OWl!wHB>6YcLA=$j`}R7mDGAAF+v4QG@$p1) z2?-a_W`OlW!u#m>q$D)IAAuKBFHu;rEu19kVSG`z;yClIzX)=+zi&G2jqv(qR#%G= zwpIoKY#3C1%8?6lH0W;RVrt!N?A_F|O?_3x6pRK6;1Npvb5?C}}THFOVBR+a{9FZmCp zA!}_sVvWr9DUpufuPV##pndL>bd~$f9|t@o>xbh^f5;ZxQR^IvK)!1Cm3FmS)w>2H ze%r$}MA>-C{xqtY9rZg+Ip!xC5Rm|8$ci(9JL@y*VwrxG;s0Wi&>!uW0 zWOC7HdlNL=SjF8P(KFjbcEe{0EJH18-9A>`H=<_vKypu&8$;UZfqMV->sPCUXVMFe z{-mX)yo04Q+kVbfuMI^2(MCEt-g0tsoejPV#0x#uKNKH8*;f}yFut3(z+$}Z09tEm z4eoO4?4mz<&OQQ75^7qOp0+6d_(B#5ueCakTqJ(fo^)J?Y`X(SL0EjZKT5OI7v$>| z+jx3`P=s1Obf4PTDdQ@3#& zw3I1R>geiPn$t5y6PAb2gM(QNSec?H&Y!ZJPel)XiruEK#9v1V{sJ}p3>XOctcZBCe0Yvlx@MNpARbyj=q4O>e~`J z;FpnY8NzN)2WGt3w7Sg!=`4Y<&HEEa91Kzc61k{KDU{j^22H5hF6n*=0)KEIJYAwc zc@sc}xDyQ(7eVJPk~VqfBugSd0FJ|!wsz3-dPmWfS^JnM_J?3#!1!k{AaZBb&c16M zj2DHsR+R%r%^i2YU}rB7?Nsr#pu9Zr_%R^t19om`gjqewwA0j0jZwA&ZATN@lhAL2 zeUaKBGnmXIYK6#C4aL>fptyG(6>m`%O(UcH$6@yAd^*vS*Ei}Cop$TIv0T!SM)v2a z-+}+ptE#HS$P{Pffwy{iX20)WG5Z3x?*FBEjz1a5{2MI#7mttp&vO58mi>P${?E<) i|Bs#if3ed6c{qcSJ04FB{vZv46z{3sEs}fq;@<#v8=*S@ diff --git a/packages/stream_chat_flutter/test/src/poll/creator/poll_option_reorderable_list_view_test.dart b/packages/stream_chat_flutter/test/src/poll/creator/poll_option_reorderable_list_view_test.dart index 05ee6e69fd..73cdf99695 100644 --- a/packages/stream_chat_flutter/test/src/poll/creator/poll_option_reorderable_list_view_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/creator/poll_option_reorderable_list_view_test.dart @@ -5,8 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/src/poll/creator/poll_option_reorderable_list_view.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -import '../../utils/finders.dart'; +import 'package:stream_core_flutter/src/theme/primitives/stream_icons.g.dart'; void main() { for (final brightness in Brightness.values) { @@ -374,7 +373,7 @@ void main() { ); // Find the delete buttons - final deleteButtons = find.bySvgIcon(StreamSvgIcons.delete); + final deleteButtons = find.byIcon(StreamIconData.iconTrashBin); expect(deleteButtons, findsNWidgets(3)); // Tap the first delete button @@ -412,7 +411,7 @@ void main() { expect(find.byType(TextField), findsNWidgets(3)); // Find and tap the delete button for the first option - final deleteButtons = find.bySvgIcon(StreamSvgIcons.delete); + final deleteButtons = find.byIcon(StreamIconData.iconTrashBin); await tester.tap(deleteButtons.first); await tester.pumpAndSettle(); @@ -446,7 +445,7 @@ void main() { expect(find.byType(TextField), findsNWidgets(3)); // Find and tap the delete button for the first option - final deleteButtons = find.bySvgIcon(StreamSvgIcons.delete); + final deleteButtons = find.byIcon(StreamIconData.iconTrashBin); await tester.tap(deleteButtons.first); await tester.pumpAndSettle(); @@ -480,7 +479,7 @@ void main() { expect(find.byType(TextField), findsNWidgets(2)); // Try to delete the first option - final deleteButtons = find.bySvgIcon(StreamSvgIcons.delete); + final deleteButtons = find.byIcon(StreamIconData.iconTrashBin); await tester.tap(deleteButtons.first); await tester.pumpAndSettle(); diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_dark.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_dark.png index fcab3abfa60c8820f665f465e0c2b3eb29593df0..7d50ee1a0b2d44a684bc19b57a606dda693e3f0c 100644 GIT binary patch literal 5708 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 n!(c!8o6^&-^ksi~TMMS&1YA1PgJM3nG literal 6852 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 P48S4VqX&xiV=w*#`FyZI literal 7018 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 St1#CAIBj66pLf#f+TQ`Q{Aket diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_light.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_light.png index 4b3accffd16d704fefc54d8356c0a75f72ee9969..24c03f908d0d6931b3e7c93b49566f186547e244 100644 GIT binary patch literal 5680 zcmd^DX;hQf);1Y}CJij|587!s7J45B~;Dq|pkrJpDW(TbuB zqEbK;NP%od zS&c?DTuEEU!+0&z`B>YoyKxLheKg~zZM@PTGEL90!DQ-dn^vE)?AI=_mrtxSdVSwe zlKpAwd>kc?G|PRslcaBruE6nC0PDnigW6g2yoiWO&s!V`7%vNG=F}{TYNZP^AqMf< z3Db_!HHhw*p|t0fC2tO*W?P7nHp(E>x=_SiOO(zx4|I7g?zWY_Z055y)+n8&VmQ~Vfo+`sf}YZ=e;z;%`)h#dRXP$nANcX??XIJz_ru3Q=e z-d@>A1Bp!2*Ux18b*~Hb`^X4*Q$I0&AX7hmUQ4D^`-e~E>QsEckT2h>vroP(bjn7y zY}eOwdh%t(gfydb%fO0GTRYkADj_}cT~xo#TN-`6mPUVrr7?gjSC>@YB;WmUCt9Yq z_qM+sCvU#a>#5{PaxPa5&h+{p;50TT=qJ4IOU{+cLpHX7HxO}2liX}7Cf@LqMA zTv1C*>(Zid12@rv05dLX&MT6el+KxCPWL;a=67{V26qlftz)%nnUCCrvncsNEdqxS z2K9OIGv8Lv?UZUrr=x1WxhOl59&;iYF@Rba^eQ~y+D^yK)fX-U+LwD4X1U|FwzFR> zHlU$=UYJqAtG!o?N_t(yesmq`qWcDis z@S{wHpX~EZcy(WIg8TfdQ@ndYd(r5SfFIWf@*}yQ0igPH^?$7NpR-1`HJ=_5paBw< z`aXhZ+6{euS#|@TuZD=yD07#5s(9kYdE4tv{zjOM_FG!6Ke6K6$? zC-%ShVYvE<)r*}wXZoAS*pg~>Wsv?&tLZrVN$Nczi974Lg+s@=dob5v0L$rkiEEg+ z-(NK9#8ecXjMq&q;C>HSE8ly!P)***LF*8goQxP9SqZg+N6@Yf)l12J%;DZ(kR}?ci z-q2CAB+`R7P^OwOb|#JlAeI^{oOh zb?6fIxlnDe2zK*>H;O>JSQWvJEfI=O2_`3>lJ|k$dk&ihEJnxiyEuDPh3|dVIkX0w ztOcwMW)7<}*S2;G$#fjQCiR;wFe)syQU?ns6u^pKqODV!QiqQRBvCZ6(-dlean(&~ ztPWt^&dJNI5AO`=X(uC>7hkwA_?-@yYaaFx8~oiK2>e->8jN#q)!l2jiRtklVBy<+ zN-v?eIN4vM2NSB8F7_8+(p#=0J1wfE2tY?4&_sJ2@IWWcKlel@*hIN-jgMIP*~qYI zhVxEiHSli!D>UI7OukG0>R{%!A#Vj%sb$7lnwfynr^WM{g* zGVnGqjaa|A2hS!TXFxbzV*efJ{g+`-ua&JAANM`R;xO!i6?ibWW z7-=;*yio?)Ipfi5ppPmH$x+_dUe>Sm@~jISjMxA|-LncWM(KO*_rNeY)Aprw+<-t( zP^k#gUj|mBkk{^v4Iv6NvE~zetPxWSW+NB+K{8L_cCuN4eB{Zpn0j}Zyi@HXgjZT6Ki=*9k|%z4?p4J3w4@-n-1u!8_aPy)33wz#&> zXd|_Xpptw2rNqC#kC3(scqeyWcx!p)*q_lVxLl|(^&%jTz!0vAETjl#W*RqF=_>&h zYM_R%AF_?SPxEa+URmvYdg2{4`l0zJfCYZD=$YaN zEpiL+Zm&>Lf{rRmv&0-jUo%5MRB4Zh(v48z_>CAjgT&YYC-|ny@yn5a%gs4#$(>wY zTv&>LweqyU6Oo4>eT)x{zitMz#@i-3v^&UydeEh)XtSrLA~=ZKt=Y zIhA!X`w;*t_fKb4R^OQN^nz?}%ER6MB6%&h6l-LjivZq7>j#|NJr8p&b;Cl6zcvd)hAJ}h{*;H9mbFr$<=W3GznBw?Zrw>mW+1aqGXLsnT za>!b#{>Aneo$bs0h5XT6XZs9#8oj)wzHcD`&`P$F?g_RENb6n)Op#`VkFpu*VLyK= z+yW~a>*4v#8vw)}`E#hq5s|-mI~k>C$#CDJ`Y}jvzmt<>7AQzVtR=M9M}1N7bmXM| z!-*>FwFlx20LjgH8bzGx^AN?DI^ZOG3A(a0Sf9YUqdOLs3jlq>3w_Zk+wXLe_0RcBA-7`EGuS&Z0AN=J%}`u2y{b(qb*OY z4R%GN`Qv+Yi<)nRh*fPD#vSR5nkGbETn4@tp3=#2`mDyHd-8st=7L3 z!G6rBzG2sD%74BLwh9oK2J6bFXJM_Si~4`(M8k0qR(CIPA`b6%&@v8FD0F*%=8e!( z1?>6)QY_=#DSnigl%}}N(C3kRy;$tpf+%yn&xG2C_5cZ}$if2LdtiT%i zqU|%B?`}WQb4CQma$I>q8d6;(RZ7I6oerQePMG!$s4`5uS94ce#HH2hZjP{Eff}>w zr^=w?><2SNWEVtodro#*^j~mAO52q>K_( z1<;}D;L*@H4D;uRd3s$1V%_$Fr!wu?6Mhb(xsb<1y>ifL>Hgg0wN6|#Rm^lQ#_=l| z_$?HWm|uRp1XjPMZhk&~8Rx}3QtKP)GMJ4rKiJj^E9fS)PI?+I_?9sQ!rKJ1s$uaO zvI*^sNXtp@2>>hM_EjgsDUi_EnkxVctu5y&%BPouUR}aeL6nN?9+qPd0Kp_jU9p6D zojpuL<$td+`4{Szyzh~d+L-r^h)5Z!z1FRgxB|_mj|#P*(etAYN?;dQvdHEE_@G4` z9c)C&W9TS|NNi0s+W!TViH_*%vdcYZrel@52K07Ew{_Wd!M6bUx6PZcvdx%^3dK3pHxIUx2fKC8|dtTW9j z>{kq{GAgGDFN+_-#8Il#bX<7%)_hMC(^`gdN-8XgR)8+ zF7K%!V{mJ*t5!7)?xs*f`p=Yhy#5aU8!*!$`dZqgIuT8%HBI&B#H2bO9kMfoWE^ic zGPdZ$?URb+CX=*?A1kEz=7c`O+rxtOuB30{CcwJ&$A;|hz(z;C?L#D|@4zI-s>7zf zW32h{Ix}&7a%xTBfK`Sww3f)1cof_7@GC(g;n%i^oSY;5^CRH{U%C9>CWm#h&a0;7 zA3LwR+e(phEOo%T68PC&VWrbwbhE*eg;JI(v4_x_ps3CX$y8SgwWSO;GN*qOJsG4*Z5ok`-}d7~g0#*gvudaJn%Ph#N_ zN5|@|kcM~q?M`pir>gqqo~6M6Z!vv=JPW%W=T_6O7~@f94J>}6Rh7S!6iWk$bH9<4 z2;|%2=ORS2c&)N*2_sY1BT0Ct4;L*=c`yU>St%E=J2Jxvgw;{0b(=Dkz(`4Bv|j75 z+a-KXjMxLIUX0nU)r7sKQIK(}iStkiM3ZH|2PpaXh1#3-9^8U|%|Kk!M|VG5W&fgl m|JBclzvsgLcfk>^_HbE|W0M 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 R!vC#+wS}#D=}GsW{{z|7Ac6n@ diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_dialog_dark.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_dialog_dark.png index a385614ea1120934dfbedeecd2f997e190176c47..52ca2ca4c9b27a45d1f0effee48dd5d4cd3cd2a9 100644 GIT binary patch literal 18426 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 literal 18739 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 RhDU&$J$+Fx`=sq({~x|8P?rDz diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_dialog_light.png index cf9d67caee11ec55804228c2ee54303446e630d9..180d152692e474095472af62ff7cb2a40b378e8e 100644 GIT binary patch literal 17954 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 literal 18266 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 B3Zei2 literal 14138 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 B@)-pPY`yWSglWadI!IvY;C_2BUfR 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 diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_dark.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_dark.png index c207861cf4ca14fa2b72deca922b9ed1931340e8..5c812dcd313a82bbf0610a3f233cf0c9b9e98980 100644 GIT binary patch literal 11526 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 Iy8ZA!0DB~VjsO4v literal 12111 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Ȫ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 diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_dark.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_dark.png index 3b60ba2cf6b25617a813f12420ef818617586325..2ca407113b779f3d2b3dd0c4dc0b3fc57809cd54 100644 GIT binary patch literal 9560 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 PJK$RjTk}UJeXji{h~3Tr diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_light.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_light.png index d404cda7268e6ea3e76c433db7a4eae91f7ffa49..352a6c0e310a42199525477c0e57c65cac766a42 100644 GIT binary patch literal 8856 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 YiWLboKKKm$YYE_B>ts`R=JNHw0ov;u)c^nh literal 9473 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 ZvD2P!Z)|_$4!&W6&LC_p%T8aq`CnMNZ94z} diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_dark.png b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_dark.png index 6e7a5ddc8751f6ebc6d34dea247d7b96e05e42bd..cddfb16671fad6f720e5363738513e7e1fff7252 100644 GIT binary patch literal 1736 zcmbu9|3A}P9LLYTl-(6srQDS-`PL}ura~ohcd=oyrU;eMuv#=t6I<)cWyCN=LO0*q zY9U--r&ao*cu*SFsGAB?Zr@1x+P$C8?w@ea59gfcIq%o|oX6w+Iw!@K;-QPhV*voV zBu^q007ND<568@hdTBf3I5ePR-AFVH)Dkh_LWmKuR1Y^$(Yf*+0Q1=-;tm?O;KR!u zp68=hwtm?kX1_(5MpcM)LhAyk%qr6@kqsaEif)wWe<<=w#uVv>p3&o|z4q*&7g0Fa zWxB<2t4jNRJ>O*=?rd1yTVczz7wb8032VyjWA%^mUTJuR@e#B9&QIf$s(Uj-yjOWD zjyPu?_}p_f70a<&Jk2>mdBS#5jRqC_@r?CkcG+FC?Dad924X(pn?CS8Qr+H;Z`W`L z#(Nl1q<0R{!R03VgS(S_Jy~7S%R}tF!L_6goIVJ!95TiskwEt7i7;(=yiI2$ZW#h0 z@E_hoRHQ!my4Dr9$`tqlKG6byQ>d>G2$}427$^W~Bhlhbb_$GUgk&v>sTo?d8PZ}k zH62DkcmN|tNY!eAb(t0~QngxO^Sc(MS3J%NfU97hlNlNfkn5z{qmic#;rm&dMQYLQ z^wKPjI5DA^{dL5T6Boy96&Y2p^~yPDjZdGhbp`|FDOot(0%w`YAKSXJ_f!T~MyI&< zEKM1ac)Q1XdAZwj^&UM;U%~k?8_(fztc^1g)L1a!R%K34T(D+wic~HSayath>5sV? zgA}579Re7p2IXx2^jCS`>(IH0>Dqmr9gTZk6b;e-T`BpelN+`Rccl&`NQB15(c0c7`LiDU7WEup+X{~;T*3M4%+ z=i^JcgwBS!>65vTSIi`0m4F; z3L?EIQz3e=@x7^;2*v*R!l}^lXiLv}AZQtTo9B6uP#UL$JGwNpscFl^_wTam>SsNs z(BAl4Wgx}u4Z+me*_p{=aW(;Pg^R`E`UTzH-6PWzqt~UJaMML%B0i2ylv;@BQ94x>E5#9)By1|eisF#C2+n7B-@lDig$R=M!cR{9!P1b#?1=h04D)zIPSff7b;&h9i= zk@$=ZJ5yl5vw@Qy7!89Fcx>11I)3BB@pmp9*RHA{8H@`dp<$Koqm~x8P%8vLCuPUp zX1Q8fS71)W zG{$N~`^Nzw-|xtGU}XfZj*2fsF`3MbPEOa_+Q=>uD1fG|%iU#`<-0C7=!;|jn#{bs zpF`wv9Z%HImliki-oKyw=&^y&qDQ4Vboj8hzrR1fLkD24OijgGW|Mq;eALFHBj?U8 zS+b;wJ(Oxvg8;}I_4O*hZG&I2MDcZ*Y+~RpYLkOQLg6By@0wxavO6I`Uah`S^43U) zD8}RQ8yy{`NlEkV?2!P;IChL?Yimm`ql#1wCzkh4PB!S7Y`>va2WC$}*Evrv>+b4$ zZDYJ4KJU%brL!H{OI zU!M0v<-W_PF@?E@XykcSSf7F?U)wG|7?}Ei&Syb6SLJuuTp(LtAV+bz0X!bBi8GoL zJu`#X2Y34Jp0q#%L%7`LaOuwq07!!!1^-y<-CO~1hl>rn%m4rY literal 2270 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 V(5pLB1N{JM*fX|hiiOAR{{eD2|3Ux& diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_light.png b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_light.png index 7f4c1ceff8859f33e3fe442dd95a383b21ec8aa1..af50c7ee7df2e9304e67acaab3bf0c05fb422949 100644 GIT binary patch literal 1769 zcmb`HiC0s{7sn?FLS&HyF(4p>h_w{Kg{4>miD`g@R*gwm+9>-$4QP%61`rQXDI`@8 z#LAC@f*_OtVb!>R7zH6BJ7KA;Rf<6YLu3ii7Wy84!*9-5xJkj^%}i4tF5oC%j_8b-kckGGbf&3>-fOKYWZ?%&S>_* z_NKP>7VWU>Sn$GMu{UrkThAnB@vWnKZjL%}P5qNBH%%_59He$ zYk@@~LHX-0VU&9uB7}yd!+U1vMZ!`l*>ponXxxxujeZwOq#n_mHJ?Wnb92=# za`|sLwVTsx z*{9$1k6XXjU*+~3qW3EnIU*vWJ$;88QpV74+Se{;ouNJ-tLq;zGu`*Jm7at;KqB!H z5)zOyGQ9j{LhhJpwfgr7&+xS2$I_IP6sH3RLN#S3;NDjecH2g6af>Ufi$mM5#e1yF zTj`_Qcl38DyQX7Z>fhHGw3tpTTy}JH9D07K{@3P0WitR=o|;dP-S){#+D)L#9k(0A z@TafY`9Cau5ZxMfe3tBRqK--gcuLu*Xe72~>$#E3(EW;;wjCT}eSnelkBD49lk@=YMwCnV%h zQ*+_!K$XUxklsZ;kaN9uOVxPMGnfeeSpyn#KZFC5-vpDF+^davjT6Mu!75om44tYEN% zNpG|OBS=?Iua4DxH5MW+znV*c0Q__7Npl#$YSijnW2@%fK~rV)UeoO@9gMozal_ML4gp|&;ZvWjJnnVJ7V6F4OG>0?hFuyiYC5YH z5r86wvhg;`uax~p9pF#0nVFjE{-`MVH^3a=Uv=JfQ+{K4z8;lXL@Bs8HWrwcmgZaU z0oC{H@Z+O5tVn)DOMdLLX#uUbukRLwxs$WAZ-*HISS$Mbi!(E^yRztm%D7cdK<>A^ zfvY-(eZ9S*4`4mC;GVeL%_4@+%Z)WNZ|_4d9(R51-@u~~TJ7(eQl0njFQ}|^Q0+GV z;WwY=k!SIWBE{*Qq7c&*QFjs3HhQAPfK|$latxNBYQ>DN|M&9cp)8sWQig}i?2Te* zh^wumbGU#ZURvT2-KqTZ>uZ-rpI_p1<&*p!vRP0sWq+BTKmf~E6X&4QKz(+sfwTL_ zo$i`2dE(!RK`4lim^qRW5=fwzO|T7y49YR^gTQ#93&9p(4JQ@?C-@fu_&+!$A+EA+ U6!MbpI{~QCJ&#Z;DZy9%15rKilK=n! literal 2277 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 diff --git a/sample_app/ios/Flutter/AppFrameworkInfo.plist b/sample_app/ios/Flutter/AppFrameworkInfo.plist index d57061dd6b..ab8e063fe8 100644 --- a/sample_app/ios/Flutter/AppFrameworkInfo.plist +++ b/sample_app/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 13.0 diff --git a/sample_app/lib/pages/advanced_options_page.dart b/sample_app/lib/pages/advanced_options_page.dart index b30811f5f8..60ef370698 100644 --- a/sample_app/lib/pages/advanced_options_page.dart +++ b/sample_app/lib/pages/advanced_options_page.dart @@ -137,7 +137,7 @@ class _AdvancedOptionsPageState extends State { ).textTheme.headlineBold.copyWith(color: StreamChatTheme.of(context).colorTheme.textHighEmphasis), ), leading: IconButton( - icon: const StreamSvgIcon(icon: StreamSvgIcons.left), + icon: Icon(context.streamIcons.chevronLeft), color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, onPressed: () { Navigator.pop(context); diff --git a/sample_app/lib/pages/channel_file_display_screen.dart b/sample_app/lib/pages/channel_file_display_screen.dart index edf0705412..e543dc1aed 100644 --- a/sample_app/lib/pages/channel_file_display_screen.dart +++ b/sample_app/lib/pages/channel_file_display_screen.dart @@ -67,8 +67,8 @@ class _ChannelFileDisplayScreenState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamSvgIcon( - icon: StreamSvgIcons.files, + Icon( + context.streamIcons.fileBend, size: 136, color: StreamChatTheme.of(context).colorTheme.disabled, ), diff --git a/sample_app/lib/pages/channel_list_page.dart b/sample_app/lib/pages/channel_list_page.dart index fb9db8ec7c..0d440a9dc5 100644 --- a/sample_app/lib/pages/channel_list_page.dart +++ b/sample_app/lib/pages/channel_list_page.dart @@ -38,8 +38,8 @@ class _ChannelListPageState extends State { icon: Stack( clipBehavior: Clip.none, children: [ - StreamSvgIcon( - icon: StreamSvgIcons.message, + Icon( + context.streamIcons.bubble3ChatMessage, color: _isSelected(0) ? StreamChatTheme.of(context).colorTheme.textHighEmphasis : Colors.grey, ), const PositionedDirectional( @@ -52,8 +52,8 @@ class _ChannelListPageState extends State { label: AppLocalizations.of(context).chats, ), BottomNavigationBarItem( - icon: StreamSvgIcon( - icon: StreamSvgIcons.mentions, + icon: Icon( + context.streamIcons.at, color: _isSelected(1) ? StreamChatTheme.of(context).colorTheme.textHighEmphasis : Colors.grey, ), label: AppLocalizations.of(context).mentions, @@ -212,8 +212,8 @@ class LeftDrawer extends StatelessWidget { ), ), ListTile( - leading: StreamSvgIcon( - icon: StreamSvgIcons.penWrite, + leading: Icon( + context.streamIcons.pencil, color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.5), ), onTap: () { @@ -228,9 +228,9 @@ class LeftDrawer extends StatelessWidget { ), ), ListTile( - leading: StreamSvgIcon( + leading: Icon( + context.streamIcons.users, color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.5), - icon: StreamSvgIcons.contacts, ), onTap: () { Navigator.of(context).pop(); @@ -260,8 +260,8 @@ class LeftDrawer extends StatelessWidget { router.goNamed(Routes.CHOOSE_USER.name); }, - leading: StreamSvgIcon( - icon: StreamSvgIcons.user, + leading: Icon( + context.streamIcons.people, color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.5), ), title: Text( diff --git a/sample_app/lib/pages/channel_media_display_screen.dart b/sample_app/lib/pages/channel_media_display_screen.dart index cfbe915fbe..1725c44e8e 100644 --- a/sample_app/lib/pages/channel_media_display_screen.dart +++ b/sample_app/lib/pages/channel_media_display_screen.dart @@ -64,8 +64,8 @@ class _ChannelMediaDisplayScreenState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamSvgIcon( - icon: StreamSvgIcons.pictures, + Icon( + context.streamIcons.images1Alt, size: 136, color: StreamChatTheme.of(context).colorTheme.disabled, ), diff --git a/sample_app/lib/pages/chat_info_screen.dart b/sample_app/lib/pages/chat_info_screen.dart index 1aab7b6430..5c7accdad0 100644 --- a/sample_app/lib/pages/chat_info_screen.dart +++ b/sample_app/lib/pages/chat_info_screen.dart @@ -125,8 +125,8 @@ class _ChatInfoScreenState extends State { titleTextStyle: StreamChatTheme.of(context).textTheme.body, leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 22), - child: StreamSvgIcon( - icon: StreamSvgIcons.mute, + child: Icon( + context.streamIcons.mute, size: 24, color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), @@ -160,14 +160,14 @@ class _ChatInfoScreenState extends State { titleTextStyle: StreamChatTheme.of(context).textTheme.body, leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 22), - child: StreamSvgIcon( - icon: StreamSvgIcons.pin, + child: Icon( + context.streamIcons.pin, size: 24, color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), - trailing: StreamSvgIcon( - icon: StreamSvgIcons.right, + trailing: Icon( + context.streamIcons.chevronRight, color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), onTap: () { @@ -190,14 +190,14 @@ class _ChatInfoScreenState extends State { titleTextStyle: StreamChatTheme.of(context).textTheme.body, leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: StreamSvgIcon( - icon: StreamSvgIcons.pictures, + child: Icon( + context.streamIcons.images1Alt, size: 36, color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), - trailing: StreamSvgIcon( - icon: StreamSvgIcons.right, + trailing: Icon( + context.streamIcons.chevronRight, color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), onTap: () { @@ -222,14 +222,14 @@ class _ChatInfoScreenState extends State { titleTextStyle: StreamChatTheme.of(context).textTheme.body, leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 18), - child: StreamSvgIcon( - icon: StreamSvgIcons.files, + child: Icon( + context.streamIcons.fileBend, size: 32, color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), - trailing: StreamSvgIcon( - icon: StreamSvgIcons.right, + trailing: Icon( + context.streamIcons.chevronRight, color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), onTap: () { @@ -254,14 +254,14 @@ class _ChatInfoScreenState extends State { titleTextStyle: StreamChatTheme.of(context).textTheme.body, leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 22), - child: StreamSvgIcon( - icon: StreamSvgIcons.group, + child: Icon( + context.streamIcons.users, size: 24, color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), - trailing: StreamSvgIcon( - icon: StreamSvgIcons.right, + trailing: Icon( + context.streamIcons.chevronRight, color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), onTap: () { @@ -286,8 +286,8 @@ class _ChatInfoScreenState extends State { ), leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 22), - child: StreamSvgIcon( - icon: StreamSvgIcons.delete, + child: Icon( + context.streamIcons.trashBin, size: 24, color: StreamChatTheme.of(context).colorTheme.accentError, ), @@ -305,8 +305,8 @@ class _ChatInfoScreenState extends State { okText: AppLocalizations.of(context).delete.toUpperCase(), question: AppLocalizations.of(context).deleteConversationAreYouSure, cancelText: AppLocalizations.of(context).cancel.toUpperCase(), - icon: StreamSvgIcon( - icon: StreamSvgIcons.delete, + icon: Icon( + context.streamIcons.trashBin, color: StreamChatTheme.of(context).colorTheme.accentError, ), ); @@ -412,8 +412,8 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamSvgIcon( - icon: StreamSvgIcons.message, + Icon( + context.streamIcons.bubble3ChatMessage, size: 136, color: StreamChatTheme.of(context).colorTheme.disabled, ), diff --git a/sample_app/lib/pages/choose_user_page.dart b/sample_app/lib/pages/choose_user_page.dart index fc73289c67..617c52c252 100644 --- a/sample_app/lib/pages/choose_user_page.dart +++ b/sample_app/lib/pages/choose_user_page.dart @@ -135,8 +135,8 @@ class ChooseUserPage extends StatelessWidget { color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), ), - trailing: StreamSvgIcon( - icon: StreamSvgIcons.arrowRight, + trailing: Icon( + context.streamIcons.arrowRight, color: StreamChatTheme.of(context).colorTheme.accentPrimary, ), ); @@ -145,8 +145,8 @@ class ChooseUserPage extends StatelessWidget { onTap: () => GoRouter.of(context).pushNamed(Routes.ADVANCED_OPTIONS.name), leading: CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.borders, - child: StreamSvgIcon( - icon: StreamSvgIcons.settings, + child: Icon( + context.streamIcons.settingsGear2, color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, ), ), diff --git a/sample_app/lib/pages/draft_list_page.dart b/sample_app/lib/pages/draft_list_page.dart index d908593295..00d7e56f87 100644 --- a/sample_app/lib/pages/draft_list_page.dart +++ b/sample_app/lib/pages/draft_list_page.dart @@ -39,9 +39,9 @@ class _DraftListPageState extends State { children: [ CustomSlidableAction( backgroundColor: Colors.red, - child: const StreamSvgIcon( + child: Icon( + context.streamIcons.trashBin, size: 24, - icon: StreamSvgIcons.delete, color: Colors.white, ), onPressed: (context) { diff --git a/sample_app/lib/pages/group_chat_details_screen.dart b/sample_app/lib/pages/group_chat_details_screen.dart index c2de052627..0873313e26 100644 --- a/sample_app/lib/pages/group_chat_details_screen.dart +++ b/sample_app/lib/pages/group_chat_details_screen.dart @@ -108,7 +108,7 @@ class _GroupChatDetailsScreenState extends State { color: _isGroupNameEmpty ? StreamChatTheme.of(context).colorTheme.textLowEmphasis : StreamChatTheme.of(context).colorTheme.accentPrimary, - icon: const StreamSvgIcon(icon: StreamSvgIcons.check), + icon: Icon(context.streamIcons.checkmark2), onPressed: _isGroupNameEmpty ? null : () async { @@ -264,8 +264,8 @@ class _GroupChatDetailsScreenState extends State { const SizedBox( height: 26, ), - StreamSvgIcon( - icon: StreamSvgIcons.error, + Icon( + context.streamIcons.exclamationCircle1, color: StreamChatTheme.of(context).colorTheme.accentError, size: 24, ), diff --git a/sample_app/lib/pages/group_info_screen.dart b/sample_app/lib/pages/group_info_screen.dart index fbcb5e4387..768bf8ee75 100644 --- a/sample_app/lib/pages/group_info_screen.dart +++ b/sample_app/lib/pages/group_info_screen.dart @@ -185,8 +185,8 @@ class _GroupInfoScreenState extends State { }, child: Padding( padding: const EdgeInsets.all(8), - child: StreamSvgIcon( - icon: StreamSvgIcons.userAdd, + child: Icon( + context.streamIcons.peopleAdd, color: StreamChatTheme.of(context).colorTheme.accentPrimary, ), ), @@ -319,8 +319,8 @@ class _GroupInfoScreenState extends State { children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 21, vertical: 12), - child: StreamSvgIcon( - icon: StreamSvgIcons.down, + child: Icon( + context.streamIcons.chevronDown, color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), ), @@ -397,7 +397,7 @@ class _GroupInfoScreenState extends State { mainAxisSize: MainAxisSize.min, children: [ IconButton( - icon: const StreamSvgIcon(icon: StreamSvgIcons.closeSmall), + icon: Icon(context.streamIcons.crossSmall), onPressed: () { setState(() { _nameController.text = _getChannelName( @@ -412,7 +412,7 @@ class _GroupInfoScreenState extends State { ), IconButton( color: StreamChatTheme.of(context).colorTheme.accentPrimary, - icon: const StreamSvgIcon(icon: StreamSvgIcons.check), + icon: Icon(context.streamIcons.checkmark2), onPressed: () { try { channel.update({ @@ -440,7 +440,7 @@ class _GroupInfoScreenState extends State { if (channel.canMuteChannel) _GroupInfoToggle( title: AppLocalizations.of(context).muteGroup, - icon: StreamSvgIcons.mute, + icon: context.streamIcons.mute, channelStream: channel.isMutedStream, localNotifier: mutedBool, onTurnOff: channel.unmute, @@ -448,7 +448,7 @@ class _GroupInfoScreenState extends State { ), _GroupInfoToggle( title: AppLocalizations.of(context).pinGroup, - icon: StreamSvgIcons.pin, + icon: context.streamIcons.pin, channelStream: channel.isPinnedStream, localNotifier: isPinned, onTurnOff: channel.unpin, @@ -456,7 +456,7 @@ class _GroupInfoScreenState extends State { ), _GroupInfoToggle( title: AppLocalizations.of(context).archiveGroup, - icon: StreamSvgIcons.save, + icon: context.streamIcons.bookmark, channelStream: channel.isArchivedStream, localNotifier: isArchived, onTurnOff: channel.unarchive, @@ -464,7 +464,7 @@ class _GroupInfoScreenState extends State { ), _GroupInfoListTile( title: AppLocalizations.of(context).pinnedMessages, - icon: StreamSvgIcons.pin, + icon: context.streamIcons.pin, iconSize: 24, iconPadding: 16, onTap: () { @@ -483,7 +483,7 @@ class _GroupInfoScreenState extends State { ), _GroupInfoListTile( title: AppLocalizations.of(context).photosAndVideos, - icon: StreamSvgIcons.pictures, + icon: context.streamIcons.images1Alt, iconSize: 32, iconPadding: 12, onTap: () { @@ -504,7 +504,7 @@ class _GroupInfoScreenState extends State { ), _GroupInfoListTile( title: AppLocalizations.of(context).files, - icon: StreamSvgIcons.files, + icon: context.streamIcons.fileBend, iconSize: 32, iconPadding: 12, onTap: () { @@ -531,8 +531,8 @@ class _GroupInfoScreenState extends State { titleTextStyle: StreamChatTheme.of(context).textTheme.body, leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: StreamSvgIcon( - icon: StreamSvgIcons.userRemove, + child: Icon( + context.streamIcons.peopleRemove, size: 24, color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), @@ -551,8 +551,8 @@ class _GroupInfoScreenState extends State { okText: AppLocalizations.of(context).leave.toUpperCase(), question: AppLocalizations.of(context).leaveConversationAreYouSure, cancelText: AppLocalizations.of(context).cancel.toUpperCase(), - icon: StreamSvgIcon( - icon: StreamSvgIcons.userRemove, + icon: Icon( + context.streamIcons.peopleRemove, color: StreamChatTheme.of(context).colorTheme.accentError, ), ); @@ -613,8 +613,8 @@ class _GroupInfoScreenState extends State { children: [ Padding( padding: const EdgeInsets.all(24), - child: StreamSvgIcon( - icon: StreamSvgIcons.search, + child: Icon( + context.streamIcons.magnifyingGlassSearch, size: 96, color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), @@ -659,8 +659,8 @@ class _GroupInfoScreenState extends State { color: theme.colorTheme.textLowEmphasis, ), prefixIconConstraints: BoxConstraints.tight(const Size(40, 24)), - prefixIcon: StreamSvgIcon( - icon: StreamSvgIcons.search, + prefixIcon: Icon( + context.streamIcons.magnifyingGlassSearch, color: theme.colorTheme.textHighEmphasis, size: 24, ), @@ -682,7 +682,7 @@ class _GroupInfoScreenState extends State { ), ), IconButton( - icon: const StreamSvgIcon(icon: StreamSvgIcons.closeSmall), + icon: Icon(context.streamIcons.crossSmall), color: theme.colorTheme.textHighEmphasis, onPressed: () => Navigator.pop(context), ), @@ -735,10 +735,10 @@ class _GroupInfoScreenState extends State { if (StreamChat.of(context).currentUser!.id != user.id) _buildModalListTile( context, - StreamSvgIcon( + Icon( + context.streamIcons.people, color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, size: 24, - icon: StreamSvgIcons.user, ), AppLocalizations.of(context).viewInfo, () async { @@ -767,10 +767,10 @@ class _GroupInfoScreenState extends State { if (StreamChat.of(context).currentUser!.id != user.id) _buildModalListTile( context, - StreamSvgIcon( + Icon( + context.streamIcons.bubble3ChatMessage, color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, size: 24, - icon: StreamSvgIcons.message, ), AppLocalizations.of(context).message, () async { @@ -798,10 +798,10 @@ class _GroupInfoScreenState extends State { if (!channel.isDistinct && StreamChat.of(context).currentUser!.id != user.id && isUserAdmin) _buildModalListTile( context, - StreamSvgIcon( + Icon( + context.streamIcons.peopleRemove, color: StreamChatTheme.of(context).colorTheme.accentError, size: 24, - icon: StreamSvgIcons.userRemove, ), AppLocalizations.of(context).removeFromGroup, () async { @@ -823,10 +823,10 @@ class _GroupInfoScreenState extends State { ), _buildModalListTile( context, - StreamSvgIcon( + Icon( + context.streamIcons.crossSmall, color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, size: 24, - icon: StreamSvgIcons.closeSmall, ), AppLocalizations.of(context).cancel, () { @@ -965,7 +965,7 @@ class _GroupInfoToggle extends StatelessWidget { }); final String title; - final StreamSvgIconData icon; + final IconData icon; final Stream channelStream; final ValueNotifier localNotifier; final VoidCallback onTurnOff; @@ -985,8 +985,8 @@ class _GroupInfoToggle extends StatelessWidget { titleTextStyle: StreamChatTheme.of(context).textTheme.body, leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: StreamSvgIcon( - icon: icon, + child: Icon( + icon, size: 24, color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), @@ -1026,7 +1026,7 @@ class _GroupInfoListTile extends StatelessWidget { }); final String title; - final StreamSvgIconData icon; + final IconData icon; final double iconSize; final double iconPadding; final VoidCallback onTap; @@ -1039,14 +1039,14 @@ class _GroupInfoListTile extends StatelessWidget { titleTextStyle: StreamChatTheme.of(context).textTheme.body, leading: Padding( padding: EdgeInsets.symmetric(horizontal: iconPadding), - child: StreamSvgIcon( - icon: icon, + child: Icon( + icon, size: iconSize, color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), - trailing: StreamSvgIcon( - icon: StreamSvgIcons.right, + trailing: Icon( + context.streamIcons.chevronRight, color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), onTap: onTap, diff --git a/sample_app/lib/pages/new_chat_screen.dart b/sample_app/lib/pages/new_chat_screen.dart index 6ce60e5038..693c981833 100644 --- a/sample_app/lib/pages/new_chat_screen.dart +++ b/sample_app/lib/pages/new_chat_screen.dart @@ -223,7 +223,7 @@ class _NewChatScreenState extends State { showOnlineIndicator: false, ), ), - const StreamSvgIcon(icon: StreamSvgIcons.close), + Icon(context.streamIcons.crossMedium), ], ), ); @@ -248,10 +248,10 @@ class _NewChatScreenState extends State { children: [ StreamNeumorphicButton( child: Center( - child: StreamSvgIcon( + child: Icon( + context.streamIcons.users, color: StreamChatTheme.of(context).colorTheme.accentPrimary, size: 24, - icon: StreamSvgIcons.contacts, ), ), ), @@ -325,10 +325,10 @@ class _NewChatScreenState extends State { child: Center( child: Column( children: [ - const Padding( - padding: EdgeInsets.all(24), - child: StreamSvgIcon( - icon: StreamSvgIcons.search, + Padding( + padding: const EdgeInsets.all(24), + child: Icon( + context.streamIcons.magnifyingGlassSearch, size: 96, color: Colors.grey, ), diff --git a/sample_app/lib/pages/new_group_chat_screen.dart b/sample_app/lib/pages/new_group_chat_screen.dart index 9336b37ecd..fa4e9b844a 100644 --- a/sample_app/lib/pages/new_group_chat_screen.dart +++ b/sample_app/lib/pages/new_group_chat_screen.dart @@ -85,7 +85,7 @@ class _NewGroupChatScreenState extends State { if (state.users.isNotEmpty) IconButton( color: StreamChatTheme.of(context).colorTheme.accentPrimary, - icon: const StreamSvgIcon(icon: StreamSvgIcons.arrowRight), + icon: Icon(context.streamIcons.arrowRight), onPressed: () async { GoRouter.of(context).pushNamed( Routes.NEW_GROUP_CHAT_DETAILS.name, @@ -161,10 +161,10 @@ class _NewGroupChatScreenState extends State { color: StreamChatTheme.of(context).colorTheme.appBg, ), ), - child: StreamSvgIcon( + child: Icon( + context.streamIcons.crossMedium, color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, size: 24, - icon: StreamSvgIcons.close, ), ), ), @@ -238,8 +238,8 @@ class _NewGroupChatScreenState extends State { children: [ Padding( padding: const EdgeInsets.all(24), - child: StreamSvgIcon( - icon: StreamSvgIcons.search, + child: Icon( + context.streamIcons.magnifyingGlassSearch, size: 96, color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), diff --git a/sample_app/lib/pages/pinned_messages_screen.dart b/sample_app/lib/pages/pinned_messages_screen.dart index dcd4e47290..af299b826b 100644 --- a/sample_app/lib/pages/pinned_messages_screen.dart +++ b/sample_app/lib/pages/pinned_messages_screen.dart @@ -54,8 +54,8 @@ class _PinnedMessagesScreenState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamSvgIcon( - icon: StreamSvgIcons.pin, + Icon( + context.streamIcons.pin, size: 136, color: StreamChatTheme.of(context).colorTheme.disabled, ), diff --git a/sample_app/lib/pages/reminders_page.dart b/sample_app/lib/pages/reminders_page.dart index ca75dea89f..99426f30da 100644 --- a/sample_app/lib/pages/reminders_page.dart +++ b/sample_app/lib/pages/reminders_page.dart @@ -81,9 +81,9 @@ class _RemindersPageState extends State { children: [ CustomSlidableAction( backgroundColor: theme.colorTheme.inputBg, - child: StreamSvgIcon( + child: Icon( + context.streamIcons.editBig, size: 24, - icon: StreamSvgIcons.edit, color: theme.colorTheme.accentPrimary, ), onPressed: (_) async { @@ -105,9 +105,9 @@ class _RemindersPageState extends State { ), CustomSlidableAction( backgroundColor: theme.colorTheme.inputBg, - child: StreamSvgIcon( + child: Icon( + context.streamIcons.trashBin, size: 24, - icon: StreamSvgIcons.delete, color: theme.colorTheme.accentError, ), onPressed: (context) { diff --git a/sample_app/lib/pages/user_mentions_page.dart b/sample_app/lib/pages/user_mentions_page.dart index c4cb48fd35..f7eac3310c 100644 --- a/sample_app/lib/pages/user_mentions_page.dart +++ b/sample_app/lib/pages/user_mentions_page.dart @@ -41,8 +41,8 @@ class _UserMentionsPageState extends State { children: [ Padding( padding: const EdgeInsets.all(24), - child: StreamSvgIcon( - icon: StreamSvgIcons.mentions, + child: Icon( + context.streamIcons.at, size: 96, color: StreamChatTheme.of(context).colorTheme.disabled, ), diff --git a/sample_app/lib/widgets/channel_list.dart b/sample_app/lib/widgets/channel_list.dart index 319a4664c3..bde901e5df 100644 --- a/sample_app/lib/widgets/channel_list.dart +++ b/sample_app/lib/widgets/channel_list.dart @@ -170,8 +170,8 @@ class _ChannelListDefault extends StatelessWidget { if (canDeleteChannel) CustomSlidableAction( backgroundColor: backgroundColor, - child: StreamSvgIcon( - icon: StreamSvgIcons.delete, + child: Icon( + context.streamIcons.trashBin, color: chatTheme.colorTheme.accentError, ), onPressed: (_) async { @@ -181,8 +181,8 @@ class _ChannelListDefault extends StatelessWidget { question: 'Are you sure you want to delete this conversation?', okText: 'Delete', cancelText: 'Cancel', - icon: StreamSvgIcon( - icon: StreamSvgIcons.delete, + icon: Icon( + context.streamIcons.trashBin, color: chatTheme.colorTheme.accentError, ), ); @@ -210,8 +210,8 @@ class _ChannelListDefault extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( - icon: StreamSvgIcons.message, + emptyIcon: Icon( + context.streamIcons.bubble3ChatMessage, size: 148, color: StreamChatTheme.of(context).colorTheme.disabled, ), @@ -257,10 +257,10 @@ class _ChannelListSearch extends StatelessWidget { child: Center( child: Column( children: [ - const Padding( - padding: EdgeInsets.all(24), - child: StreamSvgIcon( - icon: StreamSvgIcons.search, + Padding( + padding: const EdgeInsets.all(24), + child: Icon( + context.streamIcons.magnifyingGlassSearch, size: 96, color: Colors.grey, ), diff --git a/sample_app/lib/widgets/chips_input_text_field.dart b/sample_app/lib/widgets/chips_input_text_field.dart index 8b29a15a52..e4a4ca4e41 100644 --- a/sample_app/lib/widgets/chips_input_text_field.dart +++ b/sample_app/lib/widgets/chips_input_text_field.dart @@ -122,13 +122,13 @@ class ChipInputTextFieldState extends State> { alignment: Alignment.bottomCenter, child: IconButton( icon: _chips.isEmpty - ? StreamSvgIcon( - icon: StreamSvgIcons.user, + ? Icon( + context.streamIcons.people, color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), size: 24, ) - : StreamSvgIcon( - icon: StreamSvgIcons.userAdd, + : Icon( + context.streamIcons.peopleAdd, color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), size: 24, ), diff --git a/sample_app/lib/widgets/location/location_picker_dialog.dart b/sample_app/lib/widgets/location/location_picker_dialog.dart index 2c8eaafb39..b352a6b528 100644 --- a/sample_app/lib/widgets/location/location_picker_dialog.dart +++ b/sample_app/lib/widgets/location/location_picker_dialog.dart @@ -306,9 +306,9 @@ class LocationPickerOptionItem extends StatelessWidget { ], ), ), - StreamSvgIcon( + Icon( + context.streamIcons.chevronRight, size: 24, - icon: StreamSvgIcons.right, color: colorTheme.textLowEmphasis, ), ], diff --git a/sample_app/lib/widgets/message_info_sheet.dart b/sample_app/lib/widgets/message_info_sheet.dart index fc9874e1e1..8c587b3610 100644 --- a/sample_app/lib/widgets/message_info_sheet.dart +++ b/sample_app/lib/widgets/message_info_sheet.dart @@ -158,7 +158,7 @@ class MessageInfoSheet extends StatelessWidget { ), IconButton( iconSize: 32, - icon: const StreamSvgIcon(icon: StreamSvgIcons.close), + icon: Icon(context.streamIcons.crossMedium), onPressed: Navigator.of(context).maybePop, color: colorTheme.textHighEmphasis, padding: const EdgeInsets.all(4), @@ -268,9 +268,9 @@ class _UserReadTile extends StatelessWidget { ), // Status icon - StreamSvgIcon( + Icon( + context.streamIcons.doupleCheckmark1Small, size: 18, - icon: StreamSvgIcons.checkAll, color: switch (isDelivered) { true => theme.colorTheme.textLowEmphasis, false => theme.colorTheme.accentPrimary, diff --git a/sample_app/lib/widgets/search_text_field.dart b/sample_app/lib/widgets/search_text_field.dart index 87eca779c1..8c6ae23f95 100644 --- a/sample_app/lib/widgets/search_text_field.dart +++ b/sample_app/lib/widgets/search_text_field.dart @@ -48,8 +48,8 @@ class SearchTextField extends StatelessWidget { left: 8, right: 8, ), - child: StreamSvgIcon( - icon: StreamSvgIcons.search, + child: Icon( + context.streamIcons.magnifyingGlassSearch, color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, size: 24, ), @@ -72,7 +72,7 @@ class SearchTextField extends StatelessWidget { child: IconButton( color: Colors.grey, padding: EdgeInsets.zero, - icon: const StreamSvgIcon(icon: StreamSvgIcons.closeSmall), + icon: Icon(context.streamIcons.crossSmall), splashRadius: 24, onPressed: () { if (controller!.text.isNotEmpty) { From 2e6932449960ab9f5e9dbcf112ca797b016b93c5 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 4 Mar 2026 13:29:52 +0100 Subject: [PATCH 13/33] don't use source files in tests (#2524) --- melos.yaml | 2 +- .../lib/src/icons/stream_svg_icon.dart | 19 ++++++++++++++++++- packages/stream_chat_flutter/pubspec.yaml | 2 +- .../voice_recording_attachment_test.dart | 5 ++--- .../indicators/sending_indicator_test.dart | 2 +- .../stream_audio_recorder_test.dart | 2 +- .../stream_message_send_button_test.dart | 2 +- .../message_actions_modal_test.dart | 2 +- .../moderated_message_actions_modal_test.dart | 2 +- ...oll_option_reorderable_list_view_test.dart | 2 +- 10 files changed, 28 insertions(+), 12 deletions(-) diff --git a/melos.yaml b/melos.yaml index 65aa486691..9a3792f5bf 100644 --- a/melos.yaml +++ b/melos.yaml @@ -95,7 +95,7 @@ command: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 490feb000a1b4a2f6baa56621dc8941363195742 + ref: ec7547a6832a8ec1164c7fd1cd2c2d87c7332fe4 path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 diff --git a/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.dart b/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.dart index fe8348db38..cc7f45e9cd 100644 --- a/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.dart +++ b/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; import 'package:svg_icon_widget/svg_icon_widget.dart'; part 'stream_svg_icon.g.dart'; @@ -14,7 +15,23 @@ typedef StreamSvgIconData = SvgIconData; @Deprecated('Use Icon(context.streamIcons.*) instead') class StreamSvgIcon extends StatelessWidget { /// Creates a [StreamSvgIcon]. - /// Replacement icons: + /// + /// Depcreated in favor of regular [Icon] widgets. + /// New icons can be replaced using the [StreamIcons] theme data. + /// + /// Previously: + /// + /// ```dart + /// StreamSvgIcon(icon: StreamSvgIcons.arrowRight) + /// ``` + /// + /// Replacement: + /// + /// ```dart + /// Icon(context.streamIcons.arrowRight) + /// ``` + /// + /// List of replacement icons: /// /// Exact / close matches: /// - StreamSvgIcons.arrowRight -> context.streamIcons.arrowRight diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index f36439211b..23ab500591 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 490feb000a1b4a2f6baa56621dc8941363195742 + ref: ec7547a6832a8ec1164c7fd1cd2c2d87c7332fe4 path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 diff --git a/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart b/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart index 16df41bef6..e511c030e5 100644 --- a/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart @@ -4,7 +4,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/src/audio/audio_playlist_state.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_core_flutter/src/theme/primitives/stream_icons.g.dart'; import 'package:svg_icon_widget/svg_icon_widget.dart'; import '../mocks.dart'; @@ -213,7 +212,7 @@ void main() { PlaybackSpeed speed, ValueChanged? onChangeSpeed, ) { - return const Icon(StreamIconData.iconCrossSmall); + return const Icon(Icons.close); } await tester.pumpWidget( @@ -227,7 +226,7 @@ void main() { ); // Verify custom trailing widget is rendered - expect(find.byIcon(StreamIconData.iconCrossSmall), findsOneWidget); + expect(find.byIcon(Icons.close), findsOneWidget); expect( find.byWidgetPredicate( (w) => w is SvgIcon && w.icon == StreamSvgIcons.filetypeAudioM4a, diff --git a/packages/stream_chat_flutter/test/src/indicators/sending_indicator_test.dart b/packages/stream_chat_flutter/test/src/indicators/sending_indicator_test.dart index d83bb09050..aaa620a197 100644 --- a/packages/stream_chat_flutter/test/src/indicators/sending_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/indicators/sending_indicator_test.dart @@ -2,7 +2,7 @@ import 'package:alchemist/alchemist.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_core_flutter/src/theme/primitives/stream_icons.g.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; import '../material_app_wrapper.dart'; void main() { diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart index 6b251ac9bf..4a37c491cf 100644 --- a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_core_flutter/src/theme/primitives/stream_icons.g.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; void main() { group('StreamAudioRecorderButton', () { diff --git a/packages/stream_chat_flutter/test/src/message_input/stream_message_send_button_test.dart b/packages/stream_chat_flutter/test/src/message_input/stream_message_send_button_test.dart index dacb9e4f9e..888c5ad636 100644 --- a/packages/stream_chat_flutter/test/src/message_input/stream_message_send_button_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/stream_message_send_button_test.dart @@ -6,7 +6,7 @@ import 'package:stream_chat_flutter/src/message_input/stream_message_input_icon_ import 'package:stream_chat_flutter/src/message_input/stream_message_send_button.dart'; import 'package:stream_chat_flutter/src/theme/message_input_theme.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; -import 'package:stream_core_flutter/src/theme/primitives/stream_icons.g.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; void main() { group('StreamMessageSendButton', () { diff --git a/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart index 27a49d0c93..6de1cc9a08 100644 --- a/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_core_flutter/src/theme/primitives/stream_icons.g.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; void main() { final message = Message( diff --git a/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart index 28d36b8295..01f92d525e 100644 --- a/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart @@ -4,7 +4,7 @@ import 'package:alchemist/alchemist.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_core_flutter/src/theme/primitives/stream_icons.g.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; void main() { final message = Message( diff --git a/packages/stream_chat_flutter/test/src/poll/creator/poll_option_reorderable_list_view_test.dart b/packages/stream_chat_flutter/test/src/poll/creator/poll_option_reorderable_list_view_test.dart index 73cdf99695..f1a65f47f0 100644 --- a/packages/stream_chat_flutter/test/src/poll/creator/poll_option_reorderable_list_view_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/creator/poll_option_reorderable_list_view_test.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/src/poll/creator/poll_option_reorderable_list_view.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_core_flutter/src/theme/primitives/stream_icons.g.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; void main() { for (final brightness in Brightness.values) { From ce639db91ba2121567d594f016e74f96b9b3e706 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 4 Mar 2026 15:57:48 +0100 Subject: [PATCH 14/33] Migrate message composer factories (#2525) * Migrate message composer factories * temporarily disabled flutter cache on workflows * Improve on builder factory * remove unused import --- .github/workflows/distribute_external.yml | 2 - .github/workflows/distribute_internal.yml | 3 - .github/workflows/legacy_version_analyze.yml | 1 - .github/workflows/stream_flutter_workflow.yml | 4 - .github/workflows/update_goldens.yml | 1 - melos.yaml | 2 +- .../message_composer/message_composer.dart | 1 + .../message_composer_component_props.dart | 245 ++++++++++++++++++ .../message_composer_extensions.dart | 37 +++ .../message_composer_factory.dart | 49 ---- .../message_composer_input_header.dart | 4 +- .../message_composer_input_leading.dart | 5 +- .../message_composer_input_trailing.dart | 4 +- .../message_composer_leading.dart | 6 +- .../message_composer_trailing.dart | 5 +- .../stream_chat_message_composer.dart | 77 +----- .../stream_chat_component_builders.dart | 24 ++ .../lib/stream_chat_flutter.dart | 4 + packages/stream_chat_flutter/pubspec.yaml | 2 +- sample_app/lib/app.dart | 105 ++++---- 20 files changed, 392 insertions(+), 189 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_component_props.dart create mode 100644 packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_extensions.dart delete mode 100644 packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_factory.dart create mode 100644 packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart diff --git a/.github/workflows/distribute_external.yml b/.github/workflows/distribute_external.yml index dde97d8fdd..69b3615a4b 100644 --- a/.github/workflows/distribute_external.yml +++ b/.github/workflows/distribute_external.yml @@ -71,7 +71,6 @@ jobs: with: flutter-version: ${{ env.FLUTTER_VERSION }} channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - name: Setup Ruby @@ -109,7 +108,6 @@ jobs: with: flutter-version: ${{ env.FLUTTER_VERSION }} channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - name: Setup Ruby diff --git a/.github/workflows/distribute_internal.yml b/.github/workflows/distribute_internal.yml index f1ad493bee..ce986729e3 100644 --- a/.github/workflows/distribute_internal.yml +++ b/.github/workflows/distribute_internal.yml @@ -75,7 +75,6 @@ jobs: with: flutter-version: ${{ env.FLUTTER_VERSION }} channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - name: "Install Tools" @@ -119,7 +118,6 @@ jobs: with: flutter-version: ${{ env.FLUTTER_VERSION }} channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - name: "Install Tools" @@ -167,7 +165,6 @@ jobs: with: flutter-version: ${{ env.FLUTTER_VERSION }} channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - name: "Install Tools" diff --git a/.github/workflows/legacy_version_analyze.yml b/.github/workflows/legacy_version_analyze.yml index 1a2e61233e..443dcc7560 100644 --- a/.github/workflows/legacy_version_analyze.yml +++ b/.github/workflows/legacy_version_analyze.yml @@ -44,7 +44,6 @@ jobs: with: flutter-version: ${{ env.flutter_version }} channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - name: 📊 Analyze and test packages diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 5debb88dba..0cd8433abb 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -38,7 +38,6 @@ jobs: with: flutter-version: ${{ env.flutter_version }} channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - name: "Install Tools" run: | @@ -67,7 +66,6 @@ jobs: with: flutter-version: ${{ env.flutter_version }} channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - name: "Install Tools" run: | @@ -94,7 +92,6 @@ jobs: with: flutter-version: ${{ env.flutter_version }} channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} # This step is needed due to https://github.com/actions/runner-images/issues/11279 - name: Install SQLite3 @@ -168,7 +165,6 @@ jobs: with: flutter-version: ${{ env.flutter_version }} channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - name: "Install Tools" run: flutter pub global activate melos diff --git a/.github/workflows/update_goldens.yml b/.github/workflows/update_goldens.yml index af6a6cff61..c81affdc59 100644 --- a/.github/workflows/update_goldens.yml +++ b/.github/workflows/update_goldens.yml @@ -16,7 +16,6 @@ jobs: with: flutter-version: "3.x" channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - name: 📦 Install Tools diff --git a/melos.yaml b/melos.yaml index 9a3792f5bf..4894e2382f 100644 --- a/melos.yaml +++ b/melos.yaml @@ -95,7 +95,7 @@ command: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: ec7547a6832a8ec1164c7fd1cd2c2d87c7332fe4 + ref: 07f2357a4f1266784c26fde3430f0bdff43bbee8 path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer.dart index 687278691a..d7370d4d6e 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer.dart @@ -1 +1,2 @@ +export 'message_composer_component_props.dart'; export 'stream_chat_message_composer.dart'; diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_component_props.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_component_props.dart new file mode 100644 index 0000000000..8bdf815072 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_component_props.dart @@ -0,0 +1,245 @@ +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; + +/// Properties to build any of the sub-components. +/// These properties are all the same, so features such as 'add attachment', +/// can be added to any of the sub-components. +class MessageComposerComponentProps { + /// Creates a new instance of [MessageComposerComponentProps]. + /// [controller] is the controller for the message composer component. + /// [isFloating] is whether the message composer is floating. + /// [message] is the message for the message composer component. + /// [onSendPressed] is the callback for when the send button is pressed. + /// [onMicrophonePressed] is the callback for when the microphone button is pressed. + /// [onAttachmentButtonPressed] is the callback for when the attachment button is pressed. + /// [focusNode] is the focus node for the message composer component. + /// [currentUserId] is the current user id. + const MessageComposerComponentProps({ + required this.controller, + this.isFloating = false, + this.message, + required this.onSendPressed, + this.voiceRecordingCallback, + this.onAttachmentButtonPressed, + this.focusNode, + this.currentUserId, + required this.audioRecorderState, + }); + + /// The controller for the message composer component. + final StreamMessageInputController controller; + + /// Whether the message composer is floating. + final bool isFloating; + + /// The message for the message composer component. + final Message? message; + + /// The callback for when the send button is pressed. + final VoidCallback onSendPressed; + + /// The callback for when the microphone button is pressed. + final core.VoiceRecordingCallback? voiceRecordingCallback; + + /// The callback for when the attachment button is pressed. + final VoidCallback? onAttachmentButtonPressed; + + /// The focus node for the message composer component. + final FocusNode? focusNode; + + /// The current user id. + final String? currentUserId; + + /// Whether the audio recording flow is active. + final AudioRecorderState audioRecorderState; + + /// Whether the audio recording flow is active. + bool get isAudioRecordingFlowActive => audioRecorderState is RecordStateRecording || isAudioRecordingFlowStopped; + + /// Whether the audio recording flow is locked. + bool get isAudioRecordingFlowLocked => audioRecorderState is RecordStateRecordingLocked; + + /// Whether the audio recording flow is stopped. + bool get isAudioRecordingFlowStopped => audioRecorderState is RecordStateStopped; +} + +/// Properties for building the leading component of the message composer. +class MessageComposerLeadingProps extends MessageComposerComponentProps { + const MessageComposerLeadingProps._({ + required super.controller, + required super.isFloating, + required super.message, + required super.onSendPressed, + required super.voiceRecordingCallback, + required super.onAttachmentButtonPressed, + required super.focusNode, + required super.currentUserId, + required super.audioRecorderState, + }) : super(); + + /// Creates a new instance of [MessageComposerLeadingProps] from a [MessageComposerComponentProps]. + factory MessageComposerLeadingProps.from(MessageComposerComponentProps props) { + return MessageComposerLeadingProps._( + controller: props.controller, + isFloating: props.isFloating, + message: props.message, + onSendPressed: props.onSendPressed, + voiceRecordingCallback: props.voiceRecordingCallback, + onAttachmentButtonPressed: props.onAttachmentButtonPressed, + focusNode: props.focusNode, + currentUserId: props.currentUserId, + audioRecorderState: props.audioRecorderState, + ); + } +} + +/// Properties for building the trailing component of the message composer. +class MessageComposerTrailingProps extends MessageComposerComponentProps { + const MessageComposerTrailingProps._({ + required super.controller, + required super.isFloating, + required super.message, + required super.onSendPressed, + required super.voiceRecordingCallback, + required super.onAttachmentButtonPressed, + required super.focusNode, + required super.currentUserId, + required super.audioRecorderState, + }) : super(); + + /// Creates a new instance of [MessageComposerTrailingProps] from a [MessageComposerComponentProps]. + factory MessageComposerTrailingProps.from(MessageComposerComponentProps props) { + return MessageComposerTrailingProps._( + controller: props.controller, + isFloating: props.isFloating, + message: props.message, + onSendPressed: props.onSendPressed, + voiceRecordingCallback: props.voiceRecordingCallback, + onAttachmentButtonPressed: props.onAttachmentButtonPressed, + focusNode: props.focusNode, + currentUserId: props.currentUserId, + audioRecorderState: props.audioRecorderState, + ); + } +} + +/// Properties for building the input component of the message composer. +class MessageComposerInputProps extends MessageComposerComponentProps { + const MessageComposerInputProps._({ + required super.controller, + required super.isFloating, + required super.message, + required super.onSendPressed, + required super.voiceRecordingCallback, + required super.onAttachmentButtonPressed, + required super.focusNode, + required super.currentUserId, + required super.audioRecorderState, + }) : super(); + + /// Creates a new instance of [MessageComposerInputProps] from a [MessageComposerComponentProps]. + factory MessageComposerInputProps.from(MessageComposerComponentProps props) { + return MessageComposerInputProps._( + controller: props.controller, + isFloating: props.isFloating, + message: props.message, + onSendPressed: props.onSendPressed, + voiceRecordingCallback: props.voiceRecordingCallback, + onAttachmentButtonPressed: props.onAttachmentButtonPressed, + focusNode: props.focusNode, + currentUserId: props.currentUserId, + audioRecorderState: props.audioRecorderState, + ); + } +} + +/// Properties for building the input leading component of the message composer. +class MessageComposerInputLeadingProps extends MessageComposerComponentProps { + const MessageComposerInputLeadingProps._({ + required super.controller, + required super.isFloating, + required super.message, + required super.onSendPressed, + required super.voiceRecordingCallback, + required super.onAttachmentButtonPressed, + required super.focusNode, + required super.currentUserId, + required super.audioRecorderState, + }) : super(); + + /// Creates a new instance of [MessageComposerInputLeadingProps] from a [MessageComposerComponentProps]. + factory MessageComposerInputLeadingProps.from(MessageComposerComponentProps props) { + return MessageComposerInputLeadingProps._( + controller: props.controller, + isFloating: props.isFloating, + message: props.message, + onSendPressed: props.onSendPressed, + voiceRecordingCallback: props.voiceRecordingCallback, + onAttachmentButtonPressed: props.onAttachmentButtonPressed, + focusNode: props.focusNode, + currentUserId: props.currentUserId, + audioRecorderState: props.audioRecorderState, + ); + } +} + +/// Properties for building the input header component of the message composer. +class MessageComposerInputHeaderProps extends MessageComposerComponentProps { + const MessageComposerInputHeaderProps._({ + required super.controller, + required super.isFloating, + required super.message, + required super.onSendPressed, + required super.voiceRecordingCallback, + required super.onAttachmentButtonPressed, + required super.focusNode, + required super.currentUserId, + required super.audioRecorderState, + }) : super(); + + /// Creates a new instance of [MessageComposerInputHeaderProps] from a [MessageComposerComponentProps]. + factory MessageComposerInputHeaderProps.from(MessageComposerComponentProps props) { + return MessageComposerInputHeaderProps._( + controller: props.controller, + isFloating: props.isFloating, + message: props.message, + onSendPressed: props.onSendPressed, + voiceRecordingCallback: props.voiceRecordingCallback, + onAttachmentButtonPressed: props.onAttachmentButtonPressed, + focusNode: props.focusNode, + currentUserId: props.currentUserId, + audioRecorderState: props.audioRecorderState, + ); + } +} + +/// Properties for building the input trailing component of the message composer. +class MessageComposerInputTrailingProps extends MessageComposerComponentProps { + const MessageComposerInputTrailingProps._({ + required super.controller, + required super.isFloating, + required super.message, + required super.onSendPressed, + required super.voiceRecordingCallback, + required super.onAttachmentButtonPressed, + required super.focusNode, + required super.currentUserId, + required super.audioRecorderState, + }) : super(); + + /// Creates a new instance of [MessageComposerInputTrailingProps] from a [MessageComposerComponentProps]. + factory MessageComposerInputTrailingProps.from(MessageComposerComponentProps props) { + return MessageComposerInputTrailingProps._( + controller: props.controller, + isFloating: props.isFloating, + message: props.message, + onSendPressed: props.onSendPressed, + voiceRecordingCallback: props.voiceRecordingCallback, + onAttachmentButtonPressed: props.onAttachmentButtonPressed, + focusNode: props.focusNode, + currentUserId: props.currentUserId, + audioRecorderState: props.audioRecorderState, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_extensions.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_extensions.dart new file mode 100644 index 0000000000..cb070faf71 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_extensions.dart @@ -0,0 +1,37 @@ +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Helper extensions for internal use only. +extension MessageComposerBuilderExtensions on BuildContext { + StreamComponentBuilders get _factory => StreamComponentFactory.of( + this, + ); + + /// The builder for the message composer component. + StreamComponentBuilder? get messageComposerBuilder => + _factory.extension(); + + /// The builder for the message composer leading component. + StreamComponentBuilder? get messageComposerLeadingBuilder => + _factory.extension(); + + /// The builder for the message composer trailing component. + StreamComponentBuilder? get messageComposerTrailingBuilder => + _factory.extension(); + + /// The builder for the message composer input component. + StreamComponentBuilder? get messageComposerInputBuilder => + _factory.extension(); + + /// The builder for the message composer input leading component. + StreamComponentBuilder? get messageComposerInputLeadingBuilder => + _factory.extension(); + + /// The builder for the message composer input header component. + StreamComponentBuilder? get messageComposerInputHeaderBuilder => + _factory.extension(); + + /// The builder for the message composer input trailing component. + StreamComponentBuilder? get messageComposerInputTrailingBuilder => + _factory.extension(); +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_factory.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_factory.dart deleted file mode 100644 index 281c525c43..0000000000 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_factory.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/stream_chat_message_composer.dart'; -import 'package:stream_core_flutter/stream_core_flutter.dart' show StreamComponentBuilder; - -/// A factory for building the message composer components. -class StreamMessageComposerFactory { - /// Creates a new instance of [StreamMessageComposerFactory]. - const StreamMessageComposerFactory({ - this.messageComposer, - this.leading, - this.trailing, - this.input, - this.inputLeading, - this.inputHeader, - this.inputTrailing, - }); - - /// The main message composer component. Provide a builder if you want full control over the message composer. - final StreamComponentBuilder? messageComposer; - - /// The leading component of the message composer. This is shown before the text input. - /// By default this contains a button to add attachments. - final StreamComponentBuilder? leading; - - /// The trailing component of the message composer. This is shown after the text input. - /// By default this area is empty. - final StreamComponentBuilder? trailing; - - /// The input component of the message composer. - final StreamComponentBuilder? input; - - /// The leading component of the input area. - /// This is shown before the text input, but inside of the input area. - /// By default this area is empty. - final StreamComponentBuilder? inputLeading; - - /// The header component of the input area. This is shown above the text input. - /// By default it shows the quoted message, attachments and OG attachment previews. - final StreamComponentBuilder? inputHeader; - - /// The trailing component of the input area. This is shown after the text input. - /// By default it shows the send button and the microphone button. - final StreamComponentBuilder? inputTrailing; - - /// placeholder for real implementation - static StreamMessageComposerFactory? maybeOf(BuildContext context) { - return const StreamMessageComposerFactory(); - } -} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart index 2ab5ad67eb..9000377d6d 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart @@ -1,7 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/message_composer_factory.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_extensions.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart'; @@ -17,7 +17,7 @@ class StreamMessageComposerInputHeader extends StatelessWidget { @override Widget build(BuildContext context) { - return StreamMessageComposerFactory.maybeOf(context)?.inputHeader?.call(context, props) ?? + return context.messageComposerInputHeaderBuilder?.call(context, MessageComposerInputHeaderProps.from(props)) ?? _DefaultStreamMessageComposerInputHeader(props: props); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart index 16d291893d..c959a44c0e 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart @@ -1,5 +1,5 @@ import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/message_composer_factory.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_extensions.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// A widget that shows the input leading of the message composer. @@ -14,6 +14,7 @@ class StreamMessageComposerInputLeading extends StatelessWidget { @override Widget build(BuildContext context) { - return StreamMessageComposerFactory.maybeOf(context)?.inputLeading?.call(context, props) ?? const SizedBox.shrink(); + return context.messageComposerInputLeadingBuilder?.call(context, MessageComposerInputLeadingProps.from(props)) ?? + const SizedBox.shrink(); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart index 6830e3cf07..01763810ec 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/message_composer_factory.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_extensions.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart'; @@ -17,7 +17,7 @@ class StreamMessageComposerInputTrailing extends StatelessWidget { @override Widget build(BuildContext context) { - return StreamMessageComposerFactory.maybeOf(context)?.inputTrailing?.call(context, props) ?? + return context.messageComposerInputTrailingBuilder?.call(context, MessageComposerInputTrailingProps.from(props)) ?? DefaultStreamMessageComposerInputTrailing(props: props); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart index 67408526f8..13b66ecb2f 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/message_composer_factory.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/stream_chat_message_composer.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_extensions.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart'; /// A widget that shows the leading of the message composer. @@ -16,7 +16,7 @@ class StreamMessageComposerLeading extends StatelessWidget { @override Widget build(BuildContext context) { - return StreamMessageComposerFactory.maybeOf(context)?.leading?.call(context, props) ?? + return context.messageComposerLeadingBuilder?.call(context, MessageComposerLeadingProps.from(props)) ?? _DefaultStreamMessageComposerLeading(props: props); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart index 0d60105250..c60a315a0e 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart @@ -1,5 +1,5 @@ import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/message_composer_factory.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_extensions.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// A widget that shows the trailing of the message composer. @@ -15,6 +15,7 @@ class StreamMessageComposerTrailing extends StatelessWidget { @override Widget build(BuildContext context) { - return StreamMessageComposerFactory.maybeOf(context)?.trailing?.call(context, props) ?? const SizedBox.shrink(); + return context.messageComposerTrailingBuilder?.call(context, MessageComposerTrailingProps.from(props)) ?? + const SizedBox.shrink(); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart index 117d25bbcf..7110cf02bf 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/message_composer_factory.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_extensions.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_input_header.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_input_leading.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_input_trailing.dart'; @@ -24,7 +24,7 @@ class StreamChatMessageComposer extends StatefulWidget { /// [placeholder] is the placeholder text of the message composer. StreamChatMessageComposer({ super.key, - this.controller, + StreamMessageInputController? controller, required VoidCallback onSendPressed, VoidCallback? onAttachmentButtonPressed, FocusNode? focusNode, @@ -34,6 +34,7 @@ class StreamChatMessageComposer extends StatefulWidget { bool sendVoiceRecordingAutomatically = false, AudioRecorderFeedback feedback = const AudioRecorderFeedback(), }) : props = MessageComposerProps( + controller: controller, isFloating: false, message: null, onSendPressed: onSendPressed, @@ -47,7 +48,7 @@ class StreamChatMessageComposer extends StatefulWidget { ); /// The controller for the message composer. - final StreamMessageInputController? controller; + StreamMessageInputController? get controller => props.controller; /// The properties for the message composer. final MessageComposerProps props; @@ -92,8 +93,8 @@ class _StreamChatMessageComposerState extends State { @override Widget build(BuildContext context) { - if (StreamMessageComposerFactory.maybeOf(context)?.messageComposer case final messageComposerFactory?) { - return messageComposerFactory(context, widget.props); + if (context.messageComposerBuilder?.call(context, widget.props) case final messageComposer?) { + return messageComposer; } final audioRecorderController = widget.props.audioRecorderController; @@ -162,6 +163,7 @@ class MessageComposerProps { /// [focusNode] is the focus node for the message composer. /// [currentUserId] is the current user id. const MessageComposerProps({ + this.controller, this.isFloating = false, this.message, this.placeholder = '', @@ -174,6 +176,9 @@ class MessageComposerProps { this.feedback = const AudioRecorderFeedback(), }); + /// The controller for the message composer. + final StreamMessageInputController? controller; + /// Whether the message composer is floating. final bool isFloating; @@ -208,68 +213,6 @@ class MessageComposerProps { final AudioRecorderFeedback feedback; } -/// Properties to build any of the sub-components. -/// These properties are all the same, so features such as 'add attachment', -/// can be added to any of the sub-components. -class MessageComposerComponentProps { - /// Creates a new instance of [MessageComposerComponentProps]. - /// [controller] is the controller for the message composer component. - /// [isFloating] is whether the message composer is floating. - /// [message] is the message for the message composer component. - /// [onSendPressed] is the callback for when the send button is pressed. - /// [onMicrophonePressed] is the callback for when the microphone button is pressed. - /// [onAttachmentButtonPressed] is the callback for when the attachment button is pressed. - /// [focusNode] is the focus node for the message composer component. - /// [currentUserId] is the current user id. - const MessageComposerComponentProps({ - required this.controller, - this.isFloating = false, - this.message, - required this.onSendPressed, - this.voiceRecordingCallback, - this.onAttachmentButtonPressed, - this.focusNode, - this.currentUserId, - this.audioRecorderState = const RecordStateIdle(), - }); - - /// The controller for the message composer component. - final StreamMessageInputController controller; - - /// Whether the message composer is floating. - final bool isFloating; - - /// The message for the message composer component. - final Message? message; - - /// The callback for when the send button is pressed. - final VoidCallback onSendPressed; - - /// The callback for when the microphone button is pressed. - final core.VoiceRecordingCallback? voiceRecordingCallback; - - /// The callback for when the attachment button is pressed. - final VoidCallback? onAttachmentButtonPressed; - - /// The focus node for the message composer component. - final FocusNode? focusNode; - - /// The current user id. - final String? currentUserId; - - /// Whether the audio recording flow is active. - final AudioRecorderState audioRecorderState; - - /// Whether the audio recording flow is active. - bool get isAudioRecordingFlowActive => audioRecorderState is RecordStateRecording || isAudioRecordingFlowStopped; - - /// Whether the audio recording flow is locked. - bool get isAudioRecordingFlowLocked => audioRecorderState is RecordStateRecordingLocked; - - /// Whether the audio recording flow is stopped. - bool get isAudioRecordingFlowStopped => audioRecorderState is RecordStateStopped; -} - extension on StreamAudioRecorderController { bool get isRecording => value is RecordStateRecording; bool get isLocked => isRecording && value is! RecordStateRecordingHold; diff --git a/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart b/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart new file mode 100644 index 0000000000..3d925516de --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart @@ -0,0 +1,24 @@ +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Builds the list of component builders for the stream chat components. +Iterable> streamChatComponentBuilders({ + StreamComponentBuilder? messageComposer, + StreamComponentBuilder? messageComposerLeading, + StreamComponentBuilder? messageComposerTrailing, + StreamComponentBuilder? messageComposerInput, + StreamComponentBuilder? messageComposerInputLeading, + StreamComponentBuilder? messageComposerInputHeader, + StreamComponentBuilder? messageComposerInputTrailing, +}) { + final builders = [ + if (messageComposer != null) StreamComponentBuilderExtension(builder: messageComposer), + if (messageComposerLeading != null) StreamComponentBuilderExtension(builder: messageComposerLeading), + if (messageComposerTrailing != null) StreamComponentBuilderExtension(builder: messageComposerTrailing), + if (messageComposerInput != null) StreamComponentBuilderExtension(builder: messageComposerInput), + if (messageComposerInputLeading != null) StreamComponentBuilderExtension(builder: messageComposerInputLeading), + if (messageComposerInputHeader != null) StreamComponentBuilderExtension(builder: messageComposerInputHeader), + if (messageComposerInputTrailing != null) StreamComponentBuilderExtension(builder: messageComposerInputTrailing), + ]; + + return builders; +} diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 248d646b99..724e02ec00 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -11,6 +11,9 @@ export 'package:stream_core_flutter/stream_core_flutter.dart' StreamIcons, StreamThemeExtension, StreamComponentFactory, + StreamComponentBuilder, + StreamComponentBuilders, + StreamComponentBuilderExtension, StreamContextMenu, StreamContextMenuAction, StreamContextMenuSeparator, @@ -61,6 +64,7 @@ export 'src/components/avatar/stream_user_avatar.dart'; export 'src/components/avatar/stream_user_avatar_group.dart'; export 'src/components/avatar/stream_user_avatar_stack.dart'; export 'src/components/message_composer/message_composer.dart'; +export 'src/components/stream_chat_component_builders.dart'; // endregion export 'src/fullscreen_media/full_screen_media.dart'; export 'src/fullscreen_media/full_screen_media_builder.dart'; diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 23ab500591..43741c9ee8 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: ec7547a6832a8ec1164c7fd1cd2c2d87c7332fe4 + ref: 07f2357a4f1266784c26fde3430f0bdff43bbee8 path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 diff --git a/sample_app/lib/app.dart b/sample_app/lib/app.dart index 9acf94df26..d37248f6a1 100644 --- a/sample_app/lib/app.dart +++ b/sample_app/lib/app.dart @@ -469,59 +469,66 @@ class _StreamChatSampleAppState extends State @override Widget build(BuildContext context) { - return Stack( - alignment: Alignment.center, - children: [ - if (_initNotifier.initData != null) - ChangeNotifierProvider.value( - value: _initNotifier, - builder: (context, child) => Builder( - builder: (context) { - context.watch(); // rebuild on change - return PreferenceBuilder( - preference: _initNotifier.initData!.preferences.getInt( - 'theme', - defaultValue: 0, - ), - builder: (context, snapshot) => MaterialApp.router( - theme: ThemeData( - brightness: .light, - extensions: [StreamTheme.light()], + return StreamComponentFactory( + builders: StreamComponentBuilders( + extensions: streamChatComponentBuilders( + /// Add your custom component builders here. + ), + ), + child: Stack( + alignment: Alignment.center, + children: [ + if (_initNotifier.initData != null) + ChangeNotifierProvider.value( + value: _initNotifier, + builder: (context, child) => Builder( + builder: (context) { + context.watch(); // rebuild on change + return PreferenceBuilder( + preference: _initNotifier.initData!.preferences.getInt( + 'theme', + defaultValue: 0, ), - darkTheme: ThemeData( - brightness: .dark, - extensions: [StreamTheme.dark()], - ), - themeMode: const { - -1: ThemeMode.dark, - 0: ThemeMode.system, - 1: ThemeMode.light, - }[snapshot], - supportedLocales: const [ - Locale('en'), - Locale('it'), - ], - localizationsDelegates: const [ - AppLocalizationsDelegate(), - GlobalStreamChatLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ], - builder: (context, child) => StreamChat( - client: _initNotifier.initData!.client, - streamChatConfigData: StreamChatConfigurationData( - draftMessagesEnabled: false, + builder: (context, snapshot) => MaterialApp.router( + theme: ThemeData( + brightness: .light, + extensions: [StreamTheme.light()], + ), + darkTheme: ThemeData( + brightness: .dark, + extensions: [StreamTheme.dark()], + ), + themeMode: const { + -1: ThemeMode.dark, + 0: ThemeMode.system, + 1: ThemeMode.light, + }[snapshot], + supportedLocales: const [ + Locale('en'), + Locale('it'), + ], + localizationsDelegates: const [ + AppLocalizationsDelegate(), + GlobalStreamChatLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + builder: (context, child) => StreamChat( + client: _initNotifier.initData!.client, + streamChatConfigData: StreamChatConfigurationData( + draftMessagesEnabled: false, + ), + child: child, ), - child: child, + routerConfig: _setupRouter(), ), - routerConfig: _setupRouter(), - ), - ); - }, + ); + }, + ), ), - ), - if (!animationCompleted) buildAnimation(), - ], + if (!animationCompleted) buildAnimation(), + ], + ), ); } } From 026f826126ce7172cc621a15daa52cb4277c7bb4 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Thu, 5 Mar 2026 12:29:53 +0100 Subject: [PATCH 15/33] feat(ui): channel list item (#2522) * update avatars * Use channel list item in sdk * improve muted state * improve channel avatars * change to git dependency * chore: Update Goldens * Update channel name and list tile * chore: Update Goldens * remove unused properties * revert change on location marker * rename listitem and move items from core * Add factory builder * move props construction * update theming * Add migration docs * Fix analysis issues * Simplify the builder extensions * Fix test --------- Co-authored-by: renefloor <15101411+renefloor@users.noreply.github.com> --- melos.yaml | 4 +- migrations/redesign/README.md | 78 ++ migrations/redesign/channel_list_item.md | 270 ++++++ .../example/lib/tutorial_part_3.dart | 6 +- .../lib/src/channel/channel_list_header.dart | 4 +- .../lib/src/channel/stream_channel_name.dart | 29 +- .../avatar/stream_channel_avatar.dart | 19 +- .../components/avatar/stream_user_avatar.dart | 4 +- .../message_composer_extensions.dart | 37 - .../message_composer_input_header.dart | 6 +- .../message_composer_input_leading.dart | 6 +- .../message_composer_input_trailing.dart | 6 +- .../message_composer_leading.dart | 6 +- .../message_composer_trailing.dart | 6 +- .../stream_chat_message_composer.dart | 3 +- .../stream_chat_component_builders.dart | 9 + .../lib/src/indicators/typing_indicator.dart | 13 +- .../stream_channel_list_item.dart | 788 ++++++++++++++++++ .../stream_channel_list_tile.dart | 441 ---------- .../stream_channel_list_view.dart | 10 +- .../lib/src/theme/channel_preview_theme.dart | 3 + .../theme/stream_channel_list_item_theme.dart | 144 ++++ ...tream_channel_list_item_theme.g.theme.dart | 127 +++ .../lib/src/theme/stream_chat_theme.dart | 10 + .../lib/src/theme/themes.dart | 1 + .../lib/stream_chat_flutter.dart | 3 +- packages/stream_chat_flutter/pubspec.yaml | 3 +- .../test/src/channel/channel_image_test.dart | 2 + .../stream_message_reactions_modal_dark.png | Bin 9208 -> 8707 bytes .../stream_message_reactions_modal_light.png | Bin 11490 -> 10915 bytes ..._message_reactions_modal_reversed_dark.png | Bin 9259 -> 8753 bytes ...message_reactions_modal_reversed_light.png | Bin 11397 -> 10801 bytes 32 files changed, 1514 insertions(+), 524 deletions(-) create mode 100644 migrations/redesign/channel_list_item.md delete mode 100644 packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_extensions.dart create mode 100644 packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart delete mode 100644 packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart create mode 100644 packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.dart create mode 100644 packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.g.theme.dart diff --git a/melos.yaml b/melos.yaml index 4894e2382f..5eb60b46ea 100644 --- a/melos.yaml +++ b/melos.yaml @@ -93,9 +93,9 @@ command: svg_icon_widget: ^0.0.1 # TODO: Replace with hosted version before merging PR stream_core_flutter: - git: + git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 07f2357a4f1266784c26fde3430f0bdff43bbee8 + ref: 77f66669da436947e7c146f90b64b825351cd6ef path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 diff --git a/migrations/redesign/README.md b/migrations/redesign/README.md index 3f84bfa4ef..97ed8364da 100644 --- a/migrations/redesign/README.md +++ b/migrations/redesign/README.md @@ -39,11 +39,89 @@ MaterialApp( You can also use the convenience factories `StreamTheme.light()` or `StreamTheme.dark()` as a starting point. + +## Component factories + +In the redesigned components we don't use builders in the constructors anymore, but have a centralized component factory. +The component factory contains product agnotic component builders, such as the button and the avatar, and also product specific component builders, such as the channel list item. +You can supply your component factory at any point in the widget tree, but you would usually wrap your full app around it. + +An example of a component factory with custom buttons and a custom channel list item: + +```dart +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return StreamComponentFactory( + builders: StreamComponentBuilders( + button: (context, props) => switch (props.type) { + StreamButtonType.solid => ElevatedButton( + onPressed: props.onTap, + child: Text(props.label ?? ''), + ), + StreamButtonType.outline => OutlinedButton(onPressed: props.onTap, child: Text(props.label ?? '')), + StreamButtonType.ghost => TextButton(onPressed: props.onTap, child: Text(props.label ?? '')), + }, + extensions: streamChatComponentBuilders( + channelListItem: (context, props) => StreamChannelListTile( + title: Text(props.channel.name ?? ''), + avatar: StreamChannelAvatar(channel: props.channel), + onTap: props.onTap, + onLongPress: props.onLongPress, + selected: props.selected, + ), + ), + ), + child: ... + ); + } +} +``` + +You should make the builder themselves as simple as possible by extracting this into separate widgets, such as this: + +```dart +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return StreamComponentFactory( + builders: StreamComponentBuilders( + button: (context, props) => MyCustomButton(props: props), + ), + child: ... + ); + } +} + +class MyCustomButton extends StatelessWidget { + const MyCustomButton({super.key, required this.props}); + + final StreamButtonProps props; + + @override + Widget build(BuildContext context) { + return switch (props.type) { + StreamButtonType.solid => ElevatedButton( + onPressed: props.onTap, + child: Text(props.label ?? ''), + ), + StreamButtonType.outline => OutlinedButton(onPressed: props.onTap, child: Text(props.label ?? '')), + StreamButtonType.ghost => TextButton(onPressed: props.onTap, child: Text(props.label ?? '')), + }; + } +} +``` + ## Components | Component | Migration Guide | |-----------|-----------------| | Stream Avatar | [stream_avatar.md](stream_avatar.md) | +| Channel List Item | [channel_list_item.md](channel_list_item.md) | | Message Actions | [message_actions.md](message_actions.md) | | Reaction Picker / Reactions | [reaction_picker.md](reaction_picker.md) | diff --git a/migrations/redesign/channel_list_item.md b/migrations/redesign/channel_list_item.md new file mode 100644 index 0000000000..30d236b624 --- /dev/null +++ b/migrations/redesign/channel_list_item.md @@ -0,0 +1,270 @@ +# Channel List Item Migration Guide + +This guide covers the migration for the redesigned channel list item components in Stream Chat Flutter SDK. + +--- + +## Table of Contents + +- [Quick Reference](#quick-reference) +- [StreamChannelListTile → StreamChannelListItem](#streamchannellisttile--streamchannellistitem) +- [Customizing Slots](#customizing-slots) +- [Low-level Presentational Component](#low-level-presentational-component) +- [Theme Migration](#theme-migration) +- [Migration Checklist](#migration-checklist) + +--- + +## Quick Reference + +| Old | New | +|-----|-----| +| `StreamChannelListTile` | `StreamChannelListItem` | +| Constructor props: `leading`, `title`, `subtitle`, `trailing` | `StreamChannelListItemProps` (via `StreamComponentFactory`) | +| `tileColor`, `visualDensity`, `contentPadding` | Removed — use `StreamChannelListItemThemeData` | +| `selectedTileColor` | Removed — use `StreamChannelListItemThemeData.backgroundColor` | +| `unreadIndicatorBuilder` | Removed | +| `StreamChannelPreviewThemeData` | `StreamChannelListItemThemeData` | +| `StreamChannelPreviewTheme.of(context)` | `StreamChannelListItemTheme.of(context)` | +| `StreamChatThemeData.channelPreviewTheme` | `StreamChatThemeData.channelListItemTheme` | + +--- + +## StreamChannelListTile → StreamChannelListItem + +The old `StreamChannelListTile` accepted all slot widgets directly in its constructor. The new `StreamChannelListItem` takes only the essential interaction properties. Slot customization is now done via `StreamChannelListItemProps` and the `StreamComponentFactory`. + +### Breaking Changes + +- `leading`, `title`, `subtitle`, `trailing` removed from constructor +- `tileColor` removed — use `StreamChannelListItemThemeData.backgroundColor` +- `visualDensity` removed +- `contentPadding` removed +- `selectedTileColor` removed +- `unreadIndicatorBuilder` removed +- `sendingIndicatorBuilder` removed from constructor — pass via `StreamChannelListItemProps` + +### Migration + +**Before:** +```dart +StreamChannelListTile( + channel: channel, + onTap: () => openChannel(channel), + onLongPress: () => showOptions(channel), + tileColor: Colors.white, + selectedTileColor: Colors.blue.shade50, + selected: isSelected, + contentPadding: const EdgeInsets.symmetric(horizontal: 16), + leading: StreamChannelAvatar(channel: channel), + title: StreamChannelName(channel: channel), + subtitle: ChannelListTileSubtitle(channel: channel), + trailing: ChannelLastMessageDate(channel: channel), +) +``` + +**After:** +```dart +StreamChannelListItem( + channel: channel, + onTap: () => openChannel(channel), + onLongPress: () => showOptions(channel), + selected: isSelected, +) +``` + +--- + +## Customizing Slots + +To customize the slot widgets (avatar, title, subtitle, timestamp), provide a custom builder via `StreamComponentFactory`: + +**Before:** +```dart +StreamChannelListTile( + channel: channel, + leading: MyCustomAvatar(channel: channel), + subtitle: MyCustomSubtitle(channel: channel), +) +``` + +**After:** +```dart +StreamComponentFactory( + builders: StreamComponentBuilders( + extensions: streamChatComponentBuilders( + channelListItem: (context, props) => StreamChannelListTile( + avatar: StreamChannelAvatar(channel: props.channel), + title: Text(props.channel.name ?? ''), + subtitle: Text(props.channel.lastMessageAt?.toString() ?? ''), + ), + ), + ), + child: ..., +) +``` + +--- + +## Low-level Presentational Component + +The new `StreamChannelListTile` is a low-level component that renders pre-resolved data without any channel-specific logic. Use this when you want to display a channel-shaped list item with fully controlled content (e.g., in a skeleton loader or a custom list): + +```dart +StreamChannelListTile( + avatar: StreamChannelAvatar(channel: channel), + title: Text('General'), + subtitle: Text('Last message preview'), + timestamp: Text('9:41 AM'), + unreadCount: 3, + isMuted: false, + onTap: () {}, +) +``` + +> **Note:** This widget does not subscribe to any streams — all values must be provided explicitly. + +--- + +## Theme Migration + +`StreamChannelPreviewThemeData` has been replaced by `StreamChannelListItemThemeData`. + +### Property Mapping + +| Old (`StreamChannelPreviewThemeData`) | New (`StreamChannelListItemThemeData`) | +|---------------------------------------|----------------------------------------| +| `titleStyle` | `titleStyle` | +| `subtitleStyle` | `subtitleStyle` | +| `lastMessageAtStyle` | `timestampStyle` | +| `avatarTheme` | Removed — use `StreamAvatarThemeData` directly | +| `unreadCounterColor` | Removed — use `StreamBadgeNotificationThemeData` | +| `indicatorIconSize` | Removed | +| `lastMessageAtFormatter` | Removed from theme — pass to `ChannelLastMessageDate(formatter: ...)` | + +### New Properties + +| Property | Type | Description | +|----------|------|-------------| +| `backgroundColor` | `WidgetStateProperty?` | Background color resolved per state (default, hover, pressed, selected) | +| `borderColor` | `Color?` | Bottom border color | +| `muteIconPosition` | `MuteIconPosition?` | Whether the mute icon appears in `title` or `subtitle` row | + +### Global Theme Migration + +**Before:** +```dart +StreamChatTheme( + data: StreamChatThemeData( + channelPreviewTheme: StreamChannelPreviewThemeData( + titleStyle: TextStyle(fontWeight: FontWeight.bold), + subtitleStyle: TextStyle(color: Colors.grey), + lastMessageAtStyle: TextStyle(fontSize: 12), + unreadCounterColor: Colors.red, + ), + ), + child: ..., +) +``` + +**After:** +```dart +StreamChatTheme( + data: StreamChatThemeData( + channelListItemTheme: StreamChannelListItemThemeData( + titleStyle: TextStyle(fontWeight: FontWeight.bold), + subtitleStyle: TextStyle(color: Colors.grey), + timestampStyle: TextStyle(fontSize: 12), + // unreadCounterColor → customize via StreamBadgeNotificationThemeData + ), + ), + child: ..., +) +``` + +### Subtree Theme Override + +**Before:** +```dart +StreamChannelPreviewTheme( + data: StreamChannelPreviewThemeData( + titleStyle: TextStyle(color: Colors.blue), + ), + child: StreamChannelListView(...), +) +``` + +**After:** +```dart +StreamChannelListItemTheme( + data: StreamChannelListItemThemeData( + titleStyle: TextStyle(color: Colors.blue), + ), + child: StreamChannelListView(...), +) +``` + +### Background and Selected Color + +**Before:** +```dart +StreamChannelListTile( + channel: channel, + tileColor: Colors.white, + selectedTileColor: Colors.blue.shade50, + selected: isSelected, +) +``` + +**After:** +```dart +StreamChannelListItemTheme( + data: StreamChannelListItemThemeData( + backgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) return Colors.blue.shade50; + return Colors.white; + }), + ), + child: StreamChannelListItem( + channel: channel, + selected: isSelected, + ), +) +``` + +### Custom Timestamp Formatter + +**Before:** +```dart +StreamChannelPreviewThemeData( + lastMessageAtFormatter: (context, date) { + return Jiffy.parseFromDateTime(date).format('d MMMM'); + }, +) +``` + +**After:** +```dart +// Pass directly to the widget — no longer a theme property +ChannelLastMessageDate( + channel: channel, + formatter: (context, date) { + return Jiffy.parseFromDateTime(date).format('d MMMM'); + }, +) +``` + +--- + +## Migration Checklist + +- [ ] Replace `StreamChannelListTile` with `StreamChannelListItem` +- [ ] Remove `tileColor`, `visualDensity`, `contentPadding`, `selectedTileColor`, `unreadIndicatorBuilder` parameters +- [ ] Move slot customization (`leading`, `title`, `subtitle`, `trailing`) to `StreamComponentFactory` +- [ ] Replace `StreamChannelPreviewTheme` with `StreamChannelListItemTheme` +- [ ] Replace `StreamChatThemeData.channelPreviewTheme` with `StreamChatThemeData.channelListItemTheme` +- [ ] Rename `lastMessageAtStyle` to `timestampStyle` +- [ ] Move `lastMessageAtFormatter` from theme to `ChannelLastMessageDate(formatter: ...)` +- [ ] Replace `tileColor`/`selectedTileColor` with `StreamChannelListItemThemeData.backgroundColor` using `WidgetStateProperty` +- [ ] Replace `unreadCounterColor` with `StreamBadgeNotificationThemeData` +- [ ] Replace `avatarTheme` in `StreamChannelPreviewThemeData` with `StreamAvatarThemeData` on the avatar widget directly diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart index bc57390a29..d6cbd0d3a2 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart @@ -17,7 +17,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// /// We're passing a custom widget /// to [StreamChannelListView.itemBuilder]; -/// this will override the default [StreamChannelListTile] and allows you +/// this will override the default [StreamChannelListItem] and allows you /// to create one yourself. /// /// There are a couple interesting things we do in this widget: @@ -116,7 +116,7 @@ class _ChannelListPageState extends State { BuildContext context, List channels, int index, - StreamChannelListTile defaultTile, + StreamChannelListItem defaultTile, ) { final channel = channels[index]; final lastMessage = channel.state?.messages.reversed.firstWhereOrNull( @@ -142,7 +142,7 @@ class _ChannelListPageState extends State { channel: channel, ), title: StreamChannelName( - textStyle: StreamChannelPreviewTheme.of(context).titleStyle!.copyWith( + textStyle: StreamChannelListItemTheme.of(context).titleStyle!.copyWith( color: StreamChatTheme.of(context).colorTheme.textHighEmphasis // ignore: deprecated_member_use .withOpacity(opacity), diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart b/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart index 19d0e06f7e..87a03735a4 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart @@ -172,10 +172,10 @@ class StreamChannelListHeader extends StatelessWidget implements PreferredSizeWi ConnectionStatus.disconnected => Colors.grey, }; - return StreamSvgIcon( + return Icon( + context.streamIcons.pencil, size: 24, color: color, - icon: StreamSvgIcons.penWrite, ); }, ), diff --git a/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart b/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart index 7b1d963f9f..4dc226be84 100644 --- a/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart +++ b/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart @@ -59,21 +59,15 @@ class StreamChannelName extends StatelessWidget { } } else { final maxWidth = constraints.maxWidth; - final maxChars = maxWidth / (textStyle?.fontSize ?? 1); - var currentChars = 0; + channelName = ''; final currentMembers = []; otherMembers.forEach((element) { - final newLength = currentChars + (element.user?.name.length ?? 0); - if (newLength < maxChars) { - currentChars = newLength; + final newTitle = _getChannelName(currentMembers: [...currentMembers, element], members: members); + if (_calculateTextSize(newTitle).width < maxWidth) { currentMembers.add(element); + channelName = newTitle; } }); - - final exceedingMembers = otherMembers.length - currentMembers.length; - channelName = - '${currentMembers.map((e) => e.user?.name).join(', ')} ' - '${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; } } @@ -84,4 +78,19 @@ class StreamChannelName extends StatelessWidget { ); }, ); + + String _getChannelName({required List currentMembers, required List members}) { + final exceedingMembers = members.length - currentMembers.length; + return '${currentMembers.map((e) => e.user?.name).join(', ')} ' + '${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; + } + + Size _calculateTextSize(String text) { + final textPainter = TextPainter( + text: TextSpan(text: text, style: textStyle), + maxLines: 1, + textDirection: TextDirection.ltr, + )..layout(minWidth: 0, maxWidth: double.infinity); + return textPainter.size; + } } diff --git a/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart b/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart index b1c4c592e7..6d47fd2dd1 100644 --- a/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart @@ -82,13 +82,25 @@ class StreamChannelAvatar extends StatelessWidget { stream: channel.state!.membersStream, initialData: channel.state!.members, builder: (context, members) { - final users = members.map((it) => it.user!); + final users = members.map((it) => it.user!).toList(); final currentUserId = channel.client.state.currentUser?.id; + if (channel.isDistinct && users.length == 2) { + final otherUser = users.firstWhere( + (u) => u.id != currentUserId, + orElse: () => users.first, + ); + return StreamUserAvatar( + user: otherUser, + size: _avatarSizeForAvatarGroupSize(effectiveSize), + // TODO: make this configurable when the online state is shown. + showOnlineIndicator: otherUser.online, + ); + } + return StreamUserAvatarGroup( size: effectiveSize, - // Sort users by current user first. - users: users.sortedBy((it) => it.id == currentUserId ? 0 : 1), + users: users.sortedBy((it) => it.id == currentUserId ? 1 : 0), ); }, ), @@ -103,6 +115,7 @@ class StreamChannelAvatar extends StatelessWidget { ) => switch (size) { .lg => StreamAvatarSize.lg, .xl => StreamAvatarSize.xl, + .xxl => StreamAvatarSize.xxl, }; } diff --git a/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar.dart b/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar.dart index 6190e0e815..cccfa2b3d1 100644 --- a/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar.dart @@ -135,7 +135,7 @@ class StreamUserAvatar extends StatelessWidget { .xs || .sm => StreamOnlineIndicatorSize.sm, .md => StreamOnlineIndicatorSize.md, .lg => StreamOnlineIndicatorSize.lg, - .xl => StreamOnlineIndicatorSize.xl, + .xl || .xxl => StreamOnlineIndicatorSize.xl, }; } @@ -158,7 +158,7 @@ class _StreamUserAvatarPlaceholder extends StatelessWidget { final userInitials = user.name.initials; if (userInitials != null && userInitials.isNotEmpty) { return switch (size) { - .md || .lg || .xl => Text(userInitials), + .md || .lg || .xl || .xxl => Text(userInitials), .xs || .sm => Text(userInitials.characters.first), }; } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_extensions.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_extensions.dart deleted file mode 100644 index cb070faf71..0000000000 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_extensions.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// Helper extensions for internal use only. -extension MessageComposerBuilderExtensions on BuildContext { - StreamComponentBuilders get _factory => StreamComponentFactory.of( - this, - ); - - /// The builder for the message composer component. - StreamComponentBuilder? get messageComposerBuilder => - _factory.extension(); - - /// The builder for the message composer leading component. - StreamComponentBuilder? get messageComposerLeadingBuilder => - _factory.extension(); - - /// The builder for the message composer trailing component. - StreamComponentBuilder? get messageComposerTrailingBuilder => - _factory.extension(); - - /// The builder for the message composer input component. - StreamComponentBuilder? get messageComposerInputBuilder => - _factory.extension(); - - /// The builder for the message composer input leading component. - StreamComponentBuilder? get messageComposerInputLeadingBuilder => - _factory.extension(); - - /// The builder for the message composer input header component. - StreamComponentBuilder? get messageComposerInputHeaderBuilder => - _factory.extension(); - - /// The builder for the message composer input trailing component. - StreamComponentBuilder? get messageComposerInputTrailingBuilder => - _factory.extension(); -} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart index 9000377d6d..1c46f99045 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart @@ -1,7 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/message_composer_extensions.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart'; @@ -17,7 +16,10 @@ class StreamMessageComposerInputHeader extends StatelessWidget { @override Widget build(BuildContext context) { - return context.messageComposerInputHeaderBuilder?.call(context, MessageComposerInputHeaderProps.from(props)) ?? + return context.chatComponentBuilder()?.call( + context, + MessageComposerInputHeaderProps.from(props), + ) ?? _DefaultStreamMessageComposerInputHeader(props: props); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart index c959a44c0e..2399796aa3 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart @@ -1,5 +1,4 @@ import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/message_composer_extensions.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// A widget that shows the input leading of the message composer. @@ -14,7 +13,10 @@ class StreamMessageComposerInputLeading extends StatelessWidget { @override Widget build(BuildContext context) { - return context.messageComposerInputLeadingBuilder?.call(context, MessageComposerInputLeadingProps.from(props)) ?? + return context.chatComponentBuilder()?.call( + context, + MessageComposerInputLeadingProps.from(props), + ) ?? const SizedBox.shrink(); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart index 01763810ec..61190893bf 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/message_composer_extensions.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart'; @@ -17,7 +16,10 @@ class StreamMessageComposerInputTrailing extends StatelessWidget { @override Widget build(BuildContext context) { - return context.messageComposerInputTrailingBuilder?.call(context, MessageComposerInputTrailingProps.from(props)) ?? + return context.chatComponentBuilder()?.call( + context, + MessageComposerInputTrailingProps.from(props), + ) ?? DefaultStreamMessageComposerInputTrailing(props: props); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart index 13b66ecb2f..eb877c7e7d 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/message_composer_extensions.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart'; @@ -16,7 +15,10 @@ class StreamMessageComposerLeading extends StatelessWidget { @override Widget build(BuildContext context) { - return context.messageComposerLeadingBuilder?.call(context, MessageComposerLeadingProps.from(props)) ?? + return context.chatComponentBuilder()?.call( + context, + MessageComposerLeadingProps.from(props), + ) ?? _DefaultStreamMessageComposerLeading(props: props); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart index c60a315a0e..f5399ebc34 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart @@ -1,5 +1,4 @@ import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/message_composer_extensions.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// A widget that shows the trailing of the message composer. @@ -15,7 +14,10 @@ class StreamMessageComposerTrailing extends StatelessWidget { @override Widget build(BuildContext context) { - return context.messageComposerTrailingBuilder?.call(context, MessageComposerTrailingProps.from(props)) ?? + return context.chatComponentBuilder()?.call( + context, + MessageComposerTrailingProps.from(props), + ) ?? const SizedBox.shrink(); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart index 7110cf02bf..e410a6b8ad 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; -import 'package:stream_chat_flutter/src/components/message_composer/message_composer_extensions.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_input_header.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_input_leading.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_input_trailing.dart'; @@ -93,7 +92,7 @@ class _StreamChatMessageComposerState extends State { @override Widget build(BuildContext context) { - if (context.messageComposerBuilder?.call(context, widget.props) case final messageComposer?) { + if (context.chatComponentBuilder()?.call(context, widget.props) case final messageComposer?) { return messageComposer; } diff --git a/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart b/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart index 3d925516de..199ceb9fa2 100644 --- a/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart +++ b/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart @@ -1,7 +1,9 @@ +import 'package:flutter/widgets.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Builds the list of component builders for the stream chat components. Iterable> streamChatComponentBuilders({ + StreamComponentBuilder? channelListItem, StreamComponentBuilder? messageComposer, StreamComponentBuilder? messageComposerLeading, StreamComponentBuilder? messageComposerTrailing, @@ -11,6 +13,7 @@ Iterable> streamChatComponentBuilders({ StreamComponentBuilder? messageComposerInputTrailing, }) { final builders = [ + if (channelListItem != null) StreamComponentBuilderExtension(builder: channelListItem), if (messageComposer != null) StreamComponentBuilderExtension(builder: messageComposer), if (messageComposerLeading != null) StreamComponentBuilderExtension(builder: messageComposerLeading), if (messageComposerTrailing != null) StreamComponentBuilderExtension(builder: messageComposerTrailing), @@ -22,3 +25,9 @@ Iterable> streamChatComponentBuilders({ return builders; } + +/// Helper extensions for the factory builders. +extension StreamChatComponentBuildersExtension on BuildContext { + /// The builder for the given component type. + StreamComponentBuilder? chatComponentBuilder() => StreamComponentFactory.of(this).extension(); +} diff --git a/packages/stream_chat_flutter/lib/src/indicators/typing_indicator.dart b/packages/stream_chat_flutter/lib/src/indicators/typing_indicator.dart index b3cf76d0d4..10a84b2c7f 100644 --- a/packages/stream_chat_flutter/lib/src/indicators/typing_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/indicators/typing_indicator.dart @@ -58,11 +58,6 @@ class StreamTypingIndicator extends StatelessWidget { mainAxisSize: MainAxisSize.min, spacing: 4, children: [ - Lottie.asset( - 'lib/assets/animations/typing_dots.json', - package: 'stream_chat_flutter', - height: 4, - ), Flexible( child: Text( context.translations.userTypingText(users), @@ -70,6 +65,14 @@ class StreamTypingIndicator extends StatelessWidget { style: style, ), ), + Padding( + padding: const EdgeInsets.only(top: 2), + child: Lottie.asset( + 'lib/assets/animations/typing_dots.json', + package: 'stream_chat_flutter', + height: 5, + ), + ), ], ), ) diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart new file mode 100644 index 0000000000..f29bacaf0f --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart @@ -0,0 +1,788 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; +import 'package:stream_chat_flutter/src/misc/timestamp.dart'; +import 'package:stream_chat_flutter/src/utils/date_formatter.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// A widget that displays a channel preview. +/// +/// This widget is intended to be used as a Tile in [StreamChannelListView]. +/// +/// It shows the last message of the channel, the last message time, the unread +/// message count, the typing indicator, the sending indicator and the channel +/// avatar. +/// +/// Internally uses [StreamListTileContainer] from the core design system for +/// consistent visual presentation. +/// +/// See also: +/// * [StreamChannelAvatar] +/// * [StreamChannelName] +class StreamChannelListItem extends StatelessWidget { + /// Creates a new instance of [StreamChannelListItem] widget. + StreamChannelListItem({ + super.key, + required Channel channel, + GestureTapCallback? onTap, + GestureLongPressCallback? onLongPress, + bool selected = false, + }) : assert( + channel.state != null, + 'Channel ${channel.id} is not initialized', + ), + props = .new( + channel: channel, + onTap: onTap, + onLongPress: onLongPress, + selected: selected, + ); + + /// The properties for the channel list item. + final StreamChannelListItemProps props; + + /// Creates a copy of this tile but with the given fields replaced with + /// the new values. + StreamChannelListItem copyWith({ + Key? key, + Channel? channel, + VoidCallback? onTap, + VoidCallback? onLongPress, + bool? selected, + }) { + return StreamChannelListItem( + key: key ?? this.key, + channel: channel ?? props.channel, + onTap: onTap ?? props.onTap, + onLongPress: onLongPress ?? props.onLongPress, + selected: selected ?? props.selected, + ); + } + + @override + Widget build(BuildContext context) { + final builder = context.chatComponentBuilder(); + return builder?.call(context, props) ?? _DefaultStreamChannelListItem(props: props); + } +} + +/// Properties for configuring a [StreamChannelListItem]. +/// +/// This class holds all the configuration options for a channel list item, +/// allowing them to be passed through the [StreamComponentFactory]. +/// +/// See also: +/// +/// * [StreamChannelListItem], which uses these properties. +/// * [DefaultStreamChannelListItem], the default implementation. +class StreamChannelListItemProps { + /// Creates properties for a channel list item. + const StreamChannelListItemProps({ + required this.channel, + this.leading, + this.title, + this.subtitle, + this.trailing, + this.onTap, + this.onLongPress, + this.sendingIndicatorBuilder, + this.selected = false, + }); + + /// The channel to display. + final Channel channel; + + /// A widget to display as the avatar. + /// + /// Defaults to [StreamChannelAvatar]. + final Widget? leading; + + /// The primary content of the list tile. + /// + /// Defaults to [StreamChannelName]. + final Widget? title; + + /// Additional content displayed below the title. + /// + /// Defaults to [ChannelListTileSubtitle] which shows typing indicators, + /// draft messages, or the last message preview. + final Widget? subtitle; + + /// A widget to display as the timestamp. + /// + /// Defaults to [ChannelLastMessageDate]. + final Widget? trailing; + + /// Called when the user taps this list tile. + final GestureTapCallback? onTap; + + /// Called when the user long-presses on this list tile. + final GestureLongPressCallback? onLongPress; + + /// The widget builder for the sending indicator. + /// + /// `Message` is the last message in the channel. Use it to determine the + /// status using [Message.state]. + final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; + + /// True if the tile is in a selected state. + final bool selected; +} + +class _DefaultStreamChannelListItem extends StatelessWidget { + const _DefaultStreamChannelListItem({ + required this.props, + }); + + final StreamChannelListItemProps props; + + @override + Widget build(BuildContext context) { + final channelState = props.channel.state!; + final textTheme = context.streamTextTheme; + + final avatar = props.leading ?? StreamChannelAvatar(channel: props.channel, size: StreamAvatarGroupSize.xl); + final titleWidget = + props.title ?? + StreamChannelName( + channel: props.channel, + textStyle: textTheme.headingSm.copyWith(height: 1), + ); + final subtitleWidget = + props.subtitle ?? + ChannelListTileSubtitle( + channel: props.channel, + sendingIndicatorBuilder: props.sendingIndicatorBuilder, + ); + final timestampWidget = props.trailing ?? ChannelLastMessageDate(channel: props.channel); + + return BetterStreamBuilder( + stream: props.channel.isMutedStream, + initialData: props.channel.isMuted, + builder: (context, isMuted) => BetterStreamBuilder( + stream: channelState.unreadCountStream, + initialData: channelState.unreadCount, + builder: (context, unreadCount) { + return StreamChannelListTile( + avatar: avatar, + title: titleWidget, + subtitle: subtitleWidget, + timestamp: timestampWidget, + unreadCount: unreadCount, + isMuted: isMuted, + onTap: props.onTap, + onLongPress: props.onLongPress, + selected: props.selected, + ); + }, + ), + ); + } +} + +/// A widget that displays a channel list tile. +/// It's the basic component for [StreamChannelListItem] without any of the logic. +/// It can be used to fully customize the list tile data being shown. +class StreamChannelListTile extends StatelessWidget { + /// Creates a new instance of [StreamChannelListTile] widget. + const StreamChannelListTile({ + super.key, + required this.avatar, + required this.title, + this.subtitle, + this.timestamp, + this.unreadCount = 0, + this.isMuted = false, + this.onTap, + this.onLongPress, + this.selected = false, + }); + + /// The avatar widget displayed at the leading edge. + /// + /// Typically a [StreamAvatar], [StreamAvatarGroup], or an avatar wrapped + /// in a [StreamOnlineIndicator]. + final Widget avatar; + + /// The channel title widget. + /// + /// Typically a [Text] widget with the channel name. The default text style + /// is provided by the theme's title style via [DefaultTextStyle]. + final Widget title; + + /// The message preview widget displayed below the title. + /// + /// Typically a [Text] widget with the last message, but can be any widget + /// for richer content (e.g., icons, read receipts, sender prefix). + final Widget? subtitle; + + /// The timestamp widget displayed in the trailing section of the title row. + /// + /// Typically a [Text] widget with a formatted date string. The default text + /// style is provided by the theme's timestamp style via [DefaultTextStyle]. + final Widget? timestamp; + + /// The number of unread messages. + /// + /// When greater than zero, a [StreamBadgeNotification] is displayed. + final int unreadCount; + + /// Whether the channel is muted. + /// + /// When true, a mute icon is displayed in the title or subtitle. + final bool isMuted; + + /// Called when the list item is tapped. + final VoidCallback? onTap; + + /// Called when the list item is long-pressed. + final VoidCallback? onLongPress; + + /// Whether the list item is in a selected state. + final bool selected; + + @override + Widget build(BuildContext context) { + final spacing = context.streamSpacing; + final channelListItemTheme = StreamChannelListItemTheme.of(context); + final defaults = _StreamChannelListItemThemeDefaults(context); + + final effectiveTitleStyle = channelListItemTheme.titleStyle ?? defaults.titleStyle; + final effectiveSubtitleStyle = channelListItemTheme.subtitleStyle ?? defaults.subtitleStyle; + final effectiveTimestampStyle = channelListItemTheme.timestampStyle ?? defaults.timestampStyle; + final effectiveMuteIconPosition = channelListItemTheme.muteIconPosition ?? defaults.muteIconPosition; + + final muteIcon = isMuted + ? Icon( + context.streamIcons.mute, + size: 20, + color: context.streamColorScheme.textTertiary, + ) + : null; + + final hasMuteIconInSubtitle = effectiveMuteIconPosition == MuteIconPosition.subtitle && isMuted; + + return StreamListTileTheme( + data: context.streamListTileTheme.copyWith( + contentPadding: EdgeInsets.all(spacing.md - 4), + backgroundColor: channelListItemTheme.backgroundColor, + ), + child: Padding( + padding: const EdgeInsets.all(4), + child: Material( + type: MaterialType.transparency, + child: StreamListTileContainer( + enabled: true, + selected: selected, + onTap: onTap, + onLongPress: onLongPress, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: spacing.md, + children: [ + avatar, + Expanded( + child: Padding( + padding: EdgeInsets.symmetric(vertical: spacing.xxxs), + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: spacing.xxs, + children: [ + _TitleRow( + title: title, + titleTrailing: effectiveMuteIconPosition == MuteIconPosition.title ? muteIcon : null, + timestamp: timestamp, + unreadCount: unreadCount, + titleStyle: effectiveTitleStyle, + timestampStyle: effectiveTimestampStyle, + spacing: spacing, + ), + if (subtitle != null || hasMuteIconInSubtitle) + _SubtitleRow( + subtitle: subtitle, + subtitleTrailing: effectiveMuteIconPosition == MuteIconPosition.subtitle ? muteIcon : null, + subtitleStyle: effectiveSubtitleStyle, + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} + +class _TitleRow extends StatelessWidget { + const _TitleRow({ + required this.title, + this.titleTrailing, + this.timestamp, + required this.unreadCount, + required this.titleStyle, + required this.timestampStyle, + required this.spacing, + }); + + final Widget title; + final Widget? titleTrailing; + final Widget? timestamp; + final int unreadCount; + final TextStyle titleStyle; + final TextStyle timestampStyle; + final StreamSpacing spacing; + + @override + Widget build(BuildContext context) { + return Row( + spacing: spacing.md, + children: [ + Expanded( + child: Row( + spacing: spacing.xxs, + children: [ + Flexible( + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: StreamBadgeNotificationSize.sm.value), + child: Align( + alignment: AlignmentDirectional.centerStart, + widthFactor: 1, + child: DefaultTextStyle.merge( + style: titleStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + child: title, + ), + ), + ), + ), + ?titleTrailing, + ], + ), + ), + if (timestamp != null || unreadCount > 0) + Row( + mainAxisSize: MainAxisSize.min, + spacing: spacing.xs, + children: [ + if (timestamp case final timestamp?) + DefaultTextStyle.merge( + style: timestampStyle, + child: timestamp, + ), + if (unreadCount > 0) StreamBadgeNotification(label: '$unreadCount'), + ], + ), + ], + ); + } +} + +class _SubtitleRow extends StatelessWidget { + const _SubtitleRow({ + required this.subtitle, + this.subtitleTrailing, + required this.subtitleStyle, + }); + + final Widget? subtitle; + final Widget? subtitleTrailing; + final TextStyle subtitleStyle; + + @override + Widget build(BuildContext context) { + return DefaultTextStyle( + style: subtitleStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + child: Row( + children: [ + Expanded(child: subtitle ?? const SizedBox.shrink()), + ?subtitleTrailing, + ], + ), + ); + } +} + +class _StreamChannelListItemThemeDefaults extends StreamChannelListItemThemeData { + _StreamChannelListItemThemeDefaults(this._context); + + final BuildContext _context; + + late final _colorScheme = _context.streamColorScheme; + late final _textTheme = _context.streamTextTheme; + + @override + TextStyle get titleStyle => _textTheme.headingSm.copyWith(color: _colorScheme.textPrimary); + + @override + TextStyle get subtitleStyle => _textTheme.captionDefault.copyWith(color: _colorScheme.textSecondary); + + @override + TextStyle get timestampStyle => _textTheme.captionDefault.copyWith(color: _colorScheme.textTertiary); + + @override + Color get borderColor => _colorScheme.borderSubtle; + + @override + MuteIconPosition get muteIconPosition => MuteIconPosition.title; +} + +/// Shows the delivery status icon + "You:" prefix for outgoing messages in +/// the channel list. +/// +/// Unlike [StreamSendingIndicator], this widget does not show a read count +/// number. It only shows: +/// - Clock icon + "You:" (sending) +/// - Single check + "You:" (sent) +/// - Double check grey + "You:" (delivered) +/// - Double check blue + "You:" (read) +class _ChannelListDeliveryStatus extends StatelessWidget { + const _ChannelListDeliveryStatus({ + required this.channel, + required this.message, + }); + + final Channel channel; + final Message message; + + @override + Widget build(BuildContext context) { + final colorTheme = StreamChatTheme.of(context).colorTheme; + final colorScheme = context.streamColorScheme; + + return BetterStreamBuilder>( + stream: channel.state?.readStream, + initialData: channel.state?.read, + builder: (context, data) { + final isRead = data.readsOf(message: message).isNotEmpty; + final isDelivered = data.deliveriesOf(message: message).isNotEmpty; + + final Widget icon; + if (isRead) { + icon = StreamSvgIcon( + size: 16, + icon: StreamSvgIcons.checkAll, + color: colorTheme.accentPrimary, + ); + } else if (isDelivered) { + icon = StreamSvgIcon( + size: 16, + icon: StreamSvgIcons.checkAll, + color: colorScheme.textTertiary, + ); + } else if (message.state.isCompleted) { + icon = StreamSvgIcon( + size: 16, + icon: StreamSvgIcons.check, + color: colorScheme.textTertiary, + ); + } else if (message.state.isOutgoing) { + icon = StreamSvgIcon( + size: 16, + icon: StreamSvgIcons.time, + color: colorScheme.textTertiary, + ); + } else { + return const Empty(); + } + + return Padding( + padding: const EdgeInsetsDirectional.only(end: 4), + child: icon, + ); + }, + ); + } +} + +/// A widget that displays the channel last message date. +class ChannelLastMessageDate extends StatelessWidget { + /// Creates a new instance of the [ChannelLastMessageDate] widget. + ChannelLastMessageDate({ + super.key, + required this.channel, + this.textStyle, + this.formatter, + }) : assert( + channel.state != null, + 'Channel ${channel.id} is not initialized', + ); + + /// The channel to display the last message date for. + final Channel channel; + + /// The style of the text displayed + final TextStyle? textStyle; + + /// The formatter to format the date. + final DateFormatter? formatter; + + @override + Widget build(BuildContext context) { + return BetterStreamBuilder( + stream: channel.lastMessageAtStream, + initialData: channel.lastMessageAt, + builder: (context, lastMessageAt) => StreamTimestamp( + date: lastMessageAt.toLocal(), + style: textStyle, + formatter: formatter, + ), + ); + } +} + +/// A widget that displays the subtitle for [StreamChannelListItem]. +/// +/// Shows typing indicators, draft messages, or the last message preview. +/// The delivery status prefix (icon + "You:") is only shown when the subtitle +/// displays an actual sent message from the current user (not for drafts or +/// typing indicators). +class ChannelListTileSubtitle extends StatelessWidget { + /// Creates a new instance of [StreamChannelListTileSubtitle] widget. + ChannelListTileSubtitle({ + super.key, + required this.channel, + this.textStyle, + this.sendingIndicatorBuilder, + }) : assert( + channel.state != null, + 'Channel ${channel.id} is not initialized', + ); + + /// The channel to create the subtitle from. + final Channel channel; + + /// The style of the text displayed + final TextStyle? textStyle; + + /// The widget builder for the sending indicator. + /// + /// `Message` is the last message in the channel. Use it to determine the + /// status using [Message.state]. + final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; + + @override + Widget build(BuildContext context) { + return StreamTypingIndicator( + channel: channel, + style: textStyle, + alternativeWidget: _ChannelLastMessageWithStatus( + channel: channel, + textStyle: textStyle, + sendingIndicatorBuilder: sendingIndicatorBuilder, + ), + ); + } +} + +/// Combines the delivery status prefix with the last message text. +/// +/// Shows the delivery status only when the displayed content is an actual +/// sent message from the current user (not a draft). +class _ChannelLastMessageWithStatus extends StatefulWidget { + const _ChannelLastMessageWithStatus({ + required this.channel, + this.textStyle, + this.sendingIndicatorBuilder, + }); + + final Channel channel; + final TextStyle? textStyle; + final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; + + @override + State<_ChannelLastMessageWithStatus> createState() => _ChannelLastMessageWithStatusState(); +} + +class _ChannelLastMessageWithStatusState extends State<_ChannelLastMessageWithStatus> { + Message? _currentLastMessage; + + static bool _defaultLastMessagePredicate(Message message) { + if (message.isShadowed) return false; + if (message.isDeleted) return false; + if (message.isError) return false; + if (message.isEphemeral) return false; + + return true; + } + + @override + Widget build(BuildContext context) { + final channelState = widget.channel.state; + if (channelState == null) return const Empty(); + + final currentUser = widget.channel.client.state.currentUser; + + return BetterStreamBuilder<(Draft?, List)>( + stream: CombineLatestStream.combine2( + channelState.draftStream, + channelState.messagesStream, + (draft, messages) => (draft, messages), + ), + initialData: (channelState.draft, channelState.messages), + builder: (context, data) { + final (draft, messages) = data; + + // If there's a draft, show only the draft preview (no delivery status). + if (draft?.message case final draftMessage?) { + return StreamDraftMessagePreviewText( + draftMessage: draftMessage, + textStyle: widget.textStyle, + ); + } + + // Find the last valid message. + final message = messages.lastWhereOrNull( + _defaultLastMessagePredicate, + ); + final latestLastMessage = [message, _currentLastMessage].latest; + + if (latestLastMessage == null) { + return Text( + context.translations.emptyMessagesText, + style: widget.textStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + } + + final isOwnMessage = currentUser != null && latestLastMessage.user?.id == currentUser.id; + + // Show delivery status prefix only for own messages. + final Widget deliveryPrefix; + if (isOwnMessage) { + deliveryPrefix = + widget.sendingIndicatorBuilder?.call(context, latestLastMessage) ?? + _ChannelListDeliveryStatus( + channel: widget.channel, + message: latestLastMessage, + ); + } else { + deliveryPrefix = const Empty(); + } + + return Row( + children: [ + deliveryPrefix, + Flexible( + child: StreamMessagePreviewText( + message: latestLastMessage, + textStyle: widget.textStyle, + channel: channelState.channelState.channel, + ), + ), + ], + ); + }, + ); + } +} + +/// A widget that displays the last message of a channel. +class ChannelLastMessageText extends StatefulWidget { + /// Creates a new instance of [ChannelLastMessageText] widget. + ChannelLastMessageText({ + super.key, + required this.channel, + this.textStyle, + this.lastMessagePredicate = _defaultLastMessagePredicate, + }) : assert( + channel.state != null, + 'Channel ${channel.id} is not initialized', + ); + + /// The channel to display the last message of. + final Channel channel; + + /// The style of the text displayed + final TextStyle? textStyle; + + /// The predicate to determine if the message should be considered for the + /// last message. + /// + /// This predicate is used to filter out messages that should not be + /// considered for the last message. + final bool Function(Message) lastMessagePredicate; + + // The default predicate to determine if the message should be + // considered for the last message. + static bool _defaultLastMessagePredicate(Message message) { + if (message.isShadowed) return false; + if (message.isDeleted) return false; + if (message.isError) return false; + if (message.isEphemeral) return false; + + return true; + } + + @override + State createState() => _ChannelLastMessageTextState(); +} + +class _ChannelLastMessageTextState extends State { + Message? _currentLastMessage; + + @override + Widget build(BuildContext context) { + final channelState = widget.channel.state; + if (channelState == null) return const Empty(); + + return BetterStreamBuilder<(Draft?, List)>( + stream: CombineLatestStream.combine2( + channelState.draftStream, + channelState.messagesStream, + (draft, messages) => (draft, messages), + ), + initialData: (channelState.draft, channelState.messages), + builder: (context, data) { + final (draft, messages) = data; + + // Prioritize the draft message if it exists. + if (draft?.message case final draftMessage?) { + return StreamDraftMessagePreviewText( + draftMessage: draftMessage, + textStyle: widget.textStyle, + ); + } + + // Otherwise, show the channel last message if it exists. + final message = messages.lastWhereOrNull(widget.lastMessagePredicate); + final latestLastMessage = [message, _currentLastMessage].latest; + + if (latestLastMessage == null) { + return Text( + maxLines: 1, + context.translations.emptyMessagesText, + style: widget.textStyle, + overflow: TextOverflow.ellipsis, + ); + } + + return StreamMessagePreviewText( + message: latestLastMessage, + textStyle: widget.textStyle, + channel: channelState.channelState.channel, + ); + }, + ); + } +} + +extension on Iterable { + Message? get latest { + return reduce((a, b) { + if (a == null) return b; + if (b == null) return a; + + if (a.createdAt.isAfter(b.createdAt)) return a; + return b; + }); + } +} diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart deleted file mode 100644 index 2b0a91bcd2..0000000000 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart +++ /dev/null @@ -1,441 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:rxdart/rxdart.dart'; -import 'package:stream_chat_flutter/src/message_widget/sending_indicator_builder.dart'; -import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; -import 'package:stream_chat_flutter/src/misc/timestamp.dart'; -import 'package:stream_chat_flutter/src/utils/date_formatter.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// A widget that displays a channel preview. -/// -/// This widget is intended to be used as a Tile in [StreamChannelListView] -/// -/// It shows the last message of the channel, the last message time, the unread -/// message count, the typing indicator, the sending indicator and the channel -/// avatar. -/// -/// See also: -/// * [StreamChannelAvatar] -/// * [StreamChannelName] -class StreamChannelListTile extends StatelessWidget { - /// Creates a new instance of [StreamChannelListTile] widget. - StreamChannelListTile({ - super.key, - required this.channel, - this.leading, - this.title, - this.subtitle, - this.trailing, - this.onTap, - this.onLongPress, - this.tileColor, - this.visualDensity = VisualDensity.compact, - this.contentPadding = const EdgeInsets.symmetric(horizontal: 8), - this.unreadIndicatorBuilder, - this.sendingIndicatorBuilder, - this.selected = false, - this.selectedTileColor, - }) : assert( - channel.state != null, - 'Channel ${channel.id} is not initialized', - ); - - /// The channel to display. - final Channel channel; - - /// A widget to display before the title. - final Widget? leading; - - /// The primary content of the list tile. - final Widget? title; - - /// Additional content displayed below the title. - final Widget? subtitle; - - /// A widget to display at the end of tile. - final Widget? trailing; - - /// Called when the user taps this list tile. - final GestureTapCallback? onTap; - - /// Called when the user long-presses on this list tile. - final GestureLongPressCallback? onLongPress; - - /// {@template flutter.material.ListTile.tileColor} - /// Defines the background color of `ListTile`. - /// - /// When the value is null, - /// the `tileColor` is set to [ListTileTheme.tileColor] - /// if it's not null and to [Colors.transparent] if it's null. - /// {@endtemplate} - final Color? tileColor; - - /// Defines how compact the list tile's layout will be. - /// - /// {@macro flutter.material.themedata.visualDensity} - /// - /// See also: - /// - /// * [ThemeData.visualDensity], which specifies the [visualDensity] for all - /// widgets within a [Theme]. - final VisualDensity visualDensity; - - /// The tile's internal padding. - /// - /// Insets a [ListTile]'s contents: its [leading], [title], [subtitle], - /// and [trailing] widgets. - /// - /// If null, `EdgeInsets.symmetric(horizontal: 16.0)` is used. - final EdgeInsetsGeometry contentPadding; - - /// The widget builder for the unread indicator. - final WidgetBuilder? unreadIndicatorBuilder; - - /// The widget builder for the sending indicator. - /// - /// `Message` is the last message in the channel, Use it to determine the - /// status using [Message.state]. - final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; - - /// True if the tile is in a selected state. - final bool selected; - - /// The color of the tile in selected state. - final Color? selectedTileColor; - - /// Creates a copy of this tile but with the given fields replaced with - /// the new values. - StreamChannelListTile copyWith({ - Key? key, - Channel? channel, - Widget? leading, - Widget? title, - Widget? subtitle, - VoidCallback? onTap, - VoidCallback? onLongPress, - VisualDensity? visualDensity, - EdgeInsetsGeometry? contentPadding, - bool? selected, - Widget Function(BuildContext, Message)? sendingIndicatorBuilder, - Color? tileColor, - Color? selectedTileColor, - WidgetBuilder? unreadIndicatorBuilder, - Widget? trailing, - }) { - return StreamChannelListTile( - key: key ?? this.key, - channel: channel ?? this.channel, - leading: leading ?? this.leading, - title: title ?? this.title, - subtitle: subtitle ?? this.subtitle, - onTap: onTap ?? this.onTap, - onLongPress: onLongPress ?? this.onLongPress, - visualDensity: visualDensity ?? this.visualDensity, - contentPadding: contentPadding ?? this.contentPadding, - sendingIndicatorBuilder: sendingIndicatorBuilder ?? this.sendingIndicatorBuilder, - tileColor: tileColor ?? this.tileColor, - trailing: trailing ?? this.trailing, - unreadIndicatorBuilder: unreadIndicatorBuilder ?? this.unreadIndicatorBuilder, - selected: selected ?? this.selected, - selectedTileColor: selectedTileColor ?? this.selectedTileColor, - ); - } - - @override - Widget build(BuildContext context) { - final channelState = channel.state!; - final currentUser = channel.client.state.currentUser!; - - final channelPreviewTheme = StreamChannelPreviewTheme.of(context); - final streamChatTheme = StreamChatTheme.of(context); - final streamChat = StreamChat.of(context); - - final leading = this.leading ?? StreamChannelAvatar(channel: channel); - - final title = - this.title ?? - StreamChannelName( - channel: channel, - textStyle: channelPreviewTheme.titleStyle, - ); - - final subtitle = - this.subtitle ?? - ChannelListTileSubtitle( - channel: channel, - textStyle: channelPreviewTheme.subtitleStyle, - ); - - final trailing = - this.trailing ?? - ChannelLastMessageDate( - channel: channel, - textStyle: channelPreviewTheme.lastMessageAtStyle, - formatter: channelPreviewTheme.lastMessageAtFormatter, - ); - - return BetterStreamBuilder( - stream: channel.isMutedStream, - initialData: channel.isMuted, - builder: (context, isMuted) => AnimatedOpacity( - opacity: isMuted ? 0.5 : 1, - duration: const Duration(milliseconds: 300), - child: ListTile( - onTap: onTap, - onLongPress: onLongPress, - visualDensity: visualDensity, - contentPadding: contentPadding, - leading: leading, - tileColor: tileColor, - selected: selected, - selectedTileColor: selectedTileColor ?? StreamChatTheme.of(context).colorTheme.borders, - title: Row( - children: [ - Expanded(child: title), - BetterStreamBuilder>( - stream: channelState.membersStream, - initialData: channelState.members, - comparator: const ListEquality().equals, - builder: (context, members) { - if (members.isEmpty) { - return const Empty(); - } - return unreadIndicatorBuilder?.call(context) ?? StreamUnreadIndicator.channels(cid: channel.cid); - }, - ), - ], - ), - subtitle: Row( - children: [ - Expanded( - child: Align( - alignment: Alignment.centerLeft, - child: subtitle, - ), - ), - BetterStreamBuilder>( - stream: channelState.messagesStream, - initialData: channelState.messages, - comparator: const ListEquality().equals, - builder: (context, messages) { - final lastMessage = messages.lastWhereOrNull( - (m) => !m.shadowed && !m.isDeleted, - ); - - if (lastMessage == null || (lastMessage.user?.id != currentUser.id)) { - return const Empty(); - } - - final hasNonUrlAttachments = lastMessage.attachments.any( - (it) => it.type != AttachmentType.urlPreview, - ); - - return Padding( - padding: const EdgeInsets.only(right: 4), - child: - sendingIndicatorBuilder?.call(context, lastMessage) ?? - SendingIndicatorBuilder( - messageTheme: streamChatTheme.ownMessageTheme, - message: lastMessage, - hasNonUrlAttachments: hasNonUrlAttachments, - streamChat: streamChat, - streamChatTheme: streamChatTheme, - channel: channel, - ), - ); - }, - ), - trailing, - ], - ), - ), - ), - ); - } -} - -/// A widget that displays the channel last message date. -class ChannelLastMessageDate extends StatelessWidget { - /// Creates a new instance of the [ChannelLastMessageDate] widget. - ChannelLastMessageDate({ - super.key, - required this.channel, - this.textStyle, - this.formatter, - }) : assert( - channel.state != null, - 'Channel ${channel.id} is not initialized', - ); - - /// The channel to display the last message date for. - final Channel channel; - - /// The style of the text displayed - final TextStyle? textStyle; - - /// The formatter to format the date. - final DateFormatter? formatter; - - @override - Widget build(BuildContext context) { - return BetterStreamBuilder( - stream: channel.lastMessageAtStream, - initialData: channel.lastMessageAt, - builder: (context, lastMessageAt) => StreamTimestamp( - date: lastMessageAt.toLocal(), - style: textStyle, - formatter: formatter, - ), - ); - } -} - -/// A widget that displays the subtitle for [StreamChannelListTile]. -class ChannelListTileSubtitle extends StatelessWidget { - /// Creates a new instance of [StreamChannelListTileSubtitle] widget. - ChannelListTileSubtitle({ - super.key, - required this.channel, - this.textStyle, - }) : assert( - channel.state != null, - 'Channel ${channel.id} is not initialized', - ); - - /// The channel to create the subtitle from. - final Channel channel; - - /// The style of the text displayed - final TextStyle? textStyle; - - @override - Widget build(BuildContext context) { - if (channel.isMuted) { - return Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Icon(context.streamIcons.mute, size: 16), - Expanded( - child: Text( - ' ${context.translations.channelIsMutedText}', - style: textStyle, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ); - } - return StreamTypingIndicator( - channel: channel, - style: textStyle, - alternativeWidget: ChannelLastMessageText( - channel: channel, - textStyle: textStyle, - ), - ); - } -} - -/// A widget that displays the last message of a channel. -class ChannelLastMessageText extends StatefulWidget { - /// Creates a new instance of [ChannelLastMessageText] widget. - ChannelLastMessageText({ - super.key, - required this.channel, - this.textStyle, - this.lastMessagePredicate = _defaultLastMessagePredicate, - }) : assert( - channel.state != null, - 'Channel ${channel.id} is not initialized', - ); - - /// The channel to display the last message of. - final Channel channel; - - /// The style of the text displayed - final TextStyle? textStyle; - - /// The predicate to determine if the message should be considered for the - /// last message. - /// - /// This predicate is used to filter out messages that should not be - /// considered for the last message. - final bool Function(Message) lastMessagePredicate; - - // The default predicate to determine if the message should be - // considered for the last message. - static bool _defaultLastMessagePredicate(Message message) { - if (message.isShadowed) return false; - if (message.isDeleted) return false; - if (message.isError) return false; - if (message.isEphemeral) return false; - - return true; - } - - @override - State createState() => _ChannelLastMessageTextState(); -} - -class _ChannelLastMessageTextState extends State { - Message? _currentLastMessage; - - @override - Widget build(BuildContext context) { - final channelState = widget.channel.state; - if (channelState == null) return const Empty(); - - return BetterStreamBuilder<(Draft?, List)>( - stream: CombineLatestStream.combine2( - channelState.draftStream, - channelState.messagesStream, - (draft, messages) => (draft, messages), - ), - initialData: (channelState.draft, channelState.messages), - builder: (context, data) { - final (draft, messages) = data; - - // Prioritize the draft message if it exists. - if (draft?.message case final draftMessage?) { - return StreamDraftMessagePreviewText( - draftMessage: draftMessage, - textStyle: widget.textStyle, - ); - } - - // Otherwise, show the channel last message if it exists. - final message = messages.lastWhereOrNull(widget.lastMessagePredicate); - final latestLastMessage = [message, _currentLastMessage].latest; - - if (latestLastMessage == null) { - return Text( - maxLines: 1, - context.translations.emptyMessagesText, - style: widget.textStyle, - overflow: TextOverflow.ellipsis, - ); - } - - return StreamMessagePreviewText( - message: latestLastMessage, - textStyle: widget.textStyle, - channel: channelState.channelState.channel, - ); - }, - ); - } -} - -extension on Iterable { - Message? get latest { - return reduce((a, b) { - if (a == null) return b; - if (b == null) return a; - - if (a.createdAt.isAfter(b.createdAt)) return a; - return b; - }); - } -} diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart index a2c3be15a3..1d697dfd6a 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart @@ -16,10 +16,10 @@ Widget defaultChannelListViewSeparatorBuilder( /// Signature for the item builder that creates the children of the /// [StreamChannelListView]. typedef StreamChannelListViewIndexedWidgetBuilder = - StreamScrollViewIndexedWidgetBuilder; + StreamScrollViewIndexedWidgetBuilder; /// A [ListView] that shows a list of [Channel]s, -/// it uses [StreamChannelListTile] as a default item. +/// it uses [StreamChannelListItem] as a default item. /// /// This is the new version of [StreamChannelListView] that uses /// [StreamChannelListController]. @@ -39,7 +39,7 @@ typedef StreamChannelListViewIndexedWidgetBuilder = /// ``` /// /// See also: -/// * [StreamChannelListTile] +/// * [StreamChannelListItem] /// * [StreamChannelListController] class StreamChannelListView extends StatelessWidget { /// Creates a new instance of [StreamChannelListView]. @@ -303,7 +303,7 @@ class StreamChannelListView extends StatelessWidget { final onTap = onChannelTap; final onLongPress = onChannelLongPress; - final streamChannelListTile = StreamChannelListTile( + final streamChannelListTile = StreamChannelListItem( channel: channel, onTap: onTap == null ? null : () => onTap(channel), onLongPress: onLongPress == null ? null : () => onLongPress(channel), @@ -365,7 +365,7 @@ class StreamChannelListView extends StatelessWidget { } /// A widget that is used to display a separator between -/// [StreamChannelListTile] items. +/// [StreamChannelListItem] items. class StreamChannelListSeparator extends StatelessWidget { /// Creates a new instance of [StreamChannelListSeparator]. const StreamChannelListSeparator({super.key}); diff --git a/packages/stream_chat_flutter/lib/src/theme/channel_preview_theme.dart b/packages/stream_chat_flutter/lib/src/theme/channel_preview_theme.dart index 89019f322a..88ea65595e 100644 --- a/packages/stream_chat_flutter/lib/src/theme/channel_preview_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/channel_preview_theme.dart @@ -11,6 +11,9 @@ import 'package:stream_chat_flutter/src/utils/date_formatter.dart'; /// /// * [StreamChannelPreviewThemeData], which is used to configure this theme. /// {@endtemplate} +/// +/// This is deprecated, but currently still used by `StreamChannelInfoBottomSheet`. +@Deprecated('Use StreamChannelListItemTheme instead.') class StreamChannelPreviewTheme extends InheritedTheme { /// Creates a [StreamChannelPreviewTheme]. /// diff --git a/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.dart b/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.dart new file mode 100644 index 0000000000..d56174f744 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.dart @@ -0,0 +1,144 @@ +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:theme_extensions_builder_annotation/theme_extensions_builder_annotation.dart'; + +part 'stream_channel_list_item_theme.g.theme.dart'; + +/// Applies a channel list item theme to descendant +/// [StreamChannelListItem] widgets. +/// +/// Wrap a subtree with [StreamChannelListItemTheme] to override styling. +/// Access the merged theme using [BuildContext.streamChannelListItemTheme]. +/// +/// {@tool snippet} +/// +/// Override channel list item colors for a specific section: +/// +/// ```dart +/// StreamChannelListItemTheme( +/// data: StreamChannelListItemThemeData( +/// backgroundColor: Colors.grey.shade50, +/// ), +/// child: StreamChannelListItem( +/// avatar: StreamAvatar(...), +/// title: 'General', +/// ), +/// ) +/// ``` +/// {@end-tool} +/// +/// See also: +/// +/// * [StreamChannelListItemThemeData], which describes the theme. +/// * [StreamChannelListItem], the widget affected by this theme. +class StreamChannelListItemTheme extends InheritedTheme { + /// Creates a channel list item theme that controls descendant widgets. + const StreamChannelListItemTheme({ + super.key, + required this.data, + required super.child, + }); + + /// The channel list item theme data for descendant widgets. + final StreamChannelListItemThemeData data; + + /// Returns the [StreamChannelListItemThemeData] merged from local and + /// global themes. + /// + /// Local values from the nearest [StreamChannelListItemTheme] ancestor + /// take precedence over global values from [StreamTheme.of]. + static StreamChannelListItemThemeData of(BuildContext context) { + final localTheme = context.dependOnInheritedWidgetOfExactType(); + return StreamChatTheme.of(context).channelListItemTheme.merge(localTheme?.data); + } + + @override + Widget wrap(BuildContext context, Widget child) { + return StreamChannelListItemTheme(data: data, child: child); + } + + @override + bool updateShouldNotify(StreamChannelListItemTheme oldWidget) => data != oldWidget.data; +} + +/// Theme data for customizing [StreamChannelListItem] widgets. +/// +/// {@tool snippet} +/// +/// Customize channel list item appearance globally: +/// +/// ```dart +/// StreamTheme( +/// channelListItemTheme: StreamChannelListItemThemeData( +/// backgroundColor: Colors.white, +/// borderColor: Colors.grey.shade200, +/// ), +/// ) +/// ``` +/// {@end-tool} +/// +/// See also: +/// +/// * [StreamChannelListItem], the widget that uses this theme data. +/// * [StreamChannelListItemTheme], for overriding theme in a widget subtree. +@themeGen +@immutable +class StreamChannelListItemThemeData with _$StreamChannelListItemThemeData { + /// Creates a channel list item theme with optional style overrides. + const StreamChannelListItemThemeData({ + this.titleStyle, + this.subtitleStyle, + this.timestampStyle, + this.backgroundColor, + this.borderColor, + this.muteIconPosition, + }); + + /// The text style for the channel title. + /// + /// Falls back to [StreamTextTheme.headingSm] with [StreamColorScheme.textPrimary]. + final TextStyle? titleStyle; + + /// The text style for the message preview subtitle. + /// + /// Falls back to [StreamTextTheme.captionDefault] with [StreamColorScheme.textSecondary]. + final TextStyle? subtitleStyle; + + /// The text style for the timestamp. + /// + /// Falls back to [StreamTextTheme.captionDefault] with [StreamColorScheme.textTertiary]. + final TextStyle? timestampStyle; + + /// Defines the default background color of the tile. + /// + /// This color is resolved from [WidgetState]s. + final WidgetStateProperty? backgroundColor; + + /// The bottom border color of the list item. + /// + /// Falls back to [StreamColorScheme.borderSubtle]. + final Color? borderColor; + + /// The position of the mute icon. + /// + /// Falls back to [MuteIconPosition.title]. + final MuteIconPosition? muteIconPosition; + + /// Linearly interpolate between two [StreamChannelListItemThemeData] objects. + static StreamChannelListItemThemeData? lerp( + StreamChannelListItemThemeData? a, + StreamChannelListItemThemeData? b, + double t, + ) => _$StreamChannelListItemThemeData.lerp(a, b, t); +} + +/// The position of the mute icon. +/// By default the mute icon will be shown directly next to the title. +/// When choosing for subtitle, the mute icon will be shown at the end of the list item. +enum MuteIconPosition { + /// Top row of the list item, next to the title. + title, + + /// Bottom row, at the end of the list item. + subtitle, +} diff --git a/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.g.theme.dart b/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.g.theme.dart new file mode 100644 index 0000000000..bb1baafe13 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.g.theme.dart @@ -0,0 +1,127 @@ +// dart format width=80 +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_element + +part of 'stream_channel_list_item_theme.dart'; + +// ************************************************************************** +// ThemeGenGenerator +// ************************************************************************** + +mixin _$StreamChannelListItemThemeData { + bool get canMerge => true; + + static StreamChannelListItemThemeData? lerp( + StreamChannelListItemThemeData? a, + StreamChannelListItemThemeData? b, + double t, + ) { + if (identical(a, b)) { + return a; + } + + if (a == null) { + return t == 1.0 ? b : null; + } + + if (b == null) { + return t == 0.0 ? a : null; + } + + return StreamChannelListItemThemeData( + titleStyle: TextStyle.lerp(a.titleStyle, b.titleStyle, t), + subtitleStyle: TextStyle.lerp(a.subtitleStyle, b.subtitleStyle, t), + timestampStyle: TextStyle.lerp(a.timestampStyle, b.timestampStyle, t), + backgroundColor: WidgetStateProperty.lerp( + a.backgroundColor, + b.backgroundColor, + t, + Color.lerp, + ), + borderColor: Color.lerp(a.borderColor, b.borderColor, t), + muteIconPosition: t < 0.5 ? a.muteIconPosition : b.muteIconPosition, + ); + } + + StreamChannelListItemThemeData copyWith({ + TextStyle? titleStyle, + TextStyle? subtitleStyle, + TextStyle? timestampStyle, + WidgetStateProperty? backgroundColor, + Color? borderColor, + MuteIconPosition? muteIconPosition, + }) { + final _this = (this as StreamChannelListItemThemeData); + + return StreamChannelListItemThemeData( + titleStyle: titleStyle ?? _this.titleStyle, + subtitleStyle: subtitleStyle ?? _this.subtitleStyle, + timestampStyle: timestampStyle ?? _this.timestampStyle, + backgroundColor: backgroundColor ?? _this.backgroundColor, + borderColor: borderColor ?? _this.borderColor, + muteIconPosition: muteIconPosition ?? _this.muteIconPosition, + ); + } + + StreamChannelListItemThemeData merge(StreamChannelListItemThemeData? other) { + final _this = (this as StreamChannelListItemThemeData); + + if (other == null || identical(_this, other)) { + return _this; + } + + if (!other.canMerge) { + return other; + } + + return copyWith( + titleStyle: _this.titleStyle?.merge(other.titleStyle) ?? other.titleStyle, + subtitleStyle: + _this.subtitleStyle?.merge(other.subtitleStyle) ?? + other.subtitleStyle, + timestampStyle: + _this.timestampStyle?.merge(other.timestampStyle) ?? + other.timestampStyle, + backgroundColor: other.backgroundColor, + borderColor: other.borderColor, + muteIconPosition: other.muteIconPosition, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + + if (other.runtimeType != runtimeType) { + return false; + } + + final _this = (this as StreamChannelListItemThemeData); + final _other = (other as StreamChannelListItemThemeData); + + return _other.titleStyle == _this.titleStyle && + _other.subtitleStyle == _this.subtitleStyle && + _other.timestampStyle == _this.timestampStyle && + _other.backgroundColor == _this.backgroundColor && + _other.borderColor == _this.borderColor && + _other.muteIconPosition == _this.muteIconPosition; + } + + @override + int get hashCode { + final _this = (this as StreamChannelListItemThemeData); + + return Object.hash( + runtimeType, + _this.titleStyle, + _this.subtitleStyle, + _this.timestampStyle, + _this.backgroundColor, + _this.borderColor, + _this.muteIconPosition, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart index 572dd3c3f2..fbef12f1b2 100644 --- a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart @@ -63,6 +63,7 @@ class StreamChatThemeData { StreamThreadListTileThemeData? threadListTileTheme, StreamDraftListTileThemeData? draftListTileTheme, StreamVoiceRecordingAttachmentThemeData? voiceRecordingAttachmentTheme, + StreamChannelListItemThemeData? channelListItemTheme, }) { brightness ??= colorTheme?.brightness ?? Brightness.light; textTheme ??= StreamTextTheme(brightness: brightness); @@ -95,6 +96,7 @@ class StreamChatThemeData { threadListTileTheme: threadListTileTheme, draftListTileTheme: draftListTileTheme, voiceRecordingAttachmentTheme: voiceRecordingAttachmentTheme, + channelListItemTheme: channelListItemTheme, ); return defaultData.merge(customizedData); @@ -129,6 +131,7 @@ class StreamChatThemeData { required this.threadListTileTheme, required this.draftListTileTheme, required this.voiceRecordingAttachmentTheme, + required this.channelListItemTheme, }); /// Creates a theme from a Material [Theme] @@ -525,6 +528,7 @@ class StreamChatThemeData { ), ), voiceRecordingAttachmentTheme: const StreamVoiceRecordingAttachmentThemeData(), + channelListItemTheme: const StreamChannelListItemThemeData(), ); } @@ -590,6 +594,9 @@ class StreamChatThemeData { /// Theme configuration for the [StreamVoiceRecordingAttachment] widget. final StreamVoiceRecordingAttachmentThemeData voiceRecordingAttachmentTheme; + /// Theme configuration for the [StreamChannelListItem] widget. + final StreamChannelListItemThemeData channelListItemTheme; + /// Theme configuration for the [StreamDraftListTile] widget. final StreamDraftListTileThemeData draftListTileTheme; @@ -628,6 +635,7 @@ class StreamChatThemeData { StreamThreadListTileThemeData? threadListTileTheme, StreamDraftListTileThemeData? draftListTileTheme, StreamVoiceRecordingAttachmentThemeData? voiceRecordingAttachmentTheme, + StreamChannelListItemThemeData? channelListItemTheme, }) => StreamChatThemeData.raw( channelListHeaderTheme: this.channelListHeaderTheme.merge(channelListHeaderTheme), textTheme: this.textTheme.merge(textTheme), @@ -650,6 +658,7 @@ class StreamChatThemeData { threadListTileTheme: threadListTileTheme ?? this.threadListTileTheme, draftListTileTheme: draftListTileTheme ?? this.draftListTileTheme, voiceRecordingAttachmentTheme: voiceRecordingAttachmentTheme ?? this.voiceRecordingAttachmentTheme, + channelListItemTheme: channelListItemTheme ?? this.channelListItemTheme, ); /// Merge themes @@ -677,6 +686,7 @@ class StreamChatThemeData { threadListTileTheme: threadListTileTheme.merge(other.threadListTileTheme), draftListTileTheme: draftListTileTheme.merge(other.draftListTileTheme), voiceRecordingAttachmentTheme: voiceRecordingAttachmentTheme.merge(other.voiceRecordingAttachmentTheme), + channelListItemTheme: channelListItemTheme.merge(other.channelListItemTheme), ); } } diff --git a/packages/stream_chat_flutter/lib/src/theme/themes.dart b/packages/stream_chat_flutter/lib/src/theme/themes.dart index 8f1558bdc3..7bcf5a75e6 100644 --- a/packages/stream_chat_flutter/lib/src/theme/themes.dart +++ b/packages/stream_chat_flutter/lib/src/theme/themes.dart @@ -15,6 +15,7 @@ export 'poll_interactor_theme.dart'; export 'poll_option_votes_dialog_theme.dart'; export 'poll_options_dialog_theme.dart'; export 'poll_results_dialog_theme.dart'; +export 'stream_channel_list_item_theme.dart'; export 'text_theme.dart'; export 'thread_list_tile_theme.dart'; export 'voice_recording_attachment_theme.dart'; diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 724e02ec00..6ff3c40507 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -4,6 +4,7 @@ export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; export 'package:stream_core_flutter/stream_core_flutter.dart' show + StreamAvatarGroupSize, StreamAvatarSize, StreamAudioWaveformSlider, StreamAudioWaveform, @@ -138,7 +139,7 @@ export 'src/reactions/picker/reaction_picker.dart'; export 'src/reactions/user_reactions.dart'; export 'src/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart'; export 'src/scroll_view/channel_scroll_view/stream_channel_grid_view.dart'; -export 'src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart'; +export 'src/scroll_view/channel_scroll_view/stream_channel_list_item.dart'; export 'src/scroll_view/channel_scroll_view/stream_channel_list_view.dart'; export 'src/scroll_view/draft_scroll_view/stream_draft_list_tile.dart'; export 'src/scroll_view/draft_scroll_view/stream_draft_list_view.dart'; diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 43741c9ee8..53af9ee6aa 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -62,10 +62,11 @@ dependencies: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 07f2357a4f1266784c26fde3430f0bdff43bbee8 + ref: 77f66669da436947e7c146f90b64b825351cd6ef path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 + theme_extensions_builder_annotation: ^7.1.0 thumblr: ^0.0.4 url_launcher: ^6.3.0 video_player: ^2.8.7 diff --git a/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart b/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart index 44921dc7c8..a1e4d22ff2 100644 --- a/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart +++ b/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart @@ -60,6 +60,7 @@ void main() { when(() => channel.client).thenReturn(client); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); + when(() => channel.isDistinct).thenReturn(true); when(() => channel.imageStream).thenAnswer((i) => Stream.value(null)); when(() => channel.image).thenReturn(null); when(() => channelState.membersStream).thenAnswer( @@ -137,6 +138,7 @@ void main() { when(() => clientState.currentUser).thenReturn(currentUser); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); + when(() => channel.isDistinct).thenReturn(false); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); when(() => channel.imageStream).thenAnswer((i) => Stream.value(null)); diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png index d060b781dd2cb55ac4c050222ce188d9112a2782..ed7e11f4e68b1f28e889d399037384bb8fe97d97 100644 GIT binary patch literal 8707 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 literal 9208 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

T)^kv$fP^ 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 diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png index a26852bf7f21fc94b8acbe013277fe588c013b6b..4f907ddec5b236625847de4cfd23371855e53838 100644 GIT binary patch literal 10915 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 Ds+TP+ literal 9259 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 literal 11397 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 I&ZD^h1x7V>^#A|> From 0959f65c4e8876afe66261aa872061df53a96315 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Fri, 6 Mar 2026 13:22:00 +0100 Subject: [PATCH 16/33] feat(ui): edit message (#2526) * started edit message * Added unit tests * change isEnabled state * fix git ref * fix analysis and format issue --- melos.yaml | 2 +- .../message_composer_input_header.dart | 45 ++++++- .../message_composer_input_trailing.dart | 10 +- .../message_input/stream_message_input.dart | 7 +- .../message_list_view/message_list_view.dart | 7 + .../src/message_widget/message_widget.dart | 14 ++ packages/stream_chat_flutter/pubspec.yaml | 2 +- .../src/message_input/message_input_test.dart | 123 ++++++++++++++++++ .../src/stream_message_input_controller.dart | 26 ++++ .../stream_message_input_controller_test.dart | 57 ++++++++ sample_app/lib/pages/channel_page.dart | 8 ++ 11 files changed, 291 insertions(+), 10 deletions(-) diff --git a/melos.yaml b/melos.yaml index 5eb60b46ea..bc132575b7 100644 --- a/melos.yaml +++ b/melos.yaml @@ -95,7 +95,7 @@ command: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 77f66669da436947e7c146f90b64b825351cd6ef + ref: 77df1314fb3be17e3bc711996421699fc7b521a2 path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart index 1c46f99045..f09be9611f 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart @@ -32,8 +32,16 @@ class _DefaultStreamMessageComposerInputHeader extends StatelessWidget { @override Widget build(BuildContext context) { - final quotedMessage = props.controller.message.quotedMessage; - final ogAttachment = props.controller.ogAttachment; + return ValueListenableBuilder( + valueListenable: props.controller, + builder: (context, _, __) => _buildContent(context), + ); + } + + Widget _buildContent(BuildContext context) { + final isEditing = !controller.message.state.isInitial; + final quotedMessage = !isEditing ? controller.message.quotedMessage : null; + final ogAttachment = controller.ogAttachment; final nonOGAttachments = controller.attachments .where((it) { return it.titleLink == null && it.type != AttachmentType.voiceRecording; @@ -46,7 +54,8 @@ class _DefaultStreamMessageComposerInputHeader extends StatelessWidget { .toList(growable: false); final hasAttachments = nonOGAttachments.isNotEmpty; - final hasContent = quotedMessage != null || hasAttachments || ogAttachment != null || voiceRecordings.isNotEmpty; + final hasContent = + isEditing || quotedMessage != null || hasAttachments || ogAttachment != null || voiceRecordings.isNotEmpty; final spacing = context.streamSpacing; final contentPadding = EdgeInsets.only( @@ -64,7 +73,15 @@ class _DefaultStreamMessageComposerInputHeader extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - if (quotedMessage != null) + if (isEditing) + Padding( + padding: contentPadding, + child: _EditMessageInHeader( + message: controller.editingOriginalMessage ?? controller.message, + onRemovePressed: controller.cancelEditMessage, + ), + ) + else if (quotedMessage != null) Padding( padding: contentPadding, child: _QuotedMessageInHeader( @@ -123,6 +140,26 @@ class _DefaultStreamMessageComposerInputHeader extends StatelessWidget { } } +class _EditMessageInHeader extends StatelessWidget { + const _EditMessageInHeader({ + required this.message, + required this.onRemovePressed, + }); + + final Message message; + final VoidCallback onRemovePressed; + + @override + Widget build(BuildContext context) { + return MessageComposerReplyAttachment( + title: context.translations.editMessageLabel, + subtitle: message.text ?? '', + onRemovePressed: onRemovePressed, + style: ReplyStyle.outgoing, + ); + } +} + class _QuotedMessageInHeader extends StatelessWidget { const _QuotedMessageInHeader({ required this.quotedMessage, diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart index 61190893bf..3c9d3284af 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart @@ -43,20 +43,26 @@ class DefaultStreamMessageComposerInputTrailing extends StatelessWidget { valueListenable: _controller, builder: (context, value, child) { final hasText = _controller.text.isNotEmpty; + final hasContent = hasText || _controller.attachments.isNotEmpty; + final isEditing = !_controller.message.state.isInitial; var buttonState = StreamMessageComposerInputTrailingState.microphone; if (props.isAudioRecordingFlowActive) { buttonState = StreamMessageComposerInputTrailingState.voiceRecordingActive; } - if (hasText || _controller.attachments.isNotEmpty) { + if (isEditing) { + buttonState = StreamMessageComposerInputTrailingState.edit; + } else if (hasContent) { buttonState = StreamMessageComposerInputTrailingState.send; } + final isEnabled = !isEditing || hasContent; + return props.isAudioRecordingFlowLocked || props.isAudioRecordingFlowStopped ? const SizedBox.shrink() : StreamCoreMessageComposerInputTrailing( controller: _controller.textFieldController, - onSendPressed: props.onSendPressed, + onSendPressed: isEnabled ? props.onSendPressed : null, voiceRecordingCallback: props.voiceRecordingCallback, buttonState: buttonState, ); diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart index 07e291bb49..46ef35e676 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart @@ -1534,14 +1534,17 @@ class StreamMessageInputState extends State with Restoration try { // Note: edited messages which are bounced back with an error needs to be // sent as new messages as the backend doesn't store them. - final resp = await switch (_isEditing && !message.isBouncedWithError) { + // Use message.state directly rather than _isEditing, because the + // controller is reset before this method is called. + final isEditing = !message.state.isInitial; + final resp = await switch (isEditing && !message.isBouncedWithError) { true => channel.updateMessage(message), false => channel.sendMessage(message), }; // We don't want to start the cooldown if an already sent message is // being edited. - if (!_isEditing) { + if (!isEditing) { _effectiveController.startCooldown(channel.getRemainingCooldown()); } diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart index 72b923885a..021ef9bfdd 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart @@ -94,6 +94,7 @@ class StreamMessageListView extends StatefulWidget { this.parentMessage, this.threadBuilder, this.onThreadTap, + this.onEditMessageTap, this.dateDividerBuilder, this.floatingDateDividerBuilder, // we need to use ClampingScrollPhysics to avoid the list view to bounce @@ -180,6 +181,11 @@ class StreamMessageListView extends StatefulWidget { /// built using [threadBuilder] final ThreadTapCallback? onThreadTap; + /// {@macro onEditMessageTap} + /// + /// If provided, the inline edit flow is used instead of the edit bottom sheet. + final void Function(Message)? onEditMessageTap; + /// If true will show a scroll to bottom button when /// the scroll offset is not zero final bool showScrollToBottom; @@ -1278,6 +1284,7 @@ class _StreamMessageListViewState extends State { showFlagButton: !isMyMessage, borderSide: borderSide, onThreadTap: _onThreadTap, + onEditMessageTap: widget.onEditMessageTap, attachmentShape: RoundedRectangleBorder( side: BorderSide( color: _streamTheme.colorTheme.borders, diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart index c45941b2a3..baa03662e8 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart @@ -57,6 +57,7 @@ class StreamMessageWidget extends StatefulWidget { this.showInChannelIndicator = false, this.onReplyTap, this.onThreadTap, + this.onEditMessageTap, this.onConfirmDeleteTap, this.showUsername = true, this.showTimestamp = true, @@ -117,6 +118,12 @@ class StreamMessageWidget extends StatefulWidget { /// {@endtemplate} final void Function(Message)? onReplyTap; + /// {@template onEditMessageTap} + /// The function called when tapping the edit action on a message. + /// If provided, the inline edit flow is used instead of the bottom sheet. + /// {@endtemplate} + final void Function(Message)? onEditMessageTap; + /// {@template onDeleteTap} /// The function called when delete confirmation button is tapped. /// {@endtemplate} @@ -404,6 +411,7 @@ class StreamMessageWidget extends StatefulWidget { void Function(User)? onMentionTap, void Function(Message)? onThreadTap, void Function(Message)? onReplyTap, + void Function(Message)? onEditMessageTap, Future Function(Message)? onConfirmDeleteTap, Widget Function(BuildContext, Message)? editMessageInputBuilder, Widget Function(BuildContext, Message)? textBuilder, @@ -470,6 +478,7 @@ class StreamMessageWidget extends StatefulWidget { onMentionTap: onMentionTap ?? this.onMentionTap, onThreadTap: onThreadTap ?? this.onThreadTap, onReplyTap: onReplyTap ?? this.onReplyTap, + onEditMessageTap: onEditMessageTap ?? this.onEditMessageTap, onConfirmDeleteTap: onConfirmDeleteTap ?? this.onConfirmDeleteTap, editMessageInputBuilder: editMessageInputBuilder ?? this.editMessageInputBuilder, textBuilder: textBuilder ?? this.textBuilder, @@ -1036,6 +1045,11 @@ class _StreamMessageWidgetState extends State Message message, Channel channel, ) { + final onEditMessageTap = widget.onEditMessageTap; + if (onEditMessageTap != null) { + onEditMessageTap(message); + return Future.value(); + } return showEditMessageSheet( context: context, channel: channel, diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 53af9ee6aa..410169f2d5 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 77f66669da436947e7c146f90b64b825351cd6ef + ref: 77df1314fb3be17e3bc711996421699fc7b521a2 path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 diff --git a/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart b/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart index df969c37da..f9db632b4c 100644 --- a/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart @@ -369,6 +369,129 @@ void main() { ); }); + group('Edit message routing', () { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + + setUp(() { + registerFallbackValue(Message()); + + when(() => client.state).thenReturn(clientState); + when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.currentUserStream).thenAnswer( + (_) => Stream.value(OwnUser(id: 'user-id')), + ); + + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(channel.getRemainingCooldown).thenReturn(0); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((_) => Stream.value(false)); + when(() => channel.extraData).thenReturn({'name': 'test'}); + when(() => channel.extraDataStream).thenAnswer((_) => Stream.value({'name': 'test'})); + when(() => channelState.isUpToDate).thenReturn(true); + when(() => channelState.members).thenReturn([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + ]); + when(() => channelState.membersStream).thenAnswer( + (_) => Stream.value([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + ]), + ); + when(() => channelState.messages).thenReturn([]); + when(() => channelState.messagesStream).thenAnswer((_) => Stream.value([])); + }); + + testWidgets( + 'calls updateMessage when controller is in edit state', + (tester) async { + when(() => channel.updateMessage(any())).thenAnswer( + (_) async => UpdateMessageResponse()..message = Message(id: 'msg-1', text: 'Edited text'), + ); + + final existingMessage = Message(id: 'msg-1', text: 'Original text'); + final messageInputController = StreamMessageInputController()..editMessage(existingMessage); + addTearDown(messageInputController.dispose); + + final key = GlobalKey(); + + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + bottomNavigationBar: StreamMessageInput( + key: key, + messageInputController: messageInputController, + ), + ), + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + await key.currentState!.sendMessage(); + await tester.pumpAndSettle(); + + verify(() => channel.updateMessage(any())).called(1); + verifyNever(() => channel.sendMessage(any())); + }, + ); + + testWidgets( + 'calls sendMessage when controller is in normal (non-edit) state', + (tester) async { + when(() => channel.sendMessage(any())).thenAnswer( + (_) async => SendMessageResponse()..message = Message(text: 'Hello'), + ); + + final messageInputController = StreamMessageInputController( + message: Message(text: 'Hello'), + ); + addTearDown(messageInputController.dispose); + + final key = GlobalKey(); + + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + bottomNavigationBar: StreamMessageInput( + key: key, + messageInputController: messageInputController, + ), + ), + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + await key.currentState!.sendMessage(); + await tester.pumpAndSettle(); + + verify(() => channel.sendMessage(any())).called(1); + verifyNever(() => channel.updateMessage(any())); + }, + ); + }); + group('DmCheckboxListTile integration in MessageInput', () { final client = MockClient(); final clientState = MockClientState(); diff --git a/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart index 35cbc4915a..a9b121914d 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart @@ -315,6 +315,32 @@ class StreamMessageInputController extends ValueNotifier { message = Message(); } + /// The original message being edited, before any user changes. + /// + /// This is set by [editMessage] and cleared by [cancelEditMessage]. + /// Use this to display a stable preview of the original message while the + /// user is typing their edits. + Message? get editingOriginalMessage => _editingOriginalMessage; + Message? _editingOriginalMessage; + + /// Sets the controller to edit an existing [message]. + /// + /// Stores a snapshot of [message] in [editingOriginalMessage] so the + /// original content stays visible while the user types. + /// Resets [_initialMessage] to an empty message so that [cancelEditMessage] + /// returns to a clean empty state. + void editMessage(Message message) { + _editingOriginalMessage = message; + _initialMessage = Message(); + this.message = message.copyWith(state: MessageState.updating); + } + + /// Cancels the current edit and resets the controller to an empty state. + void cancelEditMessage() { + _editingOriginalMessage = null; + reset(); + } + /// Sets the [message] to the initial [Message] value. void reset({bool resetId = true}) { if (resetId) { diff --git a/packages/stream_chat_flutter_core/test/stream_message_input_controller_test.dart b/packages/stream_chat_flutter_core/test/stream_message_input_controller_test.dart index f2159360fa..f3cae092ca 100644 --- a/packages/stream_chat_flutter_core/test/stream_message_input_controller_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_message_input_controller_test.dart @@ -380,6 +380,63 @@ void main() { }); }); + group('Edit Message', () { + test('editMessage sets state to MessageState.updating', () { + final existingMessage = Message(id: 'msg-1', text: 'Original text'); + controller.editMessage(existingMessage); + + expect(controller.message.state.isInitial, isFalse); + expect(controller.message.state.isUpdating, isTrue); + }); + + test('editMessage preserves the message id and text', () { + final existingMessage = Message(id: 'msg-1', text: 'Original text'); + controller.editMessage(existingMessage); + + expect(controller.message.id, 'msg-1'); + expect(controller.message.text, 'Original text'); + }); + + test('editMessage stores original in editingOriginalMessage', () { + final existingMessage = Message(id: 'msg-1', text: 'Original text'); + controller.editMessage(existingMessage); + + expect(controller.editingOriginalMessage, isNotNull); + expect(controller.editingOriginalMessage!.id, 'msg-1'); + expect(controller.editingOriginalMessage!.text, 'Original text'); + }); + + test('editingOriginalMessage text is not affected by subsequent typing', () { + final existingMessage = Message(id: 'msg-1', text: 'Original text'); + controller.editMessage(existingMessage); + + controller.text = 'Edited text'; + + expect(controller.editingOriginalMessage!.text, 'Original text'); + expect(controller.message.text, 'Edited text'); + }); + + test('cancelEditMessage clears editingOriginalMessage', () { + final existingMessage = Message(id: 'msg-1', text: 'Original text'); + controller.editMessage(existingMessage); + + controller.cancelEditMessage(); + + expect(controller.editingOriginalMessage, isNull); + }); + + test('cancelEditMessage resets controller to empty state, not the edited message', () { + final existingMessage = Message(id: 'msg-1', text: 'Original text'); + controller.editMessage(existingMessage); + controller.text = 'Edited text'; + + controller.cancelEditMessage(); + + expect(controller.text, isEmpty); + expect(controller.message.state.isInitial, isTrue); + }); + }); + group('Reset and Clear', () { test('clear resets the message to empty state', () { controller.text = 'Some text'; diff --git a/sample_app/lib/pages/channel_page.dart b/sample_app/lib/pages/channel_page.dart index 66975951fd..1accd2658c 100644 --- a/sample_app/lib/pages/channel_page.dart +++ b/sample_app/lib/pages/channel_page.dart @@ -51,6 +51,13 @@ class _ChannelPageState extends State { }); } + void _editMessage(Message message) { + _messageInputController.editMessage(message); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + _focusNode!.requestFocus(); + }); + } + @override Widget build(BuildContext context) { final theme = StreamChatTheme.of(context); @@ -98,6 +105,7 @@ class _ChannelPageState extends State { initialScrollIndex: widget.initialScrollIndex, initialAlignment: widget.initialAlignment, highlightInitialMessage: widget.highlightInitialMessage, + onEditMessageTap: _editMessage, //onMessageSwiped: _reply, messageFilter: defaultFilter, messageBuilder: customMessageBuilder, From e1245849f0fbb9c643e869fe10efbef58aa11e8e Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Fri, 6 Mar 2026 13:25:52 +0100 Subject: [PATCH 17/33] feat(ui): inline attachment picker (#2527) * make inline attachment picker # Conflicts: # packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart # packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart * start with new UI * Improve layout of gallery picker * hide picker when keyboard is visible * Improve on minimal spacing in message composer * update tests * remove unused variable --- melos.yaml | 2 +- .../message_composer_component_props.dart | 16 + .../message_composer_leading.dart | 26 +- .../stream_chat_message_composer.dart | 7 + .../stream_attachment_picker.dart | 304 +++++++-------- ...stream_attachment_picker_bottom_sheet.dart | 234 +---------- .../stream_attachment_picker_option.dart | 2 +- .../message_input/stream_message_input.dart | 191 ++++++--- .../stream_photo_gallery_tile.dart | 56 ++- packages/stream_chat_flutter/pubspec.yaml | 2 +- .../stream_attachment_picker_test.dart | 364 +++++++----------- sample_app/lib/pages/channel_page.dart | 2 +- 12 files changed, 506 insertions(+), 700 deletions(-) diff --git a/melos.yaml b/melos.yaml index bc132575b7..27ae7fc461 100644 --- a/melos.yaml +++ b/melos.yaml @@ -95,7 +95,7 @@ command: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 77df1314fb3be17e3bc711996421699fc7b521a2 + ref: 0d5268a506dbc045fcdb8ac901bb2fc70367a3af path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_component_props.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_component_props.dart index 8bdf815072..9d450d0615 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_component_props.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_component_props.dart @@ -22,6 +22,7 @@ class MessageComposerComponentProps { required this.onSendPressed, this.voiceRecordingCallback, this.onAttachmentButtonPressed, + this.isPickerOpen = false, this.focusNode, this.currentUserId, required this.audioRecorderState, @@ -45,6 +46,9 @@ class MessageComposerComponentProps { /// The callback for when the attachment button is pressed. final VoidCallback? onAttachmentButtonPressed; + /// Whether the inline attachment picker is currently open. + final bool isPickerOpen; + /// The focus node for the message composer component. final FocusNode? focusNode; @@ -73,6 +77,7 @@ class MessageComposerLeadingProps extends MessageComposerComponentProps { required super.onSendPressed, required super.voiceRecordingCallback, required super.onAttachmentButtonPressed, + required super.isPickerOpen, required super.focusNode, required super.currentUserId, required super.audioRecorderState, @@ -87,6 +92,7 @@ class MessageComposerLeadingProps extends MessageComposerComponentProps { onSendPressed: props.onSendPressed, voiceRecordingCallback: props.voiceRecordingCallback, onAttachmentButtonPressed: props.onAttachmentButtonPressed, + isPickerOpen: props.isPickerOpen, focusNode: props.focusNode, currentUserId: props.currentUserId, audioRecorderState: props.audioRecorderState, @@ -103,6 +109,7 @@ class MessageComposerTrailingProps extends MessageComposerComponentProps { required super.onSendPressed, required super.voiceRecordingCallback, required super.onAttachmentButtonPressed, + required super.isPickerOpen, required super.focusNode, required super.currentUserId, required super.audioRecorderState, @@ -117,6 +124,7 @@ class MessageComposerTrailingProps extends MessageComposerComponentProps { onSendPressed: props.onSendPressed, voiceRecordingCallback: props.voiceRecordingCallback, onAttachmentButtonPressed: props.onAttachmentButtonPressed, + isPickerOpen: props.isPickerOpen, focusNode: props.focusNode, currentUserId: props.currentUserId, audioRecorderState: props.audioRecorderState, @@ -133,6 +141,7 @@ class MessageComposerInputProps extends MessageComposerComponentProps { required super.onSendPressed, required super.voiceRecordingCallback, required super.onAttachmentButtonPressed, + required super.isPickerOpen, required super.focusNode, required super.currentUserId, required super.audioRecorderState, @@ -147,6 +156,7 @@ class MessageComposerInputProps extends MessageComposerComponentProps { onSendPressed: props.onSendPressed, voiceRecordingCallback: props.voiceRecordingCallback, onAttachmentButtonPressed: props.onAttachmentButtonPressed, + isPickerOpen: props.isPickerOpen, focusNode: props.focusNode, currentUserId: props.currentUserId, audioRecorderState: props.audioRecorderState, @@ -163,6 +173,7 @@ class MessageComposerInputLeadingProps extends MessageComposerComponentProps { required super.onSendPressed, required super.voiceRecordingCallback, required super.onAttachmentButtonPressed, + required super.isPickerOpen, required super.focusNode, required super.currentUserId, required super.audioRecorderState, @@ -177,6 +188,7 @@ class MessageComposerInputLeadingProps extends MessageComposerComponentProps { onSendPressed: props.onSendPressed, voiceRecordingCallback: props.voiceRecordingCallback, onAttachmentButtonPressed: props.onAttachmentButtonPressed, + isPickerOpen: props.isPickerOpen, focusNode: props.focusNode, currentUserId: props.currentUserId, audioRecorderState: props.audioRecorderState, @@ -193,6 +205,7 @@ class MessageComposerInputHeaderProps extends MessageComposerComponentProps { required super.onSendPressed, required super.voiceRecordingCallback, required super.onAttachmentButtonPressed, + required super.isPickerOpen, required super.focusNode, required super.currentUserId, required super.audioRecorderState, @@ -207,6 +220,7 @@ class MessageComposerInputHeaderProps extends MessageComposerComponentProps { onSendPressed: props.onSendPressed, voiceRecordingCallback: props.voiceRecordingCallback, onAttachmentButtonPressed: props.onAttachmentButtonPressed, + isPickerOpen: props.isPickerOpen, focusNode: props.focusNode, currentUserId: props.currentUserId, audioRecorderState: props.audioRecorderState, @@ -223,6 +237,7 @@ class MessageComposerInputTrailingProps extends MessageComposerComponentProps { required super.onSendPressed, required super.voiceRecordingCallback, required super.onAttachmentButtonPressed, + required super.isPickerOpen, required super.focusNode, required super.currentUserId, required super.audioRecorderState, @@ -237,6 +252,7 @@ class MessageComposerInputTrailingProps extends MessageComposerComponentProps { onSendPressed: props.onSendPressed, voiceRecordingCallback: props.voiceRecordingCallback, onAttachmentButtonPressed: props.onAttachmentButtonPressed, + isPickerOpen: props.isPickerOpen, focusNode: props.focusNode, currentUserId: props.currentUserId, audioRecorderState: props.audioRecorderState, diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart index eb877c7e7d..942100e1c6 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart @@ -30,6 +30,9 @@ class _DefaultStreamMessageComposerLeading extends StatelessWidget { @override Widget build(BuildContext context) { + // 45 degrees = 0.125 turns + const closedRotation = 0.125; + return AnimatedOpacity( opacity: props.isAudioRecordingFlowActive ? 0.0 : 1.0, duration: props.isAudioRecordingFlowActive ? Duration.zero : const Duration(milliseconds: 200), @@ -40,15 +43,20 @@ class _DefaultStreamMessageComposerLeading extends StatelessWidget { child: Row( children: [ if (!props.isAudioRecordingFlowActive) ...[ - StreamButton.icon( - icon: context.streamIcons.plusLarge, - style: StreamButtonStyle.secondary, - type: StreamButtonType.outline, - size: StreamButtonSize.large, - isFloating: props.isFloating, - onTap: () { - props.onAttachmentButtonPressed?.call(); - }, + AnimatedRotation( + turns: props.isPickerOpen ? closedRotation : 0, + duration: const Duration(milliseconds: 150), + curve: Curves.easeOut, + child: StreamButton.icon( + icon: context.streamIcons.plusLarge, + style: StreamButtonStyle.secondary, + type: StreamButtonType.outline, + size: StreamButtonSize.large, + isFloating: props.isFloating, + onTap: () { + props.onAttachmentButtonPressed?.call(); + }, + ), ), SizedBox(width: context.streamSpacing.xs), ], diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart index e410a6b8ad..86f57388c0 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart @@ -26,6 +26,7 @@ class StreamChatMessageComposer extends StatefulWidget { StreamMessageInputController? controller, required VoidCallback onSendPressed, VoidCallback? onAttachmentButtonPressed, + bool isPickerOpen = false, FocusNode? focusNode, String? currentUserId, String placeholder = '', @@ -38,6 +39,7 @@ class StreamChatMessageComposer extends StatefulWidget { message: null, onSendPressed: onSendPressed, onAttachmentButtonPressed: onAttachmentButtonPressed, + isPickerOpen: isPickerOpen, focusNode: focusNode, currentUserId: currentUserId, placeholder: placeholder, @@ -168,6 +170,7 @@ class MessageComposerProps { this.placeholder = '', required this.onSendPressed, this.onAttachmentButtonPressed, + this.isPickerOpen = false, this.focusNode, this.currentUserId, this.audioRecorderController, @@ -193,6 +196,9 @@ class MessageComposerProps { /// The callback for when the attachment button is pressed. final VoidCallback? onAttachmentButtonPressed; + /// Whether the inline attachment picker is currently open. + final bool isPickerOpen; + /// The focus node for the message composer. final FocusNode? focusNode; @@ -263,6 +269,7 @@ class DefaultStreamChatMessageComposer extends StatelessWidget { onSendPressed: props.onSendPressed, voiceRecordingCallback: _createVoiceRecordingCallback(context), onAttachmentButtonPressed: props.onAttachmentButtonPressed, + isPickerOpen: props.isPickerOpen, audioRecorderState: audioRecorderState, focusNode: props.focusNode, ); diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart index 6cef2c49c1..1becd80611 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart @@ -5,13 +5,15 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/message_input/attachment_picker/options/options.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; -/// Bottom sheet widget for the system attachment picker interface. -/// This is used when the attachment picker uses system integration, -/// typically on web/desktop or when useSystemAttachmentPicker is true. -class StreamSystemAttachmentPickerBottomSheet extends StatelessWidget { - /// Creates a new instance of [StreamSystemAttachmentPickerBottomSheet]. - const StreamSystemAttachmentPickerBottomSheet({ +/// Inline widget for the system attachment picker interface. +/// +/// Shows a list of options that launch native platform dialogs. +/// Selections are applied in real-time via the [controller]. +class StreamSystemAttachmentPicker extends StatelessWidget { + /// Creates a new instance of [StreamSystemAttachmentPicker]. + const StreamSystemAttachmentPicker({ super.key, required this.options, required this.controller, @@ -40,7 +42,7 @@ class StreamSystemAttachmentPickerBottomSheet extends StatelessWidget { return ListTile( enabled: isEnabled, - leading: option.icon, + leading: Icon(option.icon), title: Text(option.title), onTap: () => option.onTap(context, controller), ); @@ -53,17 +55,21 @@ class StreamSystemAttachmentPickerBottomSheet extends StatelessWidget { } } -/// Bottom sheet widget for the tabbed attachment picker interface. -/// This is used when the attachment picker displays a tabbed interface, -/// typically on mobile when useSystemAttachmentPicker is false. -class StreamTabbedAttachmentPickerBottomSheet extends StatefulWidget { - /// Creates a new instance of [StreamTabbedAttachmentPickerBottomSheet]. - const StreamTabbedAttachmentPickerBottomSheet({ +/// Inline widget for the tabbed attachment picker interface. +/// +/// Displays a tabbed interface with horizontal tabs for different attachment +/// types (gallery, camera, files, etc.). Each tab shows a specialized +/// interface for selecting that type of attachment. +/// +/// Selections are applied in real-time via the [controller] rather than +/// through a modal result pattern. +class StreamTabbedAttachmentPicker extends StatefulWidget { + /// Creates a new instance of [StreamTabbedAttachmentPicker]. + const StreamTabbedAttachmentPicker({ super.key, required this.options, required this.controller, this.initialOption, - this.onSendValue, }); /// The list of options. @@ -75,15 +81,11 @@ class StreamTabbedAttachmentPickerBottomSheet extends StatefulWidget { /// The controller of the attachment picker. final StreamAttachmentPickerController controller; - /// The callback when the send button gets tapped. - final ValueSetter? onSendValue; - @override - State createState() => _StreamTabbedAttachmentPickerBottomSheetState(); + State createState() => _StreamTabbedAttachmentPickerState(); } -class _StreamTabbedAttachmentPickerBottomSheetState extends State { - // The current option selected in the tabbed attachment picker. +class _StreamTabbedAttachmentPickerState extends State { late var _currentOption = _calculateInitialOption(); TabbedAttachmentPickerOption _calculateInitialOption() { if (widget.initialOption case final option?) return option; @@ -105,13 +107,13 @@ class _StreamTabbedAttachmentPickerBottomSheetState extends State[ _TabbedAttachmentPickerOptions( controller: widget.controller, options: widget.options, currentOption: _currentOption, - onSendValue: widget.onSendValue, onOptionSelected: (option) async { setState(() => _currentOption = option); }, @@ -135,84 +137,51 @@ class _TabbedAttachmentPickerOptions extends StatelessWidget { required this.currentOption, required this.controller, this.onOptionSelected, - this.onSendValue, }); final Iterable options; final TabbedAttachmentPickerOption currentOption; final StreamAttachmentPickerController controller; final ValueSetter? onOptionSelected; - final ValueSetter? onSendValue; @override Widget build(BuildContext context) { - final colorTheme = StreamChatTheme.of(context).colorTheme; + final spacing = context.streamSpacing; + return ValueListenableBuilder( valueListenable: controller, builder: (context, value, __) { final enabledTypes = value.filterEnabledTypes(options: options); - return Row( - children: [ - Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: [ - ...options.map( - (option) { - final supported = option.supportedTypes; - final isEnabled = enabledTypes.any(supported.contains); - final isSelected = option == currentOption; - - final color = switch (isSelected) { - true => colorTheme.accentPrimary, - _ => colorTheme.textLowEmphasis, - }; - - final onPressed = switch (isEnabled) { - true => () => onOptionSelected?.call(option), - _ => null, - }; - - return IconButton( - color: color, - disabledColor: colorTheme.disabled, - icon: option.icon, - onPressed: onPressed, - ); - }, - ), - ], - ), + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + padding: EdgeInsets.symmetric(horizontal: spacing.md, vertical: spacing.sm), + child: Row( + spacing: spacing.xxxs, + children: [ + ...options.map( + (option) { + final supported = option.supportedTypes; + final isEnabled = enabledTypes.any(supported.contains); + final isSelected = option == currentOption; + + final onPressed = switch (isEnabled) { + true => () => onOptionSelected?.call(option), + _ => null, + }; + + return StreamButton.icon( + style: StreamButtonStyle.secondary, + type: StreamButtonType.ghost, + size: StreamButtonSize.large, + icon: option.icon, + onTap: onPressed, + isSelected: isSelected, + ); + }, ), - ), - Builder( - builder: (context) { - final initialValue = controller.initialValue; - final isValueChanged = value != initialValue; - - final onPressed = switch (onSendValue) { - final onSendValue? when isValueChanged => () { - final result = AttachmentsPicked( - attachments: value.attachments, - ); - return onSendValue(result); - }, - _ => null, - }; - - return IconButton( - color: colorTheme.accentPrimary, - disabledColor: colorTheme.disabled, - icon: Icon( - context.streamIcons.chevronRight, - ), - onPressed: onPressed, - ); - }, - ), - ], + ], + ), ); }, ); @@ -246,7 +215,7 @@ class EndOfFrameCallbackWidget extends StatefulWidget { /// The widget below this widget in the tree. final Widget? child; - /// The callback that is called when the end of the frame is reached.x + /// The callback that is called when the end of the frame is reached. final EndOfFrameCallback onEndOfFrame; /// The callback that will be called if the [onEndOfFrame] callback throws an @@ -291,8 +260,6 @@ class _EndOfFrameCallbackWidgetState extends State { return const Text('An error occurred'); } - // Reset the error and stack trace so that we don't keep showing the same - // error over and over. _error = null; _stackTrace = null; @@ -357,9 +324,7 @@ class OptionDrawer extends StatelessWidget { @override Widget build(BuildContext context) { - final colorTheme = StreamChatTheme.of(context).colorTheme; - - var height = 20.0; + var height = 0.0; if (title != null || actions.isNotEmpty) { height = 40.0; } @@ -378,34 +343,20 @@ class OptionDrawer extends StatelessWidget { trailing = const Empty(); } - return Card( - elevation: elevation, - color: color ?? colorTheme.barsBg, - margin: margin, - shape: shape, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - height: height, - child: Row( - children: [ - Expanded(child: leading), - Container( - height: 4, - width: 40, - decoration: BoxDecoration( - color: colorTheme.disabled, - borderRadius: BorderRadius.circular(6), - ), - ), - Expanded(child: trailing), - ], - ), + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: height, + child: Row( + children: [ + Expanded(child: leading), + Expanded(child: trailing), + ], ), - Expanded(child: child), - ], - ), + ), + Expanded(child: child), + ], ); } } @@ -418,9 +369,13 @@ class OptionDrawer extends StatelessWidget { /// attachment. Tabs get enabled or disabled based on what you've already /// selected. /// -/// This is the default interface for mobile platforms. Configure with -/// [customOptions], [galleryPickerConfig], [pollConfig], and -/// [allowedTypes]. +/// Selections are applied in real-time via the [controller]. +/// +/// The [onError] callback is invoked when an error occurs during attachment +/// selection (e.g., file too large or attachment limit reached). +/// +/// The [onPollCreated] callback is invoked when a poll is created, allowing +/// the caller to handle poll-specific logic. Widget tabbedAttachmentPickerBuilder({ required BuildContext context, required StreamAttachmentPickerController controller, @@ -428,23 +383,13 @@ Widget tabbedAttachmentPickerBuilder({ GalleryPickerConfig? galleryPickerConfig, List allowedTypes = AttachmentPickerType.values, AttachmentPickerOptionsBuilder? optionsBuilder, + ValueSetter? onError, + ValueSetter? onPollCreated, }) { - Future _handleSingePick( - StreamAttachmentPickerController controller, - Attachment? attachment, - ) async { - try { - if (attachment != null) await controller.addAttachment(attachment); - return AttachmentsPicked(attachments: controller.value.attachments); - } catch (error, stk) { - return AttachmentPickerError(error: error, stackTrace: stk); - } - } - final defaultOptions = [ TabbedAttachmentPickerOption( key: 'gallery-picker', - icon: Icon(context.streamIcons.images1Alt), + icon: context.streamIcons.images1Alt, supportedTypes: [ AttachmentPickerType.images, AttachmentPickerType.videos, @@ -462,8 +407,9 @@ Widget tabbedAttachmentPickerBuilder({ } return await controller.addAssetAttachment(media); } catch (e, stk) { - final err = AttachmentPickerError(error: e, stackTrace: stk); - return Navigator.pop(context, err); + onError?.call( + AttachmentPickerError(error: e, stackTrace: stk), + ); } }, ); @@ -471,40 +417,55 @@ Widget tabbedAttachmentPickerBuilder({ ), TabbedAttachmentPickerOption( key: 'file-picker', - icon: Icon(context.streamIcons.fileBend), + icon: context.streamIcons.fileBend, supportedTypes: [AttachmentPickerType.files], optionViewBuilder: (context, controller) => StreamFilePicker( onFilePicked: (file) async { - final result = await _handleSingePick(controller, file); - return Navigator.pop(context, result); + try { + if (file != null) await controller.addAttachment(file); + } catch (e, stk) { + onError?.call( + AttachmentPickerError(error: e, stackTrace: stk), + ); + } }, ), ), TabbedAttachmentPickerOption( key: 'image-picker', - icon: Icon(context.streamIcons.camera1), + icon: context.streamIcons.camera1, supportedTypes: [AttachmentPickerType.images], optionViewBuilder: (context, controller) => StreamImagePicker( onImagePicked: (image) async { - final result = await _handleSingePick(controller, image); - return Navigator.pop(context, result); + try { + if (image != null) await controller.addAttachment(image); + } catch (e, stk) { + onError?.call( + AttachmentPickerError(error: e, stackTrace: stk), + ); + } }, ), ), TabbedAttachmentPickerOption( key: 'video-picker', - icon: Icon(context.streamIcons.video), + icon: context.streamIcons.video, supportedTypes: [AttachmentPickerType.videos], optionViewBuilder: (context, controller) => StreamVideoPicker( onVideoPicked: (video) async { - final result = await _handleSingePick(controller, video); - return Navigator.pop(context, result); + try { + if (video != null) await controller.addAttachment(video); + } catch (e, stk) { + onError?.call( + AttachmentPickerError(error: e, stackTrace: stk), + ); + } }, ), ), TabbedAttachmentPickerOption( key: 'poll-creator', - icon: Icon(context.streamIcons.chart5), + icon: context.streamIcons.chart5, supportedTypes: [AttachmentPickerType.poll], optionViewBuilder: (context, controller) { final initialPoll = controller.value.poll; @@ -512,11 +473,9 @@ Widget tabbedAttachmentPickerBuilder({ poll: initialPoll, config: pollConfig, onPollCreated: (poll) { - if (poll == null) return Navigator.pop(context); + if (poll == null) return; controller.poll = poll; - - final result = PollCreated(poll: poll); - return Navigator.pop(context, result); + onPollCreated?.call(poll); }, ); }, @@ -537,9 +496,8 @@ Widget tabbedAttachmentPickerBuilder({ ); } - return StreamTabbedAttachmentPickerBottomSheet( + return StreamTabbedAttachmentPicker( controller: controller, - onSendValue: Navigator.of(context).pop, options: { ...validOptions.where( (option) => option.supportedTypes.every(allowedTypes.contains), @@ -554,9 +512,12 @@ Widget tabbedAttachmentPickerBuilder({ /// built-in file browser, camera app, or other native tools instead of /// custom interfaces. /// -/// This is the default for web and desktop platforms, and can be enabled on -/// mobile with `useSystemAttachmentPicker`. Configure with [customOptions], -/// [pollConfig], and [allowedTypes]. +/// Selections are applied in real-time via the [controller]. +/// +/// The [onError] callback is invoked when an error occurs during attachment +/// selection. +/// +/// The [onPollCreated] callback is invoked when a poll is created. Widget systemAttachmentPickerBuilder({ required BuildContext context, required StreamAttachmentPickerController controller, @@ -564,18 +525,18 @@ Widget systemAttachmentPickerBuilder({ GalleryPickerConfig? galleryPickerConfig = const GalleryPickerConfig(), List allowedTypes = AttachmentPickerType.values, AttachmentPickerOptionsBuilder? optionsBuilder, + ValueSetter? onError, + ValueSetter? onPollCreated, }) { - Future _pickSystemFile( + Future pickSystemFile( StreamAttachmentPickerController controller, FileType type, ) async { try { final file = await StreamAttachmentHandler.instance.pickFile(type: type); if (file != null) await controller.addAttachment(file); - - return AttachmentsPicked(attachments: controller.value.attachments); - } catch (error, stk) { - return AttachmentPickerError(error: error, stackTrace: stk); + } catch (e, stk) { + onError?.call(AttachmentPickerError(error: e, stackTrace: stk)); } } @@ -583,37 +544,34 @@ Widget systemAttachmentPickerBuilder({ SystemAttachmentPickerOption( key: 'image-picker', supportedTypes: [AttachmentPickerType.images], - icon: Icon(context.streamIcons.images1Alt), + icon: context.streamIcons.images1Alt, title: context.translations.uploadAPhotoLabel, onTap: (context, controller) async { - final result = await _pickSystemFile(controller, FileType.image); - return Navigator.pop(context, result); + await pickSystemFile(controller, FileType.image); }, ), SystemAttachmentPickerOption( key: 'video-picker', supportedTypes: [AttachmentPickerType.videos], - icon: Icon(context.streamIcons.video), + icon: context.streamIcons.video, title: context.translations.uploadAVideoLabel, onTap: (context, controller) async { - final result = await _pickSystemFile(controller, FileType.video); - return Navigator.pop(context, result); + await pickSystemFile(controller, FileType.video); }, ), SystemAttachmentPickerOption( key: 'file-picker', supportedTypes: [AttachmentPickerType.files], - icon: Icon(context.streamIcons.fileBend), + icon: context.streamIcons.fileBend, title: context.translations.uploadAFileLabel, onTap: (context, controller) async { - final result = await _pickSystemFile(controller, FileType.any); - return Navigator.pop(context, result); + await pickSystemFile(controller, FileType.any); }, ), SystemAttachmentPickerOption( key: 'poll-creator', supportedTypes: [AttachmentPickerType.poll], - icon: Icon(context.streamIcons.chart5), + icon: context.streamIcons.chart5, title: context.translations.createPollLabel(isNew: true), onTap: (context, controller) async { final initialPoll = controller.value.poll; @@ -623,11 +581,9 @@ Widget systemAttachmentPickerBuilder({ config: pollConfig, ); - if (poll == null) return Navigator.pop(context); + if (poll == null) return; controller.poll = poll; - - final result = PollCreated(poll: poll); - return Navigator.pop(context, result); + onPollCreated?.call(poll); }, ), ]; @@ -646,7 +602,7 @@ Widget systemAttachmentPickerBuilder({ ); } - return StreamSystemAttachmentPickerBottomSheet( + return StreamSystemAttachmentPicker( controller: controller, options: { ...validOptions.where( diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart index 13d309ff64..4ae640eb6d 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart @@ -1,8 +1,5 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/message_input/attachment_picker/options/stream_gallery_picker.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/src/message_input/attachment_picker/stream_attachment_picker_option.dart'; /// {@template streamAttachmentPickerOptionsBuilder} /// Signature for a function that creates a list of [AttachmentPickerOption]s @@ -13,230 +10,3 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@endtemplate} typedef AttachmentPickerOptionsBuilder = List Function(BuildContext context, List defaultOptions); - -/// Shows a modal bottom sheet with the Stream attachment picker. -/// -/// The picker supports two modes: -/// -/// - **Tabbed interface**: Typically used on mobile platforms. Provide -/// [TabbedAttachmentPickerOption] values in [customOptions]. This mode is -/// active when [useSystemAttachmentPicker] is false (default). -/// -/// - **System integration**: Used on web, desktop, or when -/// [useSystemAttachmentPicker] is true. Provide -/// [SystemAttachmentPickerOption] values in [customOptions]. -/// -/// When using the system picker, all [customOptions] must be -/// [SystemAttachmentPickerOption] instances. If any other type is included, -/// an [ArgumentError] is thrown. -/// -/// Example using the tabbed interface: -/// ```dart -/// showStreamAttachmentPickerModalBottomSheet( -/// context: context, -/// customOptions: [ -/// TabbedAttachmentPickerOption( -/// key: 'gallery', -/// icon: Icon(Icons.photo), -/// supportedTypes: [AttachmentPickerType.images], -/// optionViewBuilder: (context, controller) { -/// return CustomGalleryWidget(); -/// }, -/// ), -/// ], -/// ); -/// ``` -/// -/// Example using the system picker: -/// ```dart -/// showStreamAttachmentPickerModalBottomSheet( -/// context: context, -/// useSystemAttachmentPicker: true, -/// customOptions: [ -/// SystemAttachmentPickerOption( -/// key: 'upload', -/// type: AttachmentPickerType.files, -/// icon: Icon(Icons.upload_file), -/// title: 'Upload File', -/// onTap: (context, controller) async { -/// // Handle file picker -/// }, -/// ), -/// ], -/// ); -/// ``` -/// -/// Returns a [Future] that completes with the value passed to [Navigator.pop], -/// or `null` if the sheet was dismissed. -Future showStreamAttachmentPickerModalBottomSheet({ - required BuildContext context, - AttachmentPickerOptionsBuilder? optionsBuilder, - List allowedTypes = AttachmentPickerType.values, - Poll? initialPoll, - PollConfig? pollConfig, - GalleryPickerConfig? galleryPickerConfig, - List? initialAttachments, - Map? initialExtraData, - StreamAttachmentPickerController? controller, - Color? backgroundColor, - double? elevation, - BoxConstraints? constraints, - Color? barrierColor, - bool isScrollControlled = false, - bool useRootNavigator = false, - bool isDismissible = true, - bool enableDrag = true, - bool useSystemAttachmentPicker = false, - RouteSettings? routeSettings, - AnimationController? transitionAnimationController, - Clip? clipBehavior = Clip.hardEdge, - ShapeBorder? shape, -}) { - final colorTheme = StreamChatTheme.of(context).colorTheme; - final color = backgroundColor ?? colorTheme.inputBg; - - return showModalBottomSheet( - context: context, - backgroundColor: color, - elevation: elevation, - shape: shape, - clipBehavior: clipBehavior, - constraints: constraints, - barrierColor: barrierColor, - isScrollControlled: isScrollControlled, - useRootNavigator: useRootNavigator, - isDismissible: isDismissible, - enableDrag: enableDrag, - routeSettings: routeSettings, - transitionAnimationController: transitionAnimationController, - builder: (BuildContext context) { - return StreamAttachmentPickerBottomSheetBuilder( - controller: controller, - initialPoll: initialPoll, - initialAttachments: initialAttachments, - initialExtraData: initialExtraData, - builder: (context, controller, child) { - final isWebOrDesktop = switch (CurrentPlatform.type) { - PlatformType.android || PlatformType.ios => false, - _ => true, - }; - - final useSystemPicker = useSystemAttachmentPicker || isWebOrDesktop; - - final builder = switch (useSystemPicker) { - true => systemAttachmentPickerBuilder, - false => tabbedAttachmentPickerBuilder, - }; - - return builder.call( - context: context, - controller: controller, - allowedTypes: allowedTypes, - pollConfig: pollConfig, - galleryPickerConfig: galleryPickerConfig, - optionsBuilder: optionsBuilder, - ); - }, - ); - }, - ); -} - -/// Builds the attachment picker bottom sheet. -class StreamAttachmentPickerBottomSheetBuilder extends StatefulWidget { - /// Creates a new instance of the widget. - const StreamAttachmentPickerBottomSheetBuilder({ - super.key, - this.initialPoll, - this.initialAttachments, - this.initialExtraData, - this.child, - this.controller, - required this.builder, - }); - - /// The child widget. - final Widget? child; - - /// Builder for the attachment picker bottom sheet. - final Widget Function( - BuildContext context, - StreamAttachmentPickerController controller, - Widget? child, - ) - builder; - - /// The initial poll. - final Poll? initialPoll; - - /// The initial attachments. - final List? initialAttachments; - - /// The initial extra data for the attachment picker. - final Map? initialExtraData; - - /// The controller. - final StreamAttachmentPickerController? controller; - - @override - State createState() => _StreamAttachmentPickerBottomSheetBuilderState(); -} - -class _StreamAttachmentPickerBottomSheetBuilderState extends State { - late StreamAttachmentPickerController _controller; - - @override - void initState() { - super.initState(); - _controller = - widget.controller ?? - StreamAttachmentPickerController( - initialPoll: widget.initialPoll, - initialAttachments: widget.initialAttachments, - initialExtraData: widget.initialExtraData, - ); - } - - // Handle a potential change in StreamAttachmentPickerController by properly - // disposing of the old one and setting up the new one, if needed. - void _updateAttachmentPickerController( - StreamAttachmentPickerController? old, - StreamAttachmentPickerController? current, - ) { - if ((old == null && current == null) || old == current) return; - if (old == null) { - _controller.dispose(); - _controller = current!; - } else if (current == null) { - _controller = StreamAttachmentPickerController( - initialPoll: widget.initialPoll, - initialAttachments: widget.initialAttachments, - initialExtraData: widget.initialExtraData, - ); - } else { - _controller = current; - } - } - - @override - void didUpdateWidget( - StreamAttachmentPickerBottomSheetBuilder oldWidget, - ) { - super.didUpdateWidget(oldWidget); - _updateAttachmentPickerController( - oldWidget.controller, - widget.controller, - ); - } - - @override - void dispose() { - if (widget.controller == null) _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return widget.builder(context, _controller, widget.child); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_option.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_option.dart index b5d7ca6d3b..ee618d84f3 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_option.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_option.dart @@ -31,7 +31,7 @@ abstract class AttachmentPickerOption { final String? key; /// The icon of the option. - final Widget icon; + final IconData icon; /// The title of the option. final String? title; diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart index 46ef35e676..96c744809a 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart @@ -524,6 +524,10 @@ class StreamMessageInputState extends State with Restoration bool get _commandEnabled => _effectiveController.message.command != null; bool _actionsShrunk = false; + bool _isPickerVisible = false; + + StreamAttachmentPickerController? _pickerController; + bool _isSyncingControllers = false; late StreamChatThemeData _streamChatTheme; late StreamMessageInputThemeData _messageInputTheme; @@ -645,8 +649,14 @@ class StreamMessageInputState extends State with Restoration @override String? get restorationId => widget.restorationId; - // ignore: no-empty-block - void _focusNodeListener() {} + void _focusNodeListener() { + if (_effectiveFocusNode.hasFocus && _isPickerVisible) { + setState(() { + _isPickerVisible = false; + _closePicker(); + }); + } + } @override Widget build(BuildContext context) { @@ -685,6 +695,8 @@ class StreamMessageInputState extends State with Restoration final shadow = widget.shadow ?? _messageInputTheme.shadow; final elevation = widget.elevation ?? _messageInputTheme.elevation; + final spacing = context.streamSpacing; + return Portal( child: Material( elevation: elevation ?? 8, @@ -694,7 +706,8 @@ class StreamMessageInputState extends State with Restoration boxShadow: [if (shadow != null) shadow], ), child: SimpleSafeArea( - enabled: widget.enableSafeArea ?? _messageInputTheme.enableSafeArea, + enabled: !_isPickerVisible && (widget.enableSafeArea ?? _messageInputTheme.enableSafeArea ?? true), + minimum: EdgeInsets.only(bottom: _isPickerVisible ? 0 : spacing.md), child: Center(heightFactor: 1, child: messageInput), ), ), @@ -768,15 +781,60 @@ class StreamMessageInputState extends State with Restoration return StreamMessageValueListenableBuilder( valueListenable: controller, - builder: (context, value, _) => StreamChatMessageComposer( - controller: controller, - currentUserId: currentUserId, - onAttachmentButtonPressed: _onAttachmentButtonPressed, - placeholder: _getHint(context) ?? '', - focusNode: focusNode, - onSendPressed: sendMessage, - audioRecorderController: _audioRecorderController, + builder: (context, value, _) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + StreamChatMessageComposer( + controller: controller, + currentUserId: currentUserId, + onAttachmentButtonPressed: _onAttachmentButtonPressed, + isPickerOpen: _isPickerVisible, + placeholder: _getHint(context) ?? '', + focusNode: focusNode, + onSendPressed: sendMessage, + audioRecorderController: _audioRecorderController, + ), + if (_isPickerVisible && _pickerController != null) _buildInlineAttachmentPicker(context), + ], + ), + ); + } + + Widget _buildInlineAttachmentPicker(BuildContext context) { + final allowedTypes = _getAllowedAttachmentPickerTypes(); + + final messageInputTheme = StreamMessageInputTheme.of(context); + final isWebOrDesktop = switch (CurrentPlatform.type) { + PlatformType.android || PlatformType.ios => false, + _ => true, + }; + final useSystemPicker = + widget.useSystemAttachmentPicker || (messageInputTheme.useSystemAttachmentPicker ?? false) || isWebOrDesktop; + + final builder = switch (useSystemPicker) { + true => () => systemAttachmentPickerBuilder( + context: context, + controller: _pickerController!, + allowedTypes: allowedTypes, + pollConfig: widget.pollConfig, + optionsBuilder: widget.attachmentPickerOptionsBuilder, + onError: _onPickerError, + onPollCreated: _onPollCreated, + ), + false => () => tabbedAttachmentPickerBuilder( + context: context, + controller: _pickerController!, + allowedTypes: allowedTypes, + pollConfig: widget.pollConfig, + optionsBuilder: widget.attachmentPickerOptionsBuilder, + onError: _onPickerError, + onPollCreated: _onPollCreated, ), + }; + + return SizedBox( + height: MediaQuery.of(context).size.height * 0.4, + child: builder(), ); } @@ -972,6 +1030,14 @@ class StreamMessageInputState extends State with Restoration } Future _onPollCreated(Poll poll) async { + // Close the picker after poll creation. + if (_isPickerVisible) { + setState(() { + _isPickerVisible = false; + _closePicker(); + }); + } + final channel = StreamChannel.maybeOf(context)?.channel; if (channel == null) return; @@ -997,52 +1063,70 @@ class StreamMessageInputState extends State with Restoration return allowedTypes.toList(growable: false); } - /// Handle the platform-specific logic for selecting files. - /// - /// On mobile, this will open the file selection bottom sheet. On desktop, - /// this will open the native file system and allow the user to select one - /// or more files. - Future _onAttachmentButtonPressed() async { - final initialPoll = _effectiveController.poll; - final initialAttachments = _effectiveController.attachments; - final allowedTypes = _getAllowedAttachmentPickerTypes(); - - final messageInputTheme = StreamMessageInputTheme.of(context); - final useSystemPicker = widget.useSystemAttachmentPicker || (messageInputTheme.useSystemAttachmentPicker ?? false); + /// Toggles the inline attachment picker visibility. + void _onAttachmentButtonPressed() { + setState(() { + _isPickerVisible = !_isPickerVisible; + if (_isPickerVisible) { + _openPicker(); + } else { + _closePicker(); + } + }); + } - final result = await showStreamAttachmentPickerModalBottomSheet( - context: context, - allowedTypes: allowedTypes, - initialPoll: initialPoll, - pollConfig: widget.pollConfig, - initialAttachments: initialAttachments, - useSystemAttachmentPicker: useSystemPicker, - optionsBuilder: widget.attachmentPickerOptionsBuilder, + Future _openPicker() async { + if (_effectiveFocusNode.hasFocus) { + _effectiveFocusNode.unfocus(); + await Future.delayed(const Duration(milliseconds: 30)); + } + _pickerController = StreamAttachmentPickerController( + initialAttachments: _effectiveController.attachments, + initialPoll: _effectiveController.poll, ); + _pickerController!.addListener(_onPickerControllerChanged); + _effectiveController.addListener(_onMessageInputControllerChanged); + } - if (result == null || result is! StreamAttachmentPickerResult) return; - - // Returns early if the result is already handled by the user. - final resultHandled = await widget.onAttachmentPickerResult?.call(result); - if (resultHandled ?? false) return; + void _closePicker() { + _pickerController?.removeListener(_onPickerControllerChanged); + _effectiveController.removeListener(_onMessageInputControllerChanged); + _pickerController?.dispose(); + _pickerController = null; + } - void _onAttachmentsPicked(List attachments) { - _effectiveController.attachments = attachments; + void _onPickerControllerChanged() { + if (_isSyncingControllers) return; + _isSyncingControllers = true; + try { + final pickerAttachments = _pickerController?.value.attachments ?? []; + _effectiveController.attachments = pickerAttachments; + } finally { + _isSyncingControllers = false; } + } + + void _onMessageInputControllerChanged() { + if (_isSyncingControllers) return; + _isSyncingControllers = true; + try { + final pickerController = _pickerController; + if (pickerController == null) return; + + final messageAttachmentIds = _effectiveController.attachments.map((a) => a.id).toSet(); + final pickerAttachmentIds = pickerController.value.attachments.map((a) => a.id).toSet(); - void _onAttachmentPickerError(AttachmentPickerError error) { - return widget.onError?.call(error.error, error.stackTrace); + // Remove attachments from picker that were removed from the composer. + for (final id in pickerAttachmentIds.difference(messageAttachmentIds)) { + pickerController.removeAttachmentById(id); + } + } finally { + _isSyncingControllers = false; } + } - return switch (result) { - // Add the attachments to the controller. - AttachmentsPicked() => _onAttachmentsPicked(result.attachments), - // Send the created poll in the channel. - PollCreated() => _onPollCreated(result.poll), - // Handle/Notify returned errors. - AttachmentPickerError() => _onAttachmentPickerError(result), - _ => Future.value(), // Ignore other results. - }; + void _onPickerError(AttachmentPickerError error) { + widget.onError?.call(error.error, error.stackTrace); } Widget _buildTextInput(BuildContext context) { @@ -1474,6 +1558,14 @@ class StreamMessageInputState extends State with Restoration if (_effectiveController.isSlowModeActive) return; if (!widget.validator(_effectiveController.message)) return; + // Close the picker when sending a message. + if (_isPickerVisible) { + setState(() { + _isPickerVisible = false; + _closePicker(); + }); + } + final streamChannel = StreamChannel.maybeOf(context); if (streamChannel == null) return; @@ -1631,6 +1723,7 @@ class StreamMessageInputState extends State with Restoration @override void dispose() { + _closePicker(); _effectiveController.removeListener(_onChangedDebounced); _controller?.dispose(); _effectiveFocusNode.removeListener(_focusNodeListener); diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart index ecdaadd460..a9edd1a23d 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart @@ -100,28 +100,22 @@ class StreamPhotoGalleryTile extends StatelessWidget { child: AnimatedOpacity( duration: const Duration(milliseconds: 300), opacity: selected ? 1.0 : 0.0, - child: Container( - color: - // ignore: deprecated_member_use - chatThemeData.colorTheme.textHighEmphasis.withOpacity(0.5), - alignment: Alignment.topRight, - padding: const EdgeInsets.only( - top: 8, - right: 8, - ), - child: CircleAvatar( - radius: 12, - backgroundColor: chatThemeData.colorTheme.barsBg, - child: Icon( - context.streamIcons.checkmark2, - size: 24, - color: chatThemeData.colorTheme.textHighEmphasis, - ), + child: DecoratedBox( + decoration: BoxDecoration( + // ignore: deprecated_member_use + color: chatThemeData.colorTheme.textHighEmphasis.withOpacity(0.15), ), ), ), ), ), + Positioned( + top: 8, + right: 8, + child: IgnorePointer( + child: _GallerySelectedIndicator(selected: selected), + ), + ), if (media.type == AssetType.video) ...[ Positioned( left: 8, @@ -154,6 +148,34 @@ class StreamPhotoGalleryTile extends StatelessWidget { } } +class _GallerySelectedIndicator extends StatelessWidget { + const _GallerySelectedIndicator({required this.selected}); + + final bool selected; + + @override + Widget build(BuildContext context) { + return AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: 24, + height: 24, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: selected ? const Color(0xFF005FFF) : Colors.transparent, + border: Border.all(color: Colors.white, width: 2), + ), + child: selected + ? Icon( + context.streamIcons.checkmark1Small, + fontWeight: FontWeight.w900, + size: 12, + color: Colors.white, + ) + : null, + ); + } +} + extension on Duration { String format() { final s = '$this'.split('.')[0].padLeft(8, '0'); diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 410169f2d5..b0e39d39d2 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 77df1314fb3be17e3bc711996421699fc7b521a2 + ref: 0d5268a506dbc045fcdb8ac901bb2fc70367a3af path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 diff --git a/packages/stream_chat_flutter/test/src/message_input/attachment_picker/stream_attachment_picker_test.dart b/packages/stream_chat_flutter/test/src/message_input/attachment_picker/stream_attachment_picker_test.dart index 71f3b67ca8..ca2ff3baf9 100644 --- a/packages/stream_chat_flutter/test/src/message_input/attachment_picker/stream_attachment_picker_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/attachment_picker/stream_attachment_picker_test.dart @@ -3,37 +3,38 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() { - group('showStreamAttachmentPickerModalBottomSheet', () { - group('attachmentPickerOptionsBuilder', () { + group('tabbedAttachmentPickerBuilder', () { + group('optionsBuilder', () { testWidgets( 'should call optionsBuilder with default options', (tester) async { var builderCalled = false; int? defaultOptionsCount; + final controller = StreamAttachmentPickerController(); + addTearDown(controller.dispose); + await tester.pumpWidget( _wrapWithStreamChatApp( Builder( builder: (context) { - return ElevatedButton( - onPressed: () { - showStreamAttachmentPickerModalBottomSheet( - context: context, - optionsBuilder: (context, defaultOptions) { - builderCalled = true; - defaultOptionsCount = defaultOptions.length; - return defaultOptions; - }, - ); - }, - child: const Text('Show Picker'), + return SizedBox( + height: 400, + child: tabbedAttachmentPickerBuilder( + context: context, + controller: controller, + optionsBuilder: (context, defaultOptions) { + builderCalled = true; + defaultOptionsCount = defaultOptions.length; + return defaultOptions; + }, + ), ); }, ), ), ); - await tester.tap(find.text('Show Picker')); await tester.pumpAndSettle(); expect(builderCalled, isTrue); @@ -47,330 +48,263 @@ void main() { (tester) async { int? defaultOptionsCount; + final controller = StreamAttachmentPickerController(); + addTearDown(controller.dispose); + await tester.pumpWidget( _wrapWithStreamChatApp( Builder( builder: (context) { - return ElevatedButton( - onPressed: () { - showStreamAttachmentPickerModalBottomSheet( - context: context, - optionsBuilder: (context, defaultOptions) { - defaultOptionsCount = defaultOptions.length; - // Return only first option - return [defaultOptions.first]; - }, - ); - }, - child: const Text('Show Picker'), + return SizedBox( + height: 400, + child: tabbedAttachmentPickerBuilder( + context: context, + controller: controller, + optionsBuilder: (context, defaultOptions) { + defaultOptionsCount = defaultOptions.length; + return [defaultOptions.first]; + }, + ), ); }, ), ), ); - await tester.tap(find.text('Show Picker')); await tester.pumpAndSettle(); - final bottomSheet = tester.widget( - find.byType(StreamSystemAttachmentPickerBottomSheet), + final picker = tester.widget( + find.byType(StreamTabbedAttachmentPicker), ); - expect(bottomSheet.options.length, equals(1)); - expect(bottomSheet.options.length, lessThan(defaultOptionsCount!)); + expect(picker.options.length, equals(1)); + expect(picker.options.length, lessThan(defaultOptionsCount!)); }, ); testWidgets( - 'should allow adding custom options', + 'should throw ArgumentError when wrong option types are provided', (tester) async { - int? defaultOptionsCount; - const customOptionKey = 'custom-location'; + final controller = StreamAttachmentPickerController(); + addTearDown(controller.dispose); await tester.pumpWidget( _wrapWithStreamChatApp( Builder( builder: (context) { - return ElevatedButton( - onPressed: () { - showStreamAttachmentPickerModalBottomSheet( - context: context, - optionsBuilder: (context, defaultOptions) { - defaultOptionsCount = defaultOptions.length; - return [ - ...defaultOptions, - SystemAttachmentPickerOption( - key: customOptionKey, - icon: const Icon(Icons.location_on), - supportedTypes: [AttachmentPickerType.images], - title: 'Send Location', - onTap: (context, controller) async {}, - ), - ]; - }, - ); - }, - child: const Text('Show Picker'), + return SizedBox( + height: 400, + child: tabbedAttachmentPickerBuilder( + context: context, + controller: controller, + optionsBuilder: (context, defaultOptions) { + return [ + SystemAttachmentPickerOption( + key: 'wrong', + icon: Icons.error, + title: 'Wrong', + supportedTypes: [AttachmentPickerType.images], + onTap: (context, controller) async {}, + ), + ]; + }, + ), ); }, ), ), ); - await tester.tap(find.text('Show Picker')); - await tester.pumpAndSettle(); - - final bottomSheet = tester.widget( - find.byType(StreamSystemAttachmentPickerBottomSheet), - ); - - // Should have one more option than default - expect(bottomSheet.options.length, equals(defaultOptionsCount! + 1)); - - // Verify our custom option exists - expect( - bottomSheet.options.any((option) => option.key == customOptionKey), - isTrue, - ); + expect(tester.takeException(), isA()); }, ); + }); + group('allowedTypes', () { testWidgets( - 'should allow reordering options', + 'should filter options based on allowedTypes', (tester) async { - String? firstDefaultKey; - String? firstReversedKey; + final controller = StreamAttachmentPickerController(); + addTearDown(controller.dispose); await tester.pumpWidget( _wrapWithStreamChatApp( Builder( builder: (context) { - return ElevatedButton( - onPressed: () { - showStreamAttachmentPickerModalBottomSheet( - context: context, - optionsBuilder: (context, defaultOptions) { - firstDefaultKey = defaultOptions.first.key; - final reversed = defaultOptions.reversed.toList(); - firstReversedKey = reversed.first.key; - return reversed; - }, - ); - }, - child: const Text('Show Picker'), + return SizedBox( + height: 400, + child: tabbedAttachmentPickerBuilder( + context: context, + controller: controller, + allowedTypes: [AttachmentPickerType.images], + ), ); }, ), ), ); - await tester.tap(find.text('Show Picker')); await tester.pumpAndSettle(); - // Verify first option changed after reversing - expect(firstDefaultKey, isNotNull); - expect(firstReversedKey, isNotNull); - expect(firstDefaultKey, isNot(equals(firstReversedKey))); - }, - ); + final picker = tester.widget( + find.byType(StreamTabbedAttachmentPicker), + ); - testWidgets( - 'should throw ArgumentError when wrong option types are provided', - (tester) async { - await tester.pumpWidget( - _wrapWithStreamChatApp( - Builder( - builder: (context) { - return ElevatedButton( - onPressed: () { - showStreamAttachmentPickerModalBottomSheet( - context: context, - optionsBuilder: (context, defaultOptions) { - // Return tabbed option for system picker (wrong type) - return [ - TabbedAttachmentPickerOption( - key: 'wrong', - icon: const Icon(Icons.error), - title: 'Wrong', - supportedTypes: [AttachmentPickerType.images], - optionViewBuilder: (context, controller) { - return const Text('Wrong'); - }, - ), - ]; - }, - ); - }, - child: const Text('Show Picker'), - ); - }, - ), + expect( + picker.options.every( + (option) => option.supportedTypes.contains(AttachmentPickerType.images), ), + isTrue, ); - - await tester.tap(find.text('Show Picker')); - - // Should throw ArgumentError - await tester.pumpAndSettle(); - - expect(tester.takeException(), isA()); }, ); }); + }); - group('allowedTypes', () { + group('systemAttachmentPickerBuilder', () { + group('optionsBuilder', () { testWidgets( - 'should filter options based on allowedTypes', + 'should call optionsBuilder with default options', (tester) async { + var builderCalled = false; + + final controller = StreamAttachmentPickerController(); + addTearDown(controller.dispose); + await tester.pumpWidget( _wrapWithStreamChatApp( Builder( builder: (context) { - return ElevatedButton( - onPressed: () { - showStreamAttachmentPickerModalBottomSheet( - context: context, - allowedTypes: [AttachmentPickerType.images], - ); + return systemAttachmentPickerBuilder( + context: context, + controller: controller, + optionsBuilder: (context, defaultOptions) { + builderCalled = true; + return defaultOptions; }, - child: const Text('Show Picker'), ); }, ), ), ); - await tester.tap(find.text('Show Picker')); await tester.pumpAndSettle(); - final bottomSheet = tester.widget( - find.byType(StreamSystemAttachmentPickerBottomSheet), - ); - - // All options should support images + expect(builderCalled, isTrue); expect( - bottomSheet.options.every( - (option) => option.supportedTypes.contains(AttachmentPickerType.images), - ), - isTrue, + find.byType(StreamSystemAttachmentPicker), + findsOneWidget, ); }, ); testWidgets( - 'should work with optionsBuilder and allowedTypes together', + 'should allow adding custom system picker options', (tester) async { + final controller = StreamAttachmentPickerController(); + addTearDown(controller.dispose); + await tester.pumpWidget( _wrapWithStreamChatApp( Builder( builder: (context) { - return ElevatedButton( - onPressed: () { - showStreamAttachmentPickerModalBottomSheet( - context: context, - allowedTypes: [ - AttachmentPickerType.images, - AttachmentPickerType.videos, - ], - optionsBuilder: (context, defaultOptions) { - return defaultOptions; - }, - ); + return systemAttachmentPickerBuilder( + context: context, + controller: controller, + optionsBuilder: (context, defaultOptions) { + return [ + ...defaultOptions, + SystemAttachmentPickerOption( + key: 'custom-upload', + icon: Icons.cloud_upload, + title: 'Custom Upload', + supportedTypes: [AttachmentPickerType.files], + onTap: (context, controller) async {}, + ), + ]; }, - child: const Text('Show Picker'), ); }, ), ), ); - await tester.tap(find.text('Show Picker')); await tester.pumpAndSettle(); - expect( - find.byType(StreamSystemAttachmentPickerBottomSheet), - findsOneWidget, - ); + expect(find.text('Custom Upload'), findsOneWidget); }, ); - }); - group('System picker with optionsBuilder', () { testWidgets( - 'should use optionsBuilder with system picker', + 'should throw ArgumentError when wrong option types are provided', (tester) async { - var builderCalled = false; + final controller = StreamAttachmentPickerController(); + addTearDown(controller.dispose); await tester.pumpWidget( _wrapWithStreamChatApp( Builder( builder: (context) { - return ElevatedButton( - onPressed: () { - showStreamAttachmentPickerModalBottomSheet( - context: context, - useSystemAttachmentPicker: true, - optionsBuilder: (context, defaultOptions) { - builderCalled = true; - return defaultOptions; - }, - ); + return systemAttachmentPickerBuilder( + context: context, + controller: controller, + optionsBuilder: (context, defaultOptions) { + return [ + TabbedAttachmentPickerOption( + key: 'wrong', + icon: Icons.error, + title: 'Wrong', + supportedTypes: [AttachmentPickerType.images], + optionViewBuilder: (context, controller) { + return const Text('Wrong'); + }, + ), + ]; }, - child: const Text('Show Picker'), ); }, ), ), ); - await tester.tap(find.text('Show Picker')); - await tester.pumpAndSettle(); - - expect(builderCalled, isTrue); - expect( - find.byType(StreamSystemAttachmentPickerBottomSheet), - findsOneWidget, - ); + expect(tester.takeException(), isA()); }, ); + }); + group('allowedTypes', () { testWidgets( - 'should allow adding custom system picker options', + 'should filter options based on allowedTypes', (tester) async { + final controller = StreamAttachmentPickerController(); + addTearDown(controller.dispose); + await tester.pumpWidget( _wrapWithStreamChatApp( Builder( builder: (context) { - return ElevatedButton( - onPressed: () { - showStreamAttachmentPickerModalBottomSheet( - context: context, - useSystemAttachmentPicker: true, - optionsBuilder: (context, defaultOptions) { - return [ - ...defaultOptions, - SystemAttachmentPickerOption( - key: 'custom-upload', - icon: const Icon(Icons.cloud_upload), - title: 'Custom Upload', - supportedTypes: [AttachmentPickerType.files], - onTap: (context, controller) async {}, - ), - ]; - }, - ); - }, - child: const Text('Show Picker'), + return systemAttachmentPickerBuilder( + context: context, + controller: controller, + allowedTypes: [AttachmentPickerType.images], ); }, ), ), ); - await tester.tap(find.text('Show Picker')); await tester.pumpAndSettle(); - expect(find.text('Custom Upload'), findsOneWidget); + final picker = tester.widget( + find.byType(StreamSystemAttachmentPicker), + ); + + expect( + picker.options.every( + (option) => option.supportedTypes.contains(AttachmentPickerType.images), + ), + isTrue, + ); }, ); }); diff --git a/sample_app/lib/pages/channel_page.dart b/sample_app/lib/pages/channel_page.dart index 1accd2658c..deebac62fd 100644 --- a/sample_app/lib/pages/channel_page.dart +++ b/sample_app/lib/pages/channel_page.dart @@ -150,7 +150,7 @@ class _ChannelPageState extends State { ...defaultOptions, TabbedAttachmentPickerOption( key: 'location-picker', - icon: const Icon(Icons.near_me_rounded), + icon: Icons.near_me_rounded, supportedTypes: [const LocationPickerType()], isEnabled: (value) { // Enable if nothing has been selected yet. From 6b4e3b75382eb8d1be01e9ea4728c2d9d81d02fd Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Fri, 6 Mar 2026 13:27:17 +0100 Subject: [PATCH 18/33] feat(ui): minor ui updates (#2528) * Update channel and messageview header * Fix thread list header * minor fixes * chore: Update Goldens * update bottom nav in sample app --------- Co-authored-by: renefloor <15101411+renefloor@users.noreply.github.com> --- .../lib/src/channel/channel_header.dart | 21 +++++++- .../lib/src/channel/channel_info.dart | 17 +++--- .../lib/src/channel/channel_list_header.dart | 43 ++++++++------- .../lib/src/localization/translations.dart | 4 +- .../lib/src/misc/thread_header.dart | 50 +++++++++++------- .../lib/src/theme/stream_chat_theme.dart | 4 -- .../src/channel/channel_list_header_test.dart | 7 +-- .../ci/channel_header_bottom_widget.png | Bin 1763 -> 1787 bytes .../test/src/misc/thread_header_test.dart | 9 ++-- .../lib/src/stream_chat_localizations_ca.dart | 2 +- .../lib/src/stream_chat_localizations_de.dart | 2 +- .../lib/src/stream_chat_localizations_en.dart | 6 +-- .../lib/src/stream_chat_localizations_es.dart | 2 +- .../lib/src/stream_chat_localizations_fr.dart | 2 +- .../lib/src/stream_chat_localizations_hi.dart | 2 +- .../lib/src/stream_chat_localizations_it.dart | 2 +- .../lib/src/stream_chat_localizations_ja.dart | 2 +- .../lib/src/stream_chat_localizations_ko.dart | 2 +- .../lib/src/stream_chat_localizations_no.dart | 2 +- .../lib/src/stream_chat_localizations_pt.dart | 2 +- sample_app/lib/pages/channel_list_page.dart | 12 +++-- sample_app/lib/pages/thread_page.dart | 4 +- sample_app/lib/widgets/search_text_field.dart | 22 ++++---- 23 files changed, 126 insertions(+), 93 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_header.dart b/packages/stream_chat_flutter/lib/src/channel/channel_header.dart index c0a6bab4b0..00d7727290 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_header.dart @@ -68,7 +68,8 @@ class StreamChannelHeader extends StatelessWidget implements PreferredSizeWidget this.actions, this.bottom, this.backgroundColor, - this.elevation = 1, + this.elevation = 0, + this.scrolledUnderElevation = 0, this.bottomOpacity = 1, }); @@ -122,6 +123,9 @@ class StreamChannelHeader extends StatelessWidget implements PreferredSizeWidget /// The elevation for this [StreamChannelHeader]. final double elevation; + /// The scrolled under elevation for this [StreamChannelHeader]. + final double scrolledUnderElevation; + /// The opacity of the bottom widget. final double bottomOpacity; @@ -170,6 +174,7 @@ class StreamChannelHeader extends StatelessWidget implements PreferredSizeWidget } final theme = Theme.of(context); + final colorScheme = context.streamColorScheme; return StreamInfoTile( showMessage: showConnectionStateTile && showStatus, @@ -181,9 +186,17 @@ class StreamChannelHeader extends StatelessWidget implements PreferredSizeWidget ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark, elevation: elevation, + scrolledUnderElevation: scrolledUnderElevation, leading: leadingWidget, bottom: bottom, bottomOpacity: bottomOpacity, + shape: LinearBorder( + side: BorderSide( + color: colorScheme.borderDefault, + width: 1, + ), + bottom: const LinearBorderEdge(), + ), backgroundColor: backgroundColor ?? channelHeaderTheme.color, actions: actions ?? @@ -213,7 +226,11 @@ class StreamChannelHeader extends StatelessWidget implements PreferredSizeWidget title ?? StreamChannelName( channel: channel, - textStyle: channelHeaderTheme.titleStyle, + textStyle: + channelHeaderTheme.titleStyle ?? + context.streamTextTheme.headingSm.copyWith( + color: context.streamColorScheme.textPrimary, + ), ), const SizedBox(height: 2), subtitle ?? diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel/channel_info.dart index ae81af0ab1..a5c973d1a0 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_info.dart @@ -33,6 +33,12 @@ class StreamChannelInfo extends StatelessWidget { @override Widget build(BuildContext context) { final client = StreamChat.of(context).client; + final effectiveTextStyle = + textStyle ?? + context.streamTextTheme.captionDefault.copyWith( + color: context.streamColorScheme.textSecondary, + ); + return BetterStreamBuilder>( stream: channel.state!.membersStream, initialData: channel.state!.members, @@ -43,16 +49,16 @@ class StreamChannelInfo extends StatelessWidget { return _ConnectedTitleState( channel: channel, showTypingIndicator: showTypingIndicator, - textStyle: textStyle, + textStyle: effectiveTextStyle, members: data, parentId: parentId, ); case ConnectionStatus.connecting: - return _ConnectingTitleState(textStyle: textStyle); + return _ConnectingTitleState(textStyle: effectiveTextStyle); case ConnectionStatus.disconnected: return _DisconnectedTitleState( client: client, - textStyle: textStyle, + textStyle: effectiveTextStyle, ); } }, @@ -87,10 +93,7 @@ class _ConnectedTitleState extends StatelessWidget { if (onlineCount > 0) { text += ', ${context.translations.watchersCountText(onlineCount)}'; } - alternativeWidget = Text( - text, - style: StreamChannelHeaderTheme.of(context).subtitleStyle, - ); + alternativeWidget = Text(text, style: textStyle); } else { final userId = StreamChat.of(context).currentUser?.id; final otherMember = members?.firstWhereOrNull( diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart b/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart index 87a03735a4..446a15936d 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template streamChannelListHeader} /// Shows the current [StreamChatClient] status. @@ -53,7 +54,7 @@ class StreamChannelListHeader extends StatelessWidget implements PreferredSizeWi this.leading, this.actions, this.backgroundColor, - this.elevation = 1, + this.elevation = 0, }); /// Use this if you don't have a [StreamChatClient] in your widget tree. @@ -124,9 +125,10 @@ class StreamChannelListHeader extends StatelessWidget implements PreferredSizeWi break; } - final chatThemeData = StreamChatTheme.of(context); final channelListHeaderThemeData = StreamChannelListHeaderTheme.of(context); final theme = Theme.of(context); + final colorScheme = context.streamColorScheme; + return StreamInfoTile( showMessage: showConnectionStateTile && showStatus, message: statusString, @@ -139,6 +141,13 @@ class StreamChannelListHeader extends StatelessWidget implements PreferredSizeWi elevation: elevation, backgroundColor: backgroundColor ?? channelListHeaderThemeData.color, centerTitle: centerTitle, + shape: LinearBorder( + side: BorderSide( + color: colorScheme.borderDefault, + width: 1, + ), + bottom: const LinearBorderEdge(), + ), leading: switch ((leading, user)) { (final leading?, _) => leading, (_, final user?) => Center( @@ -162,25 +171,19 @@ class StreamChannelListHeader extends StatelessWidget implements PreferredSizeWi actions: actions ?? [ - StreamNeumorphicButton( - child: IconButton( - icon: StreamConnectionStatusBuilder( - statusBuilder: (context, status) { - final color = switch (status) { - ConnectionStatus.connected => chatThemeData.colorTheme.accentPrimary, - ConnectionStatus.connecting => Colors.grey, - ConnectionStatus.disconnected => Colors.grey, - }; + StreamConnectionStatusBuilder( + statusBuilder: (context, status) { + final callback = switch (status) { + ConnectionStatus.connected => onNewChatButtonTap, + ConnectionStatus.connecting => null, + ConnectionStatus.disconnected => null, + }; - return Icon( - context.streamIcons.pencil, - size: 24, - color: color, - ); - }, - ), - onPressed: onNewChatButtonTap, - ), + return StreamButton.icon( + icon: context.streamIcons.plusLarge, + onTap: callback, + ); + }, ), ], title: Column( diff --git a/packages/stream_chat_flutter/lib/src/localization/translations.dart b/packages/stream_chat_flutter/lib/src/localization/translations.dart index 823bead692..c96c11dac5 100644 --- a/packages/stream_chat_flutter/lib/src/localization/translations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/translations.dart @@ -597,13 +597,13 @@ class DefaultTranslations implements Translations { } @override - String get threadReplyLabel => 'Thread Reply'; + String get threadReplyLabel => 'Thread'; @override String get onlyVisibleToYouText => 'Only visible to you'; @override - String threadReplyCountText(int count) => '$count Thread Replies'; + String threadReplyCountText(int count) => count == 1 ? '1 reply' : '$count replies'; @override String attachmentsUploadProgressText({ diff --git a/packages/stream_chat_flutter/lib/src/misc/thread_header.dart b/packages/stream_chat_flutter/lib/src/misc/thread_header.dart index e4a93fb4d4..9f7eb4ebda 100644 --- a/packages/stream_chat_flutter/lib/src/misc/thread_header.dart +++ b/packages/stream_chat_flutter/lib/src/misc/thread_header.dart @@ -72,7 +72,8 @@ class StreamThreadHeader extends StatelessWidget implements PreferredSizeWidget this.onTitleTap, this.showTypingIndicator = true, this.backgroundColor, - this.elevation = 1, + this.elevation = 0, + this.scrolledUnderElevation = 0, }) : preferredSize = const Size.fromHeight(kToolbarHeight); /// Whether to show the leading back button. @@ -117,6 +118,9 @@ class StreamThreadHeader extends StatelessWidget implements PreferredSizeWidget /// The elevation for this [StreamThreadHeader]. final double elevation; + /// The scrolled under elevation for this [StreamThreadHeader]. + final double scrolledUnderElevation; + @override Widget build(BuildContext context) { final effectiveCenterTitle = getEffectiveCenterTitle( @@ -126,25 +130,27 @@ class StreamThreadHeader extends StatelessWidget implements PreferredSizeWidget ); final channelHeaderTheme = StreamChannelHeaderTheme.of(context); + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; + + final replyCount = parent.replyCount; final defaultSubtitle = subtitle ?? - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - '${context.translations.withText} ', - style: channelHeaderTheme.subtitleStyle, - ), - Flexible( - child: StreamChannelName( - channel: StreamChannel.of(context).channel, - textStyle: channelHeaderTheme.subtitleStyle, - ), - ), - ], - ); + (replyCount != null + ? Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + context.translations.threadReplyCountText(replyCount), + style: + channelHeaderTheme.subtitleStyle ?? + textTheme.captionDefault.copyWith(color: colorScheme.textSecondary), + ), + ], + ) + : const SizedBox.shrink()); final theme = Theme.of(context); return AppBar( @@ -153,6 +159,7 @@ class StreamThreadHeader extends StatelessWidget implements PreferredSizeWidget titleTextStyle: theme.textTheme.titleLarge, systemOverlayStyle: theme.brightness == Brightness.dark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark, elevation: elevation, + scrolledUnderElevation: scrolledUnderElevation, leading: leading ?? (showBackButton @@ -165,6 +172,13 @@ class StreamThreadHeader extends StatelessWidget implements PreferredSizeWidget backgroundColor: backgroundColor ?? channelHeaderTheme.color, centerTitle: centerTitle, actions: actions, + shape: LinearBorder( + side: BorderSide( + color: colorScheme.borderDefault, + width: 1, + ), + bottom: const LinearBorderEdge(), + ), title: InkWell( onTap: onTitleTap, child: SizedBox( @@ -177,7 +191,7 @@ class StreamThreadHeader extends StatelessWidget implements PreferredSizeWidget title ?? Text( context.translations.threadReplyLabel, - style: channelHeaderTheme.titleStyle, + style: channelHeaderTheme.titleStyle ?? textTheme.headingSm, ), const SizedBox(height: 2), if (showTypingIndicator) diff --git a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart index fbef12f1b2..25f6016190 100644 --- a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart @@ -162,10 +162,6 @@ class StreamChatThemeData { ), ), color: colorTheme.barsBg, - titleStyle: textTheme.headlineBold, - subtitleStyle: textTheme.footnote.copyWith( - color: colorTheme.textLowEmphasis, - ), ); final channelPreviewTheme = StreamChannelPreviewThemeData( unreadCounterColor: colorTheme.accentError, diff --git a/packages/stream_chat_flutter/test/src/channel/channel_list_header_test.dart b/packages/stream_chat_flutter/test/src/channel/channel_list_header_test.dart index d136ce40fb..b39b0a4cbf 100644 --- a/packages/stream_chat_flutter/test/src/channel/channel_list_header_test.dart +++ b/packages/stream_chat_flutter/test/src/channel/channel_list_header_test.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; import '../mocks.dart'; @@ -30,7 +31,7 @@ void main() { final userAvatar = tester.widget(find.byType(StreamUserAvatar)); expect(userAvatar.user, clientState.currentUser); - expect(find.byType(StreamNeumorphicButton), findsOneWidget); + expect(find.byType(StreamButton), findsOneWidget); expect(find.text('Stream Chat'), findsOneWidget); }, ); @@ -169,7 +170,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connected)); var tapped = 0; await tester.pumpWidget( @@ -192,7 +193,7 @@ void main() { await tester.pump(); await tester.tap(find.byType(StreamUserAvatar)); - await tester.tap(find.byType(StreamNeumorphicButton)); + await tester.tap(find.byIcon(StreamIconData.iconPlusLarge)); expect(tapped, 2); }, ); diff --git a/packages/stream_chat_flutter/test/src/channel/goldens/ci/channel_header_bottom_widget.png b/packages/stream_chat_flutter/test/src/channel/goldens/ci/channel_header_bottom_widget.png index 3e1309f68a24fed6260201f29dbfd994f65a24ad..7f62afc9b330695d6e799c74c1314aa7f6f05dc1 100644 GIT binary patch delta 1687 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 gJGC;7Y-pSu>b%707*qoM6N<$f|=|kdH?_b diff --git a/packages/stream_chat_flutter/test/src/misc/thread_header_test.dart b/packages/stream_chat_flutter/test/src/misc/thread_header_test.dart index ba7b86056f..c818e9c2f8 100644 --- a/packages/stream_chat_flutter/test/src/misc/thread_header_test.dart +++ b/packages/stream_chat_flutter/test/src/misc/thread_header_test.dart @@ -54,7 +54,8 @@ void main() { channel: channel, child: Scaffold( body: StreamThreadHeader( - parent: Message(), + parent: Message(replyCount: 1), + showTypingIndicator: false, ), ), ), @@ -65,11 +66,9 @@ void main() { // wait for the initial state to be rendered. await tester.pumpAndSettle(); - expect(find.text('with '), findsOneWidget); - expect(find.byType(StreamChannelName), findsOneWidget); expect(find.byType(StreamBackButton), findsOneWidget); - expect(find.text('1'), findsOneWidget); - expect(find.text('Thread Reply'), findsOneWidget); + expect(find.text('1 reply'), findsOneWidget); + expect(find.text('Thread'), findsOneWidget); }, ); diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart index 0f8159999d..7b405bc40d 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart @@ -288,7 +288,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get deleteConversationQuestion => 'Estàs segur que vols esborrar aquesta conversa?'; @override - String get streamChatLabel => 'Stream Chat'; + String get streamChatLabel => 'Converses'; @override String get searchingForNetworkText => 'Cercant xarxa'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart index bd0212a612..b5672ae747 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart @@ -284,7 +284,7 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String get deleteConversationQuestion => 'Sind Sie sicher, dass Sie diese Unterhaltung löschen wollen?'; @override - String get streamChatLabel => 'Stream Chat'; + String get streamChatLabel => 'Unterhaltungen'; @override String get searchingForNetworkText => 'Netzwerk wird gesucht'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart index 6c5c76f0ee..4b3fc73cf7 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart @@ -39,13 +39,13 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { } @override - String get threadReplyLabel => 'Thread Reply'; + String get threadReplyLabel => 'Thread'; @override String get onlyVisibleToYouText => 'Only visible to you'; @override - String threadReplyCountText(int count) => '$count Thread Replies'; + String threadReplyCountText(int count) => count == 1 ? '1 reply' : '$count replies'; @override String attachmentsUploadProgressText({ @@ -287,7 +287,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get deleteConversationQuestion => 'Are you sure you want to delete this conversation?'; @override - String get streamChatLabel => 'Stream Chat'; + String get streamChatLabel => 'Chats'; @override String get searchingForNetworkText => 'Searching for Network'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart index 13d9f430bb..dec8f5bb2c 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart @@ -288,7 +288,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get deleteConversationQuestion => '¿Estás seguro de que quieres borrar esta conversación?'; @override - String get streamChatLabel => 'Stream Chat'; + String get streamChatLabel => 'Conversaciones'; @override String get searchingForNetworkText => 'Buscando red'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart index 44ec8509e3..5de6c28ee1 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart @@ -288,7 +288,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get deleteConversationQuestion => 'Vous êtes sûr de vouloir supprimer cette conversation ?'; @override - String get streamChatLabel => 'Stream Chat'; + String get streamChatLabel => 'Conversations'; @override String get searchingForNetworkText => 'Recherche de réseau'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart index 331c273e55..e04feb04d8 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart @@ -286,7 +286,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get deleteConversationQuestion => 'क्या आप वाकई इस वार्तालाप को हटाना चाहते हैं?'; @override - String get streamChatLabel => 'स्ट्रीम चैट'; + String get streamChatLabel => 'चैट'; @override String get searchingForNetworkText => 'नेटवर्क खोज रहे हैं'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart index d6a318dc23..7a96d1c99e 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart @@ -291,7 +291,7 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get deleteConversationQuestion => 'Sei sicuro di voler eliminare questa conversazione?'; @override - String get streamChatLabel => 'Stream Chat'; + String get streamChatLabel => 'Conversazioni'; @override String get searchingForNetworkText => 'Cercando una connessione'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart index a425b69642..a8aa033076 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart @@ -278,7 +278,7 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { String get deleteConversationQuestion => '本当に会話を削除しますか?'; @override - String get streamChatLabel => 'ストリームチャット'; + String get streamChatLabel => 'チャット'; @override String get searchingForNetworkText => 'ネットワークを検索中'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart index b9fd0eba09..9a33f803b5 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart @@ -279,7 +279,7 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { String get deleteConversationQuestion => '대화를 삭제하시겠습니까?'; @override - String get streamChatLabel => '스트림 채팅'; + String get streamChatLabel => '채팅'; @override String get searchingForNetworkText => '네트워크를 검색하는 중입니다.'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart index 70edbb689b..321f7c42ee 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart @@ -283,7 +283,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get deleteConversationQuestion => 'Er du sikker på at du ønsker å slette denne samtalen?'; @override - String get streamChatLabel => 'Stream Chat'; + String get streamChatLabel => 'Samtaler'; @override String get searchingForNetworkText => 'Søker etter nettverk'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart index b315cf2bf2..a899386862 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart @@ -284,7 +284,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get deleteConversationQuestion => 'Tem certeza que deseja apagar essa conversa?'; @override - String get streamChatLabel => 'Stream Chat'; + String get streamChatLabel => 'Conversas'; @override String get searchingForNetworkText => 'Pesquisando rede'; diff --git a/sample_app/lib/pages/channel_list_page.dart b/sample_app/lib/pages/channel_list_page.dart index 0d440a9dc5..d02dd41bbc 100644 --- a/sample_app/lib/pages/channel_list_page.dart +++ b/sample_app/lib/pages/channel_list_page.dart @@ -33,13 +33,15 @@ class _ChannelListPageState extends State { bool _isSelected(int index) => _currentIndex == index; List get _navBarItems { + final icons = context.streamIcons; + return [ BottomNavigationBarItem( icon: Stack( clipBehavior: Clip.none, children: [ Icon( - context.streamIcons.bubble3ChatMessage, + _isSelected(0) ? icons.bubble3Solid : icons.bubble3ChatMessage, color: _isSelected(0) ? StreamChatTheme.of(context).colorTheme.textHighEmphasis : Colors.grey, ), const PositionedDirectional( @@ -53,7 +55,7 @@ class _ChannelListPageState extends State { ), BottomNavigationBarItem( icon: Icon( - context.streamIcons.at, + _isSelected(1) ? icons.atSolid : icons.at, color: _isSelected(1) ? StreamChatTheme.of(context).colorTheme.textHighEmphasis : Colors.grey, ), label: AppLocalizations.of(context).mentions, @@ -63,7 +65,7 @@ class _ChannelListPageState extends State { clipBehavior: Clip.none, children: [ Icon( - Icons.message_outlined, + _isSelected(2) ? icons.bubbleText6Solid : icons.bubbleText6ChatMessage, color: _isSelected(2) ? StreamChatTheme.of(context).colorTheme.textHighEmphasis : Colors.grey, ), PositionedDirectional( @@ -77,14 +79,14 @@ class _ChannelListPageState extends State { ), BottomNavigationBarItem( icon: Icon( - Icons.edit_note_rounded, + _isSelected(3) ? icons.editBigSolid : icons.editBig, color: _isSelected(3) ? StreamChatTheme.of(context).colorTheme.textHighEmphasis : Colors.grey, ), label: 'Drafts', ), BottomNavigationBarItem( icon: Icon( - Icons.bookmark_border_rounded, + icons.bookmark, color: _isSelected(4) ? StreamChatTheme.of(context).colorTheme.textHighEmphasis : Colors.grey, ), label: 'Reminders', diff --git a/sample_app/lib/pages/thread_page.dart b/sample_app/lib/pages/thread_page.dart index a607d94fd7..c43f9c7b50 100644 --- a/sample_app/lib/pages/thread_page.dart +++ b/sample_app/lib/pages/thread_page.dart @@ -54,9 +54,7 @@ class _ThreadPageState extends State { return Scaffold( backgroundColor: StreamChatTheme.of(context).colorTheme.appBg, - appBar: StreamThreadHeader( - parent: widget.parent, - ), + appBar: StreamThreadHeader(parent: widget.parent), body: Column( children: [ Expanded( diff --git a/sample_app/lib/widgets/search_text_field.dart b/sample_app/lib/widgets/search_text_field.dart index 8c6ae23f95..a5bcf6f1df 100644 --- a/sample_app/lib/widgets/search_text_field.dart +++ b/sample_app/lib/widgets/search_text_field.dart @@ -20,12 +20,16 @@ class SearchTextField extends StatelessWidget { @override Widget build(BuildContext context) { + final spacing = context.streamSpacing; + final colorScheme = context.streamColorScheme; + final textTheme = context.streamTextTheme; + return Container( height: 36, decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.barsBg, + color: Colors.transparent, border: Border.all( - color: StreamChatTheme.of(context).colorTheme.borders, + color: colorScheme.borderDefault, ), borderRadius: BorderRadius.circular(24), ), @@ -41,22 +45,18 @@ class SearchTextField extends StatelessWidget { controller: controller, onChanged: onChanged, decoration: InputDecoration( - prefixText: ' ', prefixIconConstraints: BoxConstraints.tight(const Size(40, 24)), prefixIcon: Padding( - padding: const EdgeInsets.only( - left: 8, - right: 8, - ), + padding: EdgeInsets.only(left: spacing.md), child: Icon( context.streamIcons.magnifyingGlassSearch, - color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, - size: 24, + color: colorScheme.textTertiary, + size: 20, ), ), hintText: hintText, - hintStyle: StreamChatTheme.of(context).textTheme.body.copyWith( - color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.5), + hintStyle: textTheme.bodyDefault.copyWith( + color: colorScheme.textTertiary, ), contentPadding: EdgeInsets.zero, border: OutlineInputBorder( From 7b681da68c3d03ab16ddac0affd1a99b214e810a Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Sat, 7 Mar 2026 18:19:37 +0530 Subject: [PATCH 19/33] feat(ui): redesign image CDN handling and thumbnail resizing (#2531) Co-authored-by: xsahil03x <25670178+xsahil03x@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> fix incorrect `stableCacheKey` String extension reference in image CDN migration guide (#2532) --- migrations/redesign/README.md | 1 + migrations/redesign/image_cdn.md | 209 +++++++++++++++ .../lib/src/attachment/image_attachment.dart | 24 +- .../thumbnail/image_attachment_thumbnail.dart | 57 ++--- .../thumbnail/media_attachment_thumbnail.dart | 29 +-- .../lib/src/stream_chat_configuration.dart | 12 + .../lib/src/utils/extensions.dart | 40 --- .../lib/src/utils/stream_image_cdn.dart | 175 +++++++++++++ .../lib/stream_chat_flutter.dart | 2 +- .../test/src/utils/stream_image_cdn_test.dart | 237 ++++++++++++++++++ 10 files changed, 678 insertions(+), 108 deletions(-) create mode 100644 migrations/redesign/image_cdn.md create mode 100644 packages/stream_chat_flutter/lib/src/utils/stream_image_cdn.dart create mode 100644 packages/stream_chat_flutter/test/src/utils/stream_image_cdn_test.dart diff --git a/migrations/redesign/README.md b/migrations/redesign/README.md index 97ed8364da..5964be74fa 100644 --- a/migrations/redesign/README.md +++ b/migrations/redesign/README.md @@ -124,6 +124,7 @@ class MyCustomButton extends StatelessWidget { | Channel List Item | [channel_list_item.md](channel_list_item.md) | | Message Actions | [message_actions.md](message_actions.md) | | Reaction Picker / Reactions | [reaction_picker.md](reaction_picker.md) | +| Image CDN & Thumbnails | [image_cdn.md](image_cdn.md) | ## Need Help? diff --git a/migrations/redesign/image_cdn.md b/migrations/redesign/image_cdn.md new file mode 100644 index 0000000000..bb341447a3 --- /dev/null +++ b/migrations/redesign/image_cdn.md @@ -0,0 +1,209 @@ +# Image CDN & Thumbnails Migration Guide + +This guide covers the migration for the redesigned image CDN handling and thumbnail resize parameters in Stream Chat Flutter SDK. + +--- + +## Table of Contents + +- [Quick Reference](#quick-reference) +- [StreamImageCDN](#streamimagecdn) +- [StreamImageAttachmentThumbnail](#streamimageattachmentthumbnail) +- [StreamMediaAttachmentThumbnail](#streammediaattachmentthumbnail) +- [StreamImageAttachment](#streamimageattachment) +- [Migration Checklist](#migration-checklist) + +--- + +## Quick Reference + +| Component | Key Changes | +|-----------|-------------| +| [**StreamImageCDN**](#streamimagecdn) | New class replacing `getResizedImageUrl` String extension (stable cache keys now via `StreamImageCDN.cacheKey()`) | +| [**StreamImageAttachmentThumbnail**](#streamimageattachmentthumbnail) | `thumbnailSize`, `thumbnailResizeType`, `thumbnailCropType` → single `resize` parameter | +| [**StreamMediaAttachmentThumbnail**](#streammediaattachmentthumbnail) | `thumbnailSize`, `thumbnailResizeType`, `thumbnailCropType` → single `resize` parameter | +| [**StreamImageAttachment**](#streamimageattachment) | `imageThumbnailSize`, `imageThumbnailResizeType`, `imageThumbnailCropType` → single `resize` parameter | + +--- + +## StreamImageCDN + +### What Changed: + +The `getResizedImageUrl` String extension has been replaced with a dedicated `StreamImageCDN` class. This class handles CDN URL resolution and stable cache key generation, preventing image reloads caused by expiring signed URL tokens. + +### Key Changes: + +- `getResizedImageUrl` String extension removed — use `StreamImageCDN.resolveUrl` instead +- New `StreamImageCDN.cacheKey` method generates stable cache keys that strip volatile signed URL tokens +- Raw `String` resize/crop type parameters replaced with `ResizeMode` and `CropMode` enums +- `StreamImageCDN` is injectable via `StreamChatConfigurationData` for custom CDN support + +### Migration: + +**Before:** +```dart +final resizedUrl = imageUrl.getResizedImageUrl( + width: 200, + height: 300, + resize: 'clip', + crop: 'center', +); +``` + +**After:** +```dart +const imageCDN = StreamImageCDN(); + +final resizedUrl = imageCDN.resolveUrl( + imageUrl, + resize: ImageResize(width: 200, height: 300), +); + +final cacheKey = imageCDN.cacheKey(resizedUrl); +``` + +### Custom CDN Support: + +Extend `StreamImageCDN` and inject it via configuration: + +```dart +class MyImageCDN extends StreamImageCDN { + @override + String cacheKey(String imageUrl) { + return Uri.parse(imageUrl).path; + } +} + +StreamChat( + client: client, + config: StreamChatConfigurationData( + imageCDN: MyImageCDN(), + ), + child: ..., +) +``` + +--- + +## StreamImageAttachmentThumbnail + +### Breaking Changes: + +- `thumbnailSize` parameter removed +- `thumbnailResizeType` parameter removed +- `thumbnailCropType` parameter removed +- New `resize` parameter (`ImageResize?`) replaces all three + +### Migration: + +**Before:** +```dart +StreamImageAttachmentThumbnail( + image: attachment, + thumbnailSize: const Size(200, 300), + thumbnailResizeType: 'clip', + thumbnailCropType: 'center', +) +``` + +**After:** +```dart +StreamImageAttachmentThumbnail( + image: attachment, + resize: ImageResize( + width: 200, + height: 300, + mode: ResizeMode.clip, + crop: CropMode.center, + ), +) +``` + +> **Note:** When `resize` is null, the size is auto-calculated from layout constraints and defaults to `ResizeMode.clip` and `CropMode.center`. + +--- + +## StreamMediaAttachmentThumbnail + +### Breaking Changes: + +- `thumbnailSize` parameter removed +- `thumbnailResizeType` parameter removed +- `thumbnailCropType` parameter removed +- New `resize` parameter (`ImageResize?`) replaces all three + +### Migration: + +**Before:** +```dart +StreamMediaAttachmentThumbnail( + media: attachment, + thumbnailSize: const Size(200, 300), + thumbnailResizeType: 'clip', + thumbnailCropType: 'center', +) +``` + +**After:** +```dart +StreamMediaAttachmentThumbnail( + media: attachment, + resize: ImageResize( + width: 200, + height: 300, + mode: ResizeMode.clip, + crop: CropMode.center, + ), +) +``` + +--- + +## StreamImageAttachment + +### Breaking Changes: + +- `imageThumbnailSize` parameter removed +- `imageThumbnailResizeType` parameter removed +- `imageThumbnailCropType` parameter removed +- New `resize` parameter (`ImageResize?`) replaces all three + +### Migration: + +**Before:** +```dart +StreamImageAttachment( + message: message, + image: attachment, + imageThumbnailSize: const Size(400, 600), + imageThumbnailResizeType: 'crop', + imageThumbnailCropType: 'center', +) +``` + +**After:** +```dart +StreamImageAttachment( + message: message, + image: attachment, + resize: ImageResize( + width: 400, + height: 600, + mode: ResizeMode.crop, + crop: CropMode.center, + ), +) +``` + +--- + +## Migration Checklist + +- [ ] Replace `getResizedImageUrl` String extension calls with `StreamImageCDN.resolveUrl` +- [ ] Use `StreamImageCDN.cacheKey` to generate stable cache keys for `CachedNetworkImage` +- [ ] Replace raw `String` resize/crop values (`'clip'`, `'crop'`, etc.) with `ResizeMode` and `CropMode` enums +- [ ] Update `StreamImageAttachmentThumbnail` to use `resize` parameter instead of `thumbnailSize`, `thumbnailResizeType`, `thumbnailCropType` +- [ ] Update `StreamMediaAttachmentThumbnail` to use `resize` parameter instead of `thumbnailSize`, `thumbnailResizeType`, `thumbnailCropType` +- [ ] Update `StreamImageAttachment` to use `resize` parameter instead of `imageThumbnailSize`, `imageThumbnailResizeType`, `imageThumbnailCropType` +- [ ] If using a custom CDN, extend `StreamImageCDN` and inject via `StreamChatConfigurationData` diff --git a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart index 7b82779f5a..f213264fe6 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart @@ -12,9 +12,7 @@ class StreamImageAttachment extends StatelessWidget { required this.image, this.shape, this.constraints = const BoxConstraints(), - this.imageThumbnailSize, - this.imageThumbnailResizeType = 'clip', - this.imageThumbnailCropType = 'center', + this.resize, }); /// The [Message] that the image is attached to. @@ -31,18 +29,14 @@ class StreamImageAttachment extends StatelessWidget { /// The constraints to use when displaying the image. final BoxConstraints constraints; - /// Size of the attachment image thumbnail. - final Size? imageThumbnailSize; - - /// Resize type of the image attachment thumbnail. + /// The resize configuration for the image attachment thumbnail. /// - /// Defaults to [crop] - final String /*clip|crop|scale|fill*/ imageThumbnailResizeType; - - /// Crop type of the image attachment thumbnail. + /// When provided, its [ImageResize.width] and [ImageResize.height] are used + /// directly as the CDN resize dimensions. /// - /// Defaults to [center] - final String /*center|top|bottom|left|right*/ imageThumbnailCropType; + /// When null, the size is auto-calculated from the layout constraints + /// and defaults to [ResizeMode.clip] and [CropMode.center]. + final ImageResize? resize; @override Widget build(BuildContext context) { @@ -86,9 +80,7 @@ class StreamImageAttachment extends StatelessWidget { fit: fit, width: double.infinity, height: double.infinity, - thumbnailSize: imageThumbnailSize, - thumbnailResizeType: imageThumbnailResizeType, - thumbnailCropType: imageThumbnailCropType, + resize: resize, ), Padding( padding: const EdgeInsets.all(8), diff --git a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/image_attachment_thumbnail.dart b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/image_attachment_thumbnail.dart index cbc7d8f1c9..9c05cb70dc 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/image_attachment_thumbnail.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/image_attachment_thumbnail.dart @@ -5,7 +5,9 @@ import 'package:flutter/material.dart'; import 'package:shimmer/shimmer.dart'; import 'package:stream_chat_flutter/src/attachment/thumbnail/thumbnail_error.dart'; import 'package:stream_chat_flutter/src/attachment/thumbnail/thumbnail_size_calculator.dart'; +import 'package:stream_chat_flutter/src/stream_chat_configuration.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; +import 'package:stream_chat_flutter/src/utils/stream_image_cdn.dart'; import 'package:stream_chat_flutter/src/utils/utils.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; @@ -22,9 +24,7 @@ class StreamImageAttachmentThumbnail extends StatelessWidget { this.width, this.height, this.fit, - this.thumbnailSize, - this.thumbnailResizeType = 'clip', - this.thumbnailCropType = 'center', + this.resize, this.errorBuilder = _defaultErrorBuilder, }); @@ -40,18 +40,14 @@ class StreamImageAttachmentThumbnail extends StatelessWidget { /// Fit of the attachment image thumbnail. final BoxFit? fit; - /// Size of the attachment image thumbnail. - final Size? thumbnailSize; - - /// Resize type of the image attachment thumbnail. + /// The resize configuration for the image attachment thumbnail. /// - /// Defaults to [crop] - final String /*clip|crop|scale|fill*/ thumbnailResizeType; - - /// Crop type of the image attachment thumbnail. + /// When provided, its [ImageResize.width] and [ImageResize.height] are used + /// directly as the CDN resize dimensions. /// - /// Defaults to [center] - final String /*center|top|bottom|left|right*/ thumbnailCropType; + /// When null, the size is auto-calculated from the layout constraints + /// and defaults to [ResizeMode.clip] and [CropMode.center]. + final ImageResize? resize; /// Builder used when the thumbnail fails to load. final ThumbnailErrorBuilder errorBuilder; @@ -75,35 +71,31 @@ class StreamImageAttachmentThumbnail extends StatelessWidget { Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { - // Calculate optimal thumbnail size once for all paths - final effectiveThumbnailSize = switch (thumbnailSize) { - final thumbnailSize? => thumbnailSize, - _ => ThumbnailSizeCalculator.calculate( + var effectiveResize = resize; + if (effectiveResize == null) { + final size = ThumbnailSizeCalculator.calculate( targetSize: constraints.biggest, originalSize: image.originalSize, pixelRatio: MediaQuery.devicePixelRatioOf(context), - ), - }; + ); + + if (size != null) effectiveResize = .new(width: size.width, height: size.height); + } - final cacheWidth = effectiveThumbnailSize?.width.round(); - final cacheHeight = effectiveThumbnailSize?.height.round(); + final cacheWidth = effectiveResize?.width.round(); + final cacheHeight = effectiveResize?.height.round(); // If the remote image URL is available, we can directly show it using // the _RemoteImageAttachment widget. final imageUrl = image.thumbUrl ?? image.imageUrl ?? image.assetUrl; if (imageUrl case final imageUrl?) { - var resizedImageUrl = imageUrl; - if (effectiveThumbnailSize case final thumbnailSize?) { - resizedImageUrl = imageUrl.getResizedImageUrl( - crop: thumbnailCropType, - resize: thumbnailResizeType, - width: thumbnailSize.width, - height: thumbnailSize.height, - ); - } + final imageCDN = StreamChatConfiguration.maybeOf(context)?.imageCDN ?? const StreamImageCDN(); + final resolvedUrl = imageCDN.resolveUrl(imageUrl, resize: effectiveResize); + final resolvedCacheKey = imageCDN.cacheKey(resolvedUrl); return _RemoteImageAttachment( - url: resizedImageUrl, + url: resolvedUrl, + cacheKey: resolvedCacheKey, width: width, height: height, fit: fit, @@ -195,6 +187,7 @@ class _LocalImageAttachment extends StatelessWidget { class _RemoteImageAttachment extends StatelessWidget { const _RemoteImageAttachment({ required this.url, + this.cacheKey, required this.errorBuilder, this.width, this.height, @@ -204,6 +197,7 @@ class _RemoteImageAttachment extends StatelessWidget { }); final String url; + final String? cacheKey; final double? width; final double? height; final int? cacheWidth; @@ -215,6 +209,7 @@ class _RemoteImageAttachment extends StatelessWidget { Widget build(BuildContext context) { return CachedNetworkImage( imageUrl: url, + cacheKey: cacheKey, width: width, height: height, memCacheWidth: cacheWidth, diff --git a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/media_attachment_thumbnail.dart b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/media_attachment_thumbnail.dart index ab8c60c571..b2fb2dab5f 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/media_attachment_thumbnail.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/media_attachment_thumbnail.dart @@ -3,6 +3,7 @@ import 'package:stream_chat_flutter/src/attachment/thumbnail/giphy_attachment_th import 'package:stream_chat_flutter/src/attachment/thumbnail/image_attachment_thumbnail.dart'; import 'package:stream_chat_flutter/src/attachment/thumbnail/thumbnail_error.dart'; import 'package:stream_chat_flutter/src/attachment/thumbnail/video_attachment_thumbnail.dart'; +import 'package:stream_chat_flutter/src/utils/stream_image_cdn.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// {@template mediaAttachmentThumbnail} @@ -24,9 +25,7 @@ class StreamMediaAttachmentThumbnail extends StatelessWidget { this.width, this.height, this.fit, - this.thumbnailSize, - this.thumbnailResizeType = 'clip', - this.thumbnailCropType = 'center', + this.resize, this.gifInfoType = GiphyInfoType.original, this.errorBuilder = _defaultErrorBuilder, }); @@ -46,24 +45,16 @@ class StreamMediaAttachmentThumbnail extends StatelessWidget { /// Builder used when the thumbnail fails to load. final ThumbnailErrorBuilder errorBuilder; - /// Size of the attachment image thumbnail. + /// The resize configuration for the image attachment thumbnail. /// - /// Ignored if the [Attachment.type] is not [AttachmentType.image]. - final Size? thumbnailSize; - - /// Resize type of the image attachment thumbnail. - /// - /// Defaults to [crop] - /// - /// Ignored if the [Attachment.type] is not [AttachmentType.image]. - final String /*clip|crop|scale|fill*/ thumbnailResizeType; - - /// Crop type of the image attachment thumbnail. + /// When provided, its [ImageResize.width] and [ImageResize.height] are used + /// directly as the CDN resize dimensions. /// - /// Defaults to [center] + /// When null, the size is auto-calculated from the layout constraints + /// and defaults to [ResizeMode.clip] and [CropMode.center]. /// /// Ignored if the [Attachment.type] is not [AttachmentType.image]. - final String /*center|top|bottom|left|right*/ thumbnailCropType; + final ImageResize? resize; /// The type of giphy thumbnail to build. /// @@ -94,9 +85,7 @@ class StreamMediaAttachmentThumbnail extends StatelessWidget { width: width, height: height, fit: fit, - thumbnailSize: thumbnailSize, - thumbnailResizeType: thumbnailResizeType, - thumbnailCropType: thumbnailCropType, + resize: resize, errorBuilder: errorBuilder, ); } diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart b/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart index 2673bca8f5..dc015c83c3 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart @@ -165,6 +165,7 @@ class StreamChatConfigurationData { bool? enforceUniqueReactions, bool draftMessagesEnabled = false, MessagePreviewFormatter? messagePreviewFormatter, + StreamImageCDN imageCDN = const StreamImageCDN(), }) { return StreamChatConfigurationData._( loadingIndicator: loadingIndicator, @@ -174,6 +175,7 @@ class StreamChatConfigurationData { enforceUniqueReactions: enforceUniqueReactions ?? true, draftMessagesEnabled: draftMessagesEnabled, messagePreviewFormatter: messagePreviewFormatter ?? MessagePreviewFormatter(), + imageCDN: imageCDN, ); } @@ -185,6 +187,7 @@ class StreamChatConfigurationData { required this.enforceUniqueReactions, required this.draftMessagesEnabled, required this.messagePreviewFormatter, + required this.imageCDN, }); /// Copies the configuration options from one [StreamChatConfigurationData] to @@ -197,6 +200,7 @@ class StreamChatConfigurationData { bool? enforceUniqueReactions, bool? draftMessagesEnabled, MessagePreviewFormatter? messagePreviewFormatter, + StreamImageCDN? imageCDN, }) { return StreamChatConfigurationData( reactionIconResolver: reactionIconResolver ?? this.reactionIconResolver, @@ -206,6 +210,7 @@ class StreamChatConfigurationData { enforceUniqueReactions: enforceUniqueReactions ?? this.enforceUniqueReactions, draftMessagesEnabled: draftMessagesEnabled ?? this.draftMessagesEnabled, messagePreviewFormatter: messagePreviewFormatter ?? this.messagePreviewFormatter, + imageCDN: imageCDN ?? this.imageCDN, ); } @@ -237,6 +242,13 @@ class StreamChatConfigurationData { /// Defaults to [MessagePreviewFormatter]. final MessagePreviewFormatter messagePreviewFormatter; + /// The image CDN used for generating resized image URLs and stable + /// cache keys. + /// + /// Defaults to [StreamImageCDN], which supports Stream's own CDN. + /// Extend [StreamImageCDN] to customize behavior for a custom CDN. + final StreamImageCDN imageCDN; + static Widget _defaultUserImage( BuildContext context, User user, diff --git a/packages/stream_chat_flutter/lib/src/utils/extensions.dart b/packages/stream_chat_flutter/lib/src/utils/extensions.dart index 3723535511..2ae338eb00 100644 --- a/packages/stream_chat_flutter/lib/src/utils/extensions.dart +++ b/packages/stream_chat_flutter/lib/src/utils/extensions.dart @@ -101,46 +101,6 @@ extension StringExtension on String { /// Levenshtein distance between this and [t]. int levenshteinDistance(String t) => levenshtein(this, t); - - /// Returns a resized imageUrl with the given [width], [height], [resize] - /// and [crop] if it is from Stream CDN or Dashboard. - /// - /// Read more at https://getstream.io/chat/docs/flutter-dart/file_uploads/?language=dart#image-resizing - String getResizedImageUrl({ - // TODO: Are these sizes optimal? Consider web/desktop - double width = 400, - double height = 400, - String /*clip|crop|scale|fill*/ resize = 'clip', - String /*center|top|bottom|left|right*/ crop = 'center', - }) { - final uri = Uri.parse(this); - final host = uri.host; - - final fromStreamCDN = host.endsWith('stream-io-cdn.com'); - final fromStreamDashboard = host.endsWith('stream-cloud-uploads.imgix.net'); - - if (!fromStreamCDN && !fromStreamDashboard) return this; - - final queryParameters = {...uri.queryParameters}; - - if (fromStreamCDN) { - if (queryParameters['h'].isNullOrMatches('*') && - queryParameters['w'].isNullOrMatches('*') && - queryParameters['crop'].isNullOrMatches('*') && - queryParameters['resize'].isNullOrMatches('*')) { - queryParameters['h'] = height.floor().toString(); - queryParameters['w'] = width.floor().toString(); - queryParameters['crop'] = crop; - queryParameters['resize'] = resize; - } - } else if (fromStreamDashboard) { - queryParameters['height'] = height.floor().toString(); - queryParameters['width'] = width.floor().toString(); - queryParameters['fit'] = crop; - } - - return uri.replace(queryParameters: queryParameters).toString(); - } } /// List extension diff --git a/packages/stream_chat_flutter/lib/src/utils/stream_image_cdn.dart b/packages/stream_chat_flutter/lib/src/utils/stream_image_cdn.dart new file mode 100644 index 0000000000..2b9b569b78 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/utils/stream_image_cdn.dart @@ -0,0 +1,175 @@ +/// Resize mode for CDN image transformations. +/// +/// See the [Stream Image Resizing docs](https://getstream.io/chat/docs/flutter-dart/file_uploads/?language=dart#image-resizing) +/// for more information. +enum ResizeMode { + /// Resizes the image to fit within the given dimensions, preserving the + /// aspect ratio. The image may be smaller than the requested size. + clip('clip'), + + /// Resizes and crops the image to exactly fill the given dimensions. + crop('crop'), + + /// Stretches the image to exactly fill the given dimensions, + /// ignoring the aspect ratio. + scale('scale'), + + /// Resizes the image to fill the given dimensions, preserving the + /// aspect ratio. Parts of the image may be cropped. + fill('fill') + ; + + const ResizeMode(this.value); + + /// The raw string value used as a CDN query parameter. + final String value; +} + +/// Crop alignment for CDN image transformations. +/// +/// This determines which part of the image is preserved when cropping. +enum CropMode { + /// Crop from the center of the image. + center('center'), + + /// Crop from the top of the image. + top('top'), + + /// Crop from the bottom of the image. + bottom('bottom'), + + /// Crop from the left of the image. + left('left'), + + /// Crop from the right of the image. + right('right') + ; + + const CropMode(this.value); + + /// The raw string value used as a CDN query parameter. + final String value; +} + +/// Configuration for resizing an image via a CDN. +/// +/// When passed to [StreamImageCDN.resolveUrl], the CDN will resize the image +/// to the given [width] and [height] using the specified [mode] and [crop]. +class ImageResize { + /// Creates a new [ImageResize] configuration. + const ImageResize({ + required this.width, + required this.height, + this.mode = .clip, + this.crop = .center, + }); + + /// The target width in logical pixels. + final double width; + + /// The target height in logical pixels. + final double height; + + /// The resize mode to use. + /// + /// Defaults to [ResizeMode.clip]. + final ResizeMode mode; + + /// The crop alignment when the resize mode requires cropping. + /// + /// Defaults to [CropMode.center]. + final CropMode crop; +} + +/// Handles CDN URL resolution and cache key generation for Stream Chat images. +/// +/// The default implementation supports Stream's own CDN +/// (`stream-io-cdn.com`). +/// +/// To customize behavior for a custom CDN, extend this class and override +/// [resolveUrl] and/or [cacheKey]: +/// +/// ```dart +/// class MyImageCDN extends StreamImageCDN { +/// @override +/// String cacheKey(String imageUrl) { +/// // Custom cache key logic for your CDN. +/// return Uri.parse(imageUrl).path; +/// } +/// } +/// ``` +/// +/// Then inject it via [StreamChatConfigurationData]: +/// +/// ```dart +/// StreamChat( +/// client: client, +/// config: StreamChatConfigurationData( +/// imageCDN: MyImageCDN(), +/// ), +/// child: ..., +/// ) +/// ``` +class StreamImageCDN { + /// Creates a new [StreamImageCDN] instance. + const StreamImageCDN(); + + // The host suffix for Stream's image CDN. + static const _streamCDNHost = 'stream-io-cdn.com'; + + // Query parameter names that are preserved in cache keys. + // + // These are the image-transformation parameters that affect + // which rendition of the image is returned. All other parameters + // (e.g. signed URL tokens) are stripped. + static const _persistedParameters = {'w', 'h', 'resize', 'crop'}; + + /// Resolves the [sourceUrl] by appending resize/transform parameters + /// appropriate for the CDN. + /// + /// When [resize] is null, no resizing parameters are added and the + /// [sourceUrl] is returned unchanged. + /// + /// For non-Stream CDN URLs, returns [sourceUrl] unchanged regardless + /// of [resize]. + /// + /// Override this to customize URL rewriting for a custom CDN. + String resolveUrl(String sourceUrl, {ImageResize? resize}) { + final uri = Uri.tryParse(sourceUrl); + if (uri == null || !uri.host.contains(_streamCDNHost)) return sourceUrl; + if (resize == null) return sourceUrl; + + final queryParameters = { + ...uri.queryParameters, + 'w': resize.width == 0 ? '*' : resize.width.floor().toString(), + 'h': resize.height == 0 ? '*' : resize.height.floor().toString(), + 'resize': resize.mode.value, + 'ro': '0', + if (resize.mode == ResizeMode.crop) 'crop': resize.crop.value, + }; + + return uri.replace(queryParameters: queryParameters).toString(); + } + + /// Returns a stable cache key for [imageUrl], stripping volatile + /// authentication parameters (e.g. CloudFront signed URL tokens) + /// while preserving those that identify distinct image renditions. + /// + /// This uses an allowlist approach, keeping only the parameters in + /// [_persistedParameters] for Stream CDN URLs. + /// + /// For non-Stream CDN URLs, returns the full URL string unchanged. + /// + /// Override this to customize cache key generation for a custom CDN. + String cacheKey(String imageUrl) { + final uri = Uri.tryParse(imageUrl); + if (uri == null || !uri.host.contains(_streamCDNHost)) return imageUrl; + + final filteredParams = { + for (final MapEntry(:key, :value) in uri.queryParameters.entries) + if (_persistedParameters.contains(key)) key: value, + }; + + return uri.replace(queryParameters: filteredParams).toString(); + } +} diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 6ff3c40507..007a41c14c 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -1,7 +1,6 @@ export 'package:jiffy/jiffy.dart'; export 'package:photo_manager/photo_manager.dart' show ThumbnailSize, ThumbnailFormat; export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; - export 'package:stream_core_flutter/stream_core_flutter.dart' show StreamAvatarGroupSize, @@ -172,4 +171,5 @@ export 'src/utils/device_segmentation.dart'; export 'src/utils/extensions.dart'; export 'src/utils/helpers.dart'; export 'src/utils/message_preview_formatter.dart'; +export 'src/utils/stream_image_cdn.dart'; export 'src/utils/typedefs.dart'; diff --git a/packages/stream_chat_flutter/test/src/utils/stream_image_cdn_test.dart b/packages/stream_chat_flutter/test/src/utils/stream_image_cdn_test.dart new file mode 100644 index 0000000000..967ba5a9ed --- /dev/null +++ b/packages/stream_chat_flutter/test/src/utils/stream_image_cdn_test.dart @@ -0,0 +1,237 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter/src/utils/stream_image_cdn.dart'; + +void main() { + const cdn = StreamImageCDN(); + + group('StreamImageCDN.resolveUrl', () { + group('Stream CDN URLs', () { + test('returns unchanged URL when resize is null', () { + const url = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?Policy=abc&Signature=xyz&Key-Pair-Id=123'; + + expect(cdn.resolveUrl(url), equals(url)); + }); + + test('adds resize params when none exist', () { + const url = 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg'; + const resize = ImageResize(width: 200, height: 300); + + final result = cdn.resolveUrl(url, resize: resize); + + expect(result, contains('w=200')); + expect(result, contains('h=300')); + expect(result, contains('resize=clip')); + expect(result, contains('ro=0')); + expect(result, isNot(contains('crop='))); + }); + + test('includes crop param only when mode is crop', () { + const url = 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg'; + const resize = ImageResize( + width: 400, + height: 400, + mode: ResizeMode.crop, + crop: CropMode.top, + ); + + final result = cdn.resolveUrl(url, resize: resize); + + expect(result, contains('resize=crop')); + expect(result, contains('crop=top')); + expect(result, contains('ro=0')); + }); + + test('does not include crop param when mode is not crop', () { + const url = 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg'; + + for (final mode in [ + ResizeMode.clip, + ResizeMode.scale, + ResizeMode.fill, + ]) { + final result = cdn.resolveUrl( + url, + resize: ImageResize(width: 200, height: 200, mode: mode), + ); + + expect( + result, + isNot(contains('crop=')), + reason: 'crop should not be present for mode ${mode.value}', + ); + } + }); + + test('always overrides existing resize params', () { + const url = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?w=100&h=100&resize=fill'; + const resize = ImageResize( + width: 200, + height: 300, + mode: ResizeMode.crop, + crop: CropMode.left, + ); + + final result = cdn.resolveUrl(url, resize: resize); + + expect(result, contains('w=200')); + expect(result, contains('h=300')); + expect(result, contains('resize=crop')); + expect(result, contains('crop=left')); + }); + + test('preserves existing non-resize query parameters', () { + const url = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?Policy=abc&Signature=xyz&Key-Pair-Id=123'; + const resize = ImageResize(width: 200, height: 300); + + final result = cdn.resolveUrl(url, resize: resize); + + expect(result, contains('Policy=abc')); + expect(result, contains('Signature=xyz')); + expect(result, contains('Key-Pair-Id=123')); + expect(result, contains('w=200')); + }); + + test('floors fractional dimensions', () { + const url = 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg'; + const resize = ImageResize(width: 199.7, height: 300.3); + + final result = cdn.resolveUrl(url, resize: resize); + + expect(result, contains('w=199')); + expect(result, contains('h=300')); + }); + + test('uses wildcard for zero dimensions', () { + const url = 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg'; + const resize = ImageResize(width: 0, height: 300); + + final result = cdn.resolveUrl(url, resize: resize); + + expect(result, contains('w=%2A')); + expect(result, contains('h=300')); + }); + }); + + group('non-Stream URLs', () { + test('returns URL unchanged regardless of resize', () { + const url = 'https://example.com/photo.jpg'; + const resize = ImageResize(width: 200, height: 300); + + expect(cdn.resolveUrl(url, resize: resize), equals(url)); + }); + + test('returns URL unchanged when resize is null', () { + const url = 'https://example.com/photo.jpg?token=abc'; + + expect(cdn.resolveUrl(url), equals(url)); + }); + }); + }); + + group('StreamImageCDN.cacheKey', () { + group('Stream CDN URLs', () { + test('strips signing parameters', () { + const url = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?Key-Pair-Id=APKAIHG&Policy=eyJTdGF0&Signature=OeMK5' + '&w=200&h=300&resize=clip&crop=center'; + + final key = cdn.cacheKey(url); + + expect(key, contains('w=200')); + expect(key, contains('h=300')); + expect(key, contains('resize=clip')); + expect(key, contains('crop=center')); + expect(key, isNot(contains('Key-Pair-Id'))); + expect(key, isNot(contains('Policy'))); + expect(key, isNot(contains('Signature'))); + }); + + test('returns URL path only when no resize params exist', () { + const url = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?Key-Pair-Id=APKAIHG&Policy=eyJTdGF0&Signature=OeMK5'; + + final key = cdn.cacheKey(url); + + expect(key, isNot(contains('Key-Pair-Id'))); + expect(key, isNot(contains('Policy'))); + expect(key, isNot(contains('Signature'))); + expect( + key, + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg?', + ); + }); + + test('produces same key for same image with different signatures', () { + const url1 = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?Key-Pair-Id=APKAIHG&Policy=policy1&Signature=sig1' + '&w=200&h=300'; + const url2 = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?Key-Pair-Id=APKAIHG&Policy=policy2&Signature=sig2' + '&w=200&h=300'; + + expect(cdn.cacheKey(url1), equals(cdn.cacheKey(url2))); + }); + + test('produces different keys for different resize dimensions', () { + const url1 = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?w=200&h=300'; + const url2 = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?w=400&h=600'; + + expect(cdn.cacheKey(url1), isNot(equals(cdn.cacheKey(url2)))); + }); + + test('strips oh and ow parameters', () { + const url = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?oh=4032&ow=3024&w=200&h=300'; + + final key = cdn.cacheKey(url); + + expect(key, isNot(contains('oh='))); + expect(key, isNot(contains('ow='))); + expect(key, contains('w=200')); + expect(key, contains('h=300')); + }); + }); + + group('non-Stream URLs', () { + test('returns full URL string unchanged', () { + const url = 'https://example.com/photo.jpg?token=abc'; + + expect(cdn.cacheKey(url), equals(url)); + }); + }); + }); + + group('ResizeMode', () { + test('all modes have correct string values', () { + expect(ResizeMode.clip.value, 'clip'); + expect(ResizeMode.crop.value, 'crop'); + expect(ResizeMode.scale.value, 'scale'); + expect(ResizeMode.fill.value, 'fill'); + }); + }); + + group('CropMode', () { + test('all modes have correct string values', () { + expect(CropMode.center.value, 'center'); + expect(CropMode.top.value, 'top'); + expect(CropMode.bottom.value, 'bottom'); + expect(CropMode.left.value, 'left'); + expect(CropMode.right.value, 'right'); + }); + }); +} From 9b7242eb187c87057bceecd01800118db4d2df2f Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Sun, 8 Mar 2026 14:52:14 +0530 Subject: [PATCH 20/33] feat(llc, ui, core): introduce `StreamReactionListController` and `StreamReactionListView` for paginated reactions (#2533) Co-authored-by: xsahil03x <25670178+xsahil03x@users.noreply.github.com> --- .../stream_chat/lib/src/client/client.dart | 17 + .../lib/src/core/api/message_api.dart | 22 + .../lib/src/core/api/responses.dart | 7 +- .../lib/src/core/api/responses.g.dart | 238 ++++--- .../lib/src/core/models/reaction.dart | 24 +- .../test/src/client/client_test.dart | 28 + .../test/src/core/api/message_api_test.dart | 78 +++ .../test/src/core/models/reaction_test.dart | 35 ++ .../src/message_widget/message_widget.dart | 2 +- .../detail/reaction_detail_sheet.dart | 79 ++- .../stream_reaction_list_view.dart | 224 +++++++ .../lib/stream_chat_flutter.dart | 1 + .../detail/reaction_detail_sheet_test.dart | 209 +++++-- .../src/stream_reaction_list_controller.dart | 182 ++++++ .../lib/stream_chat_flutter_core.dart | 1 + .../stream_reaction_list_controller_test.dart | 579 ++++++++++++++++++ 16 files changed, 1567 insertions(+), 159 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/src/scroll_view/reaction_scroll_view/stream_reaction_list_view.dart create mode 100644 packages/stream_chat_flutter_core/lib/src/stream_reaction_list_controller.dart create mode 100644 packages/stream_chat_flutter_core/test/stream_reaction_list_controller_test.dart diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index f7b529d583..e216c2061d 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -1784,6 +1784,23 @@ class StreamChatClient { pagination: pagination, ); + /// Queries reactions for a [messageId] with optional [filter], [sort], + /// and [pagination]. + /// + /// Unlike [getReactions], this method supports filtering by reaction type, + /// user ID, or creation date, sorting, and cursor-based pagination. + Future queryReactions( + String messageId, { + Filter? filter, + SortOrder? sort, + PaginationParams? pagination, + }) => _chatApi.message.queryReactions( + messageId, + filter: filter, + sort: sort, + pagination: pagination, + ); + /// Update the given message Future updateMessage( Message message, { diff --git a/packages/stream_chat/lib/src/core/api/message_api.dart b/packages/stream_chat/lib/src/core/api/message_api.dart index 3dc132ea1c..5f3fe356cf 100644 --- a/packages/stream_chat/lib/src/core/api/message_api.dart +++ b/packages/stream_chat/lib/src/core/api/message_api.dart @@ -257,6 +257,28 @@ class MessageApi { return QueryReactionsResponse.fromJson(response.data); } + /// Queries reactions for a [messageId] with optional [filter], [sort], + /// and [pagination]. + /// + /// Unlike [getReactions], this method supports filtering by reaction type, + /// user ID, or creation date, sorting, and cursor-based pagination. + Future queryReactions( + String messageId, { + Filter? filter, + SortOrder? sort, + PaginationParams? pagination, + }) async { + final response = await _client.post( + '/messages/$messageId/reactions', + data: jsonEncode({ + if (filter != null) 'filter': filter.toJson(), + if (sort != null) 'sort': sort.map((e) => e.toJson()).toList(), + if (pagination != null) ...pagination.toJson(), + }), + ); + return QueryReactionsResponse.fromJson(response.data); + } + /// Translates the [messageId] in provided [language] Future translateMessage( String messageId, diff --git a/packages/stream_chat/lib/src/core/api/responses.dart b/packages/stream_chat/lib/src/core/api/responses.dart index 00694b0a50..c5bd2e92ab 100644 --- a/packages/stream_chat/lib/src/core/api/responses.dart +++ b/packages/stream_chat/lib/src/core/api/responses.dart @@ -131,13 +131,18 @@ class QueryBannedUsersResponse extends _BaseResponse { static QueryBannedUsersResponse fromJson(Map json) => _$QueryBannedUsersResponseFromJson(json); } -/// Model response for [channel.getReactions] api call +/// Model response for [channel.getReactions] or [channel.queryReactions] api call @JsonSerializable(createToJson: false) class QueryReactionsResponse extends _BaseResponse { /// List of reactions returned by the query @JsonKey(defaultValue: []) late List reactions; + /// The cursor for the next page of results. + /// + /// Will be `null` if there are no more results. + late String? next; + /// Create a new instance from a json static QueryReactionsResponse fromJson(Map json) => _$QueryReactionsResponseFromJson(json); } diff --git a/packages/stream_chat/lib/src/core/api/responses.g.dart b/packages/stream_chat/lib/src/core/api/responses.g.dart index 6ee654641f..4c54864009 100644 --- a/packages/stream_chat/lib/src/core/api/responses.g.dart +++ b/packages/stream_chat/lib/src/core/api/responses.g.dart @@ -25,39 +25,55 @@ SyncResponse _$SyncResponseFromJson(Map json) => SyncResponse() ..duration = json['duration'] as String? ..events = (json['events'] as List?)?.map((e) => Event.fromJson(e as Map)).toList() ?? []; -QueryChannelsResponse _$QueryChannelsResponseFromJson(Map json) => QueryChannelsResponse() +QueryChannelsResponse _$QueryChannelsResponseFromJson( + Map json, +) => QueryChannelsResponse() ..duration = json['duration'] as String? ..channels = (json['channels'] as List?)?.map((e) => ChannelState.fromJson(e as Map)).toList() ?? []; -TranslateMessageResponse _$TranslateMessageResponseFromJson(Map json) => TranslateMessageResponse() +TranslateMessageResponse _$TranslateMessageResponseFromJson( + Map json, +) => TranslateMessageResponse() ..duration = json['duration'] as String? ..message = Message.fromJson(json['message'] as Map); -QueryMembersResponse _$QueryMembersResponseFromJson(Map json) => QueryMembersResponse() +QueryMembersResponse _$QueryMembersResponseFromJson( + Map json, +) => QueryMembersResponse() ..duration = json['duration'] as String? ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? []; -PartialUpdateMemberResponse _$PartialUpdateMemberResponseFromJson(Map json) => - PartialUpdateMemberResponse() - ..duration = json['duration'] as String? - ..channelMember = Member.fromJson(json['channel_member'] as Map); +PartialUpdateMemberResponse _$PartialUpdateMemberResponseFromJson( + Map json, +) => PartialUpdateMemberResponse() + ..duration = json['duration'] as String? + ..channelMember = Member.fromJson( + json['channel_member'] as Map, + ); QueryUsersResponse _$QueryUsersResponseFromJson(Map json) => QueryUsersResponse() ..duration = json['duration'] as String? ..users = (json['users'] as List?)?.map((e) => User.fromJson(e as Map)).toList() ?? []; -QueryBannedUsersResponse _$QueryBannedUsersResponseFromJson(Map json) => QueryBannedUsersResponse() +QueryBannedUsersResponse _$QueryBannedUsersResponseFromJson( + Map json, +) => QueryBannedUsersResponse() ..duration = json['duration'] as String? ..bans = (json['bans'] as List?)?.map((e) => BannedUser.fromJson(e as Map)).toList() ?? []; -QueryReactionsResponse _$QueryReactionsResponseFromJson(Map json) => QueryReactionsResponse() +QueryReactionsResponse _$QueryReactionsResponseFromJson( + Map json, +) => QueryReactionsResponse() ..duration = json['duration'] as String? ..reactions = - (json['reactions'] as List?)?.map((e) => Reaction.fromJson(e as Map)).toList() ?? []; + (json['reactions'] as List?)?.map((e) => Reaction.fromJson(e as Map)).toList() ?? [] + ..next = json['next'] as String?; -QueryRepliesResponse _$QueryRepliesResponseFromJson(Map json) => QueryRepliesResponse() +QueryRepliesResponse _$QueryRepliesResponseFromJson( + Map json, +) => QueryRepliesResponse() ..duration = json['duration'] as String? ..messages = (json['messages'] as List?)?.map((e) => Message.fromJson(e as Map)).toList() ?? []; @@ -67,7 +83,9 @@ ListDevicesResponse _$ListDevicesResponseFromJson(Map json) => ..devices = (json['devices'] as List?)?.map((e) => Device.fromJson(e as Map)).toList() ?? []; -SendAttachmentResponse _$SendAttachmentResponseFromJson(Map json) => SendAttachmentResponse() +SendAttachmentResponse _$SendAttachmentResponseFromJson( + Map json, +) => SendAttachmentResponse() ..duration = json['duration'] as String? ..file = json['file'] as String?; @@ -76,12 +94,16 @@ SendFileResponse _$SendFileResponseFromJson(Map json) => SendFi ..file = json['file'] as String? ..thumbUrl = json['thumb_url'] as String?; -SendReactionResponse _$SendReactionResponseFromJson(Map json) => SendReactionResponse() +SendReactionResponse _$SendReactionResponseFromJson( + Map json, +) => SendReactionResponse() ..duration = json['duration'] as String? ..message = Message.fromJson(json['message'] as Map) ..reaction = Reaction.fromJson(json['reaction'] as Map); -ConnectGuestUserResponse _$ConnectGuestUserResponseFromJson(Map json) => ConnectGuestUserResponse() +ConnectGuestUserResponse _$ConnectGuestUserResponseFromJson( + Map json, +) => ConnectGuestUserResponse() ..duration = json['duration'] as String? ..accessToken = json['access_token'] as String ..user = User.fromJson(json['user'] as Map); @@ -94,7 +116,9 @@ UpdateUsersResponse _$UpdateUsersResponseFromJson(Map json) => ) ?? {}; -UpdateMessageResponse _$UpdateMessageResponseFromJson(Map json) => UpdateMessageResponse() +UpdateMessageResponse _$UpdateMessageResponseFromJson( + Map json, +) => UpdateMessageResponse() ..duration = json['duration'] as String? ..message = Message.fromJson(json['message'] as Map); @@ -107,40 +131,53 @@ GetMessageResponse _$GetMessageResponseFromJson(Map json) => Ge ..message = Message.fromJson(json['message'] as Map) ..channel = json['channel'] == null ? null : ChannelModel.fromJson(json['channel'] as Map); -SearchMessagesResponse _$SearchMessagesResponseFromJson(Map json) => SearchMessagesResponse() +SearchMessagesResponse _$SearchMessagesResponseFromJson( + Map json, +) => SearchMessagesResponse() ..duration = json['duration'] as String? ..results = (json['results'] as List?) - ?.map((e) => GetMessageResponse.fromJson(e as Map)) + ?.map( + (e) => GetMessageResponse.fromJson(e as Map), + ) .toList() ?? [] ..next = json['next'] as String? ..previous = json['previous'] as String?; -GetMessagesByIdResponse _$GetMessagesByIdResponseFromJson(Map json) => GetMessagesByIdResponse() +GetMessagesByIdResponse _$GetMessagesByIdResponseFromJson( + Map json, +) => GetMessagesByIdResponse() ..duration = json['duration'] as String? ..messages = (json['messages'] as List?)?.map((e) => Message.fromJson(e as Map)).toList() ?? []; -UpdateChannelResponse _$UpdateChannelResponseFromJson(Map json) => UpdateChannelResponse() +UpdateChannelResponse _$UpdateChannelResponseFromJson( + Map json, +) => UpdateChannelResponse() ..duration = json['duration'] as String? ..channel = ChannelModel.fromJson(json['channel'] as Map) ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); -PartialUpdateChannelResponse _$PartialUpdateChannelResponseFromJson(Map json) => - PartialUpdateChannelResponse() - ..duration = json['duration'] as String? - ..channel = ChannelModel.fromJson(json['channel'] as Map) - ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList(); +PartialUpdateChannelResponse _$PartialUpdateChannelResponseFromJson( + Map json, +) => PartialUpdateChannelResponse() + ..duration = json['duration'] as String? + ..channel = ChannelModel.fromJson(json['channel'] as Map) + ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList(); -InviteMembersResponse _$InviteMembersResponseFromJson(Map json) => InviteMembersResponse() +InviteMembersResponse _$InviteMembersResponseFromJson( + Map json, +) => InviteMembersResponse() ..duration = json['duration'] as String? ..channel = ChannelModel.fromJson(json['channel'] as Map) ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? [] ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); -RemoveMembersResponse _$RemoveMembersResponseFromJson(Map json) => RemoveMembersResponse() +RemoveMembersResponse _$RemoveMembersResponseFromJson( + Map json, +) => RemoveMembersResponse() ..duration = json['duration'] as String? ..channel = ChannelModel.fromJson(json['channel'] as Map) ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? [] @@ -156,13 +193,17 @@ AddMembersResponse _$AddMembersResponseFromJson(Map json) => Ad ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? [] ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); -AcceptInviteResponse _$AcceptInviteResponseFromJson(Map json) => AcceptInviteResponse() +AcceptInviteResponse _$AcceptInviteResponseFromJson( + Map json, +) => AcceptInviteResponse() ..duration = json['duration'] as String? ..channel = ChannelModel.fromJson(json['channel'] as Map) ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? [] ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); -RejectInviteResponse _$RejectInviteResponseFromJson(Map json) => RejectInviteResponse() +RejectInviteResponse _$RejectInviteResponseFromJson( + Map json, +) => RejectInviteResponse() ..duration = json['duration'] as String? ..channel = ChannelModel.fromJson(json['channel'] as Map) ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? [] @@ -171,7 +212,9 @@ RejectInviteResponse _$RejectInviteResponseFromJson(Map json) = EmptyResponse _$EmptyResponseFromJson(Map json) => EmptyResponse()..duration = json['duration'] as String?; -ChannelStateResponse _$ChannelStateResponseFromJson(Map json) => ChannelStateResponse() +ChannelStateResponse _$ChannelStateResponseFromJson( + Map json, +) => ChannelStateResponse() ..duration = json['duration'] as String? ..channel = ChannelModel.fromJson(json['channel'] as Map) ..messages = @@ -180,7 +223,9 @@ ChannelStateResponse _$ChannelStateResponseFromJson(Map json) = ..watcherCount = (json['watcher_count'] as num?)?.toInt() ?? 0 ..read = (json['read'] as List?)?.map((e) => Read.fromJson(e as Map)).toList() ?? []; -OGAttachmentResponse _$OGAttachmentResponseFromJson(Map json) => OGAttachmentResponse() +OGAttachmentResponse _$OGAttachmentResponseFromJson( + Map json, +) => OGAttachmentResponse() ..duration = json['duration'] as String? ..ogScrapeUrl = json['og_scrape_url'] as String ..assetUrl = json['asset_url'] as String? @@ -199,7 +244,9 @@ UserBlockResponse _$UserBlockResponseFromJson(Map json) => User ..blockedUserId = json['blocked_user_id'] as String? ?? '' ..createdAt = DateTime.parse(json['created_at'] as String); -BlockedUsersResponse _$BlockedUsersResponseFromJson(Map json) => BlockedUsersResponse() +BlockedUsersResponse _$BlockedUsersResponseFromJson( + Map json, +) => BlockedUsersResponse() ..duration = json['duration'] as String? ..blocks = (json['blocks'] as List?)?.map((e) => UserBlock.fromJson(e as Map)).toList() ?? []; @@ -216,23 +263,39 @@ UpdatePollResponse _$UpdatePollResponseFromJson(Map json) => Up ..duration = json['duration'] as String? ..poll = Poll.fromJson(json['poll'] as Map); -CreatePollOptionResponse _$CreatePollOptionResponseFromJson(Map json) => CreatePollOptionResponse() +CreatePollOptionResponse _$CreatePollOptionResponseFromJson( + Map json, +) => CreatePollOptionResponse() ..duration = json['duration'] as String? - ..pollOption = PollOption.fromJson(json['poll_option'] as Map); + ..pollOption = PollOption.fromJson( + json['poll_option'] as Map, + ); -GetPollOptionResponse _$GetPollOptionResponseFromJson(Map json) => GetPollOptionResponse() +GetPollOptionResponse _$GetPollOptionResponseFromJson( + Map json, +) => GetPollOptionResponse() ..duration = json['duration'] as String? - ..pollOption = PollOption.fromJson(json['poll_option'] as Map); + ..pollOption = PollOption.fromJson( + json['poll_option'] as Map, + ); -UpdatePollOptionResponse _$UpdatePollOptionResponseFromJson(Map json) => UpdatePollOptionResponse() +UpdatePollOptionResponse _$UpdatePollOptionResponseFromJson( + Map json, +) => UpdatePollOptionResponse() ..duration = json['duration'] as String? - ..pollOption = PollOption.fromJson(json['poll_option'] as Map); + ..pollOption = PollOption.fromJson( + json['poll_option'] as Map, + ); -CastPollVoteResponse _$CastPollVoteResponseFromJson(Map json) => CastPollVoteResponse() +CastPollVoteResponse _$CastPollVoteResponseFromJson( + Map json, +) => CastPollVoteResponse() ..duration = json['duration'] as String? ..vote = PollVote.fromJson(json['vote'] as Map); -RemovePollVoteResponse _$RemovePollVoteResponseFromJson(Map json) => RemovePollVoteResponse() +RemovePollVoteResponse _$RemovePollVoteResponseFromJson( + Map json, +) => RemovePollVoteResponse() ..duration = json['duration'] as String? ..vote = PollVote.fromJson(json['vote'] as Map); @@ -241,7 +304,9 @@ QueryPollsResponse _$QueryPollsResponseFromJson(Map json) => Qu ..polls = (json['polls'] as List?)?.map((e) => Poll.fromJson(e as Map)).toList() ?? [] ..next = json['next'] as String?; -QueryPollVotesResponse _$QueryPollVotesResponseFromJson(Map json) => QueryPollVotesResponse() +QueryPollVotesResponse _$QueryPollVotesResponseFromJson( + Map json, +) => QueryPollVotesResponse() ..duration = json['duration'] as String? ..votes = (json['votes'] as List?)?.map((e) => PollVote.fromJson(e as Map)).toList() ?? [] ..next = json['next'] as String?; @@ -250,11 +315,15 @@ GetThreadResponse _$GetThreadResponseFromJson(Map json) => GetT ..duration = json['duration'] as String? ..thread = Thread.fromJson(json['thread'] as Map); -UpdateThreadResponse _$UpdateThreadResponseFromJson(Map json) => UpdateThreadResponse() +UpdateThreadResponse _$UpdateThreadResponseFromJson( + Map json, +) => UpdateThreadResponse() ..duration = json['duration'] as String? ..thread = Thread.fromJson(json['thread'] as Map); -QueryThreadsResponse _$QueryThreadsResponseFromJson(Map json) => QueryThreadsResponse() +QueryThreadsResponse _$QueryThreadsResponseFromJson( + Map json, +) => QueryThreadsResponse() ..duration = json['duration'] as String? ..threads = (json['threads'] as List?)?.map((e) => Thread.fromJson(e as Map)).toList() ?? [] ..next = json['next'] as String?; @@ -272,22 +341,34 @@ QueryDraftsResponse _$QueryDraftsResponseFromJson(Map json) => ..drafts = (json['drafts'] as List?)?.map((e) => Draft.fromJson(e as Map)).toList() ?? [] ..next = json['next'] as String?; -CreateReminderResponse _$CreateReminderResponseFromJson(Map json) => CreateReminderResponse() +CreateReminderResponse _$CreateReminderResponseFromJson( + Map json, +) => CreateReminderResponse() ..duration = json['duration'] as String? - ..reminder = MessageReminder.fromJson(json['reminder'] as Map); + ..reminder = MessageReminder.fromJson( + json['reminder'] as Map, + ); -UpdateReminderResponse _$UpdateReminderResponseFromJson(Map json) => UpdateReminderResponse() +UpdateReminderResponse _$UpdateReminderResponseFromJson( + Map json, +) => UpdateReminderResponse() ..duration = json['duration'] as String? - ..reminder = MessageReminder.fromJson(json['reminder'] as Map); + ..reminder = MessageReminder.fromJson( + json['reminder'] as Map, + ); -QueryRemindersResponse _$QueryRemindersResponseFromJson(Map json) => QueryRemindersResponse() +QueryRemindersResponse _$QueryRemindersResponseFromJson( + Map json, +) => QueryRemindersResponse() ..duration = json['duration'] as String? ..reminders = (json['reminders'] as List?)?.map((e) => MessageReminder.fromJson(e as Map)).toList() ?? [] ..next = json['next'] as String?; -GetUnreadCountResponse _$GetUnreadCountResponseFromJson(Map json) => GetUnreadCountResponse() +GetUnreadCountResponse _$GetUnreadCountResponseFromJson( + Map json, +) => GetUnreadCountResponse() ..duration = json['duration'] as String? ..totalUnreadCount = (json['total_unread_count'] as num).toInt() ..totalUnreadThreadsCount = (json['total_unread_threads_count'] as num).toInt() @@ -295,37 +376,48 @@ GetUnreadCountResponse _$GetUnreadCountResponseFromJson(Map jso (k, e) => MapEntry(k, (e as num).toInt()), ) ..channels = (json['channels'] as List) - .map((e) => UnreadCountsChannel.fromJson(e as Map)) + .map( + (e) => UnreadCountsChannel.fromJson(e as Map), + ) .toList() ..channelType = (json['channel_type'] as List) - .map((e) => UnreadCountsChannelType.fromJson(e as Map)) + .map( + (e) => UnreadCountsChannelType.fromJson(e as Map), + ) .toList() ..threads = (json['threads'] as List) - .map((e) => UnreadCountsThread.fromJson(e as Map)) + .map( + (e) => UnreadCountsThread.fromJson(e as Map), + ) .toList(); -UpsertPushPreferencesResponse _$UpsertPushPreferencesResponseFromJson(Map json) => - UpsertPushPreferencesResponse() - ..duration = json['duration'] as String? - ..userPreferences = - (json['user_preferences'] as Map?)?.map( - (k, e) => MapEntry(k, PushPreference.fromJson(e as Map)), - ) ?? - {} - ..userChannelPreferences = - (json['user_channel_preferences'] as Map?)?.map( +UpsertPushPreferencesResponse _$UpsertPushPreferencesResponseFromJson( + Map json, +) => UpsertPushPreferencesResponse() + ..duration = json['duration'] as String? + ..userPreferences = + (json['user_preferences'] as Map?)?.map( + (k, e) => MapEntry(k, PushPreference.fromJson(e as Map)), + ) ?? + {} + ..userChannelPreferences = + (json['user_channel_preferences'] as Map?)?.map( + (k, e) => MapEntry( + k, + (e as Map).map( (k, e) => MapEntry( k, - (e as Map).map( - (k, e) => MapEntry(k, ChannelPushPreference.fromJson(e as Map)), - ), + ChannelPushPreference.fromJson(e as Map), ), - ) ?? - {}; - -GetActiveLiveLocationsResponse _$GetActiveLiveLocationsResponseFromJson(Map json) => - GetActiveLiveLocationsResponse() - ..duration = json['duration'] as String? - ..activeLiveLocations = (json['active_live_locations'] as List) - .map((e) => Location.fromJson(e as Map)) - .toList(); + ), + ), + ) ?? + {}; + +GetActiveLiveLocationsResponse _$GetActiveLiveLocationsResponseFromJson( + Map json, +) => GetActiveLiveLocationsResponse() + ..duration = json['duration'] as String? + ..activeLiveLocations = (json['active_live_locations'] as List) + .map((e) => Location.fromJson(e as Map)) + .toList(); diff --git a/packages/stream_chat/lib/src/core/models/reaction.dart b/packages/stream_chat/lib/src/core/models/reaction.dart index 06a1433943..c60c7b85b8 100644 --- a/packages/stream_chat/lib/src/core/models/reaction.dart +++ b/packages/stream_chat/lib/src/core/models/reaction.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:stream_chat/src/core/models/comparable_field.dart'; import 'package:stream_chat/src/core/models/user.dart'; import 'package:stream_chat/src/core/util/serializer.dart'; @@ -7,7 +8,7 @@ part 'reaction.g.dart'; /// The class that defines a reaction @JsonSerializable() -class Reaction extends Equatable { +class Reaction extends Equatable implements ComparableFieldProvider { /// Constructor used for json serialization Reaction({ this.messageId, @@ -130,4 +131,25 @@ class Reaction extends Equatable { updatedAt, extraData, ]; + + @override + ComparableField? getComparableField(String sortKey) { + final value = switch (sortKey) { + ReactionSortKey.createdAt => createdAt, + _ => null, + }; + + return ComparableField.fromValue(value); + } +} + +/// Extension type representing sortable fields for [Reaction]. +/// +/// This type provides type-safe keys that can be used for sorting reactions +/// in queries. Each constant represents a field that can be sorted on. +extension type const ReactionSortKey(String key) implements String { + /// Sort reactions by their creation date. + /// + /// This is the default sort field (in ascending order). + static const createdAt = ReactionSortKey('created_at'); } diff --git a/packages/stream_chat/test/src/client/client_test.dart b/packages/stream_chat/test/src/client/client_test.dart index 3ae52605e2..b2690505bd 100644 --- a/packages/stream_chat/test/src/client/client_test.dart +++ b/packages/stream_chat/test/src/client/client_test.dart @@ -3709,6 +3709,34 @@ void main() { verifyNoMoreInteractions(api.message); }); + test('`.queryReactions`', () async { + const messageId = 'test-message-id'; + + final reactions = List.generate( + 3, + (index) => Reaction( + type: 'test-reactions-type-$index', + messageId: messageId, + ), + ); + + when( + () => api.message.queryReactions(messageId), + ).thenAnswer( + (_) async => QueryReactionsResponse() + ..reactions = reactions + ..next = null, + ); + + final res = await client.queryReactions(messageId); + expect(res, isNotNull); + expect(res.reactions.length, reactions.length); + expect(res.reactions.every((it) => it.messageId == messageId), isTrue); + + verify(() => api.message.queryReactions(messageId)).called(1); + verifyNoMoreInteractions(api.message); + }); + test('`.updateMessage`', () async { final message = Message(id: 'test-message-id', text: 'Hello!'); diff --git a/packages/stream_chat/test/src/core/api/message_api_test.dart b/packages/stream_chat/test/src/core/api/message_api_test.dart index fb6410166f..0cfeacdd83 100644 --- a/packages/stream_chat/test/src/core/api/message_api_test.dart +++ b/packages/stream_chat/test/src/core/api/message_api_test.dart @@ -689,6 +689,84 @@ void main() { verifyNoMoreInteractions(client); }); + test('queryReactions', () async { + const messageId = 'test-message-id'; + const path = '/messages/$messageId/reactions'; + + final reactions = List.generate( + 3, + (index) => Reaction( + type: 'test-reaction-type-$index', + messageId: messageId, + ), + ); + + when( + () => client.post(path, data: any(named: 'data')), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'reactions': [ + ...reactions.map( + (it) => {...it.toJson(), 'message_id': messageId}, + ), + ], + 'next': null, + }, + ), + ); + + final res = await messageApi.queryReactions(messageId); + + expect(res, isNotNull); + expect(res.reactions.length, reactions.length); + expect(res.reactions.every((it) => it.messageId == messageId), isTrue); + expect(res.next, isNull); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('queryReactions with next cursor', () async { + const messageId = 'test-message-id'; + const path = '/messages/$messageId/reactions'; + const nextCursor = 'next_page_cursor'; + + final reactions = List.generate( + 3, + (index) => Reaction( + type: 'test-reaction-type-$index', + messageId: messageId, + ), + ); + + when( + () => client.post(path, data: any(named: 'data')), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'reactions': [ + ...reactions.map( + (it) => {...it.toJson(), 'message_id': messageId}, + ), + ], + 'next': nextCursor, + }, + ), + ); + + final res = await messageApi.queryReactions(messageId); + + expect(res, isNotNull); + expect(res.reactions.length, reactions.length); + expect(res.next, nextCursor); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + test('queryDrafts', () async { const path = '/drafts/query'; diff --git a/packages/stream_chat/test/src/core/models/reaction_test.dart b/packages/stream_chat/test/src/core/models/reaction_test.dart index 1b5bdb4de7..2b9ed6dbeb 100644 --- a/packages/stream_chat/test/src/core/models/reaction_test.dart +++ b/packages/stream_chat/test/src/core/models/reaction_test.dart @@ -125,6 +125,41 @@ void main() { expect(newReaction.userId, 'test'); }); + group('ComparableFieldProvider', () { + test('should return ComparableField for reaction.createdAt', () { + final createdAt = DateTime(2020, 1, 28); + final reaction = Reaction(type: 'like', createdAt: createdAt); + + final field = reaction.getComparableField(ReactionSortKey.createdAt); + expect(field, isNotNull); + expect(field!.value, equals(createdAt)); + }); + + test('should return null for non-existent field keys', () { + final reaction = Reaction(type: 'like'); + + final field = reaction.getComparableField('non_existent_key'); + expect(field, isNull); + }); + + test('should compare two reactions correctly using createdAt', () { + final recentReaction = Reaction( + type: 'like', + createdAt: DateTime(2020, 6, 15), + ); + final olderReaction = Reaction( + type: 'like', + createdAt: DateTime(2020, 6, 10), + ); + + final field1 = recentReaction.getComparableField(ReactionSortKey.createdAt); + final field2 = olderReaction.getComparableField(ReactionSortKey.createdAt); + + expect(field1!.compareTo(field2!), greaterThan(0)); // more recent > older + expect(field2.compareTo(field1), lessThan(0)); // older < more recent + }); + }); + test('merge', () { final reaction = Reaction.fromJson(jsonFixture('reaction.json')); final newUserCreateTime = DateTime.now(); diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart index baa03662e8..78978ed681 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart @@ -629,7 +629,7 @@ class _StreamMessageWidgetState extends State /// `true`, if there are reactions to show, and if the message is not deleted. /// {@endtemplate} bool get shouldShowReactions => - widget.showReactions && (widget.message.latestReactions?.isNotEmpty == true) && !widget.message.isDeleted; + widget.showReactions && (widget.message.reactionGroups?.isNotEmpty == true) && !widget.message.isDeleted; @override bool get wantKeepAlive => widget.message.attachments.isNotEmpty; diff --git a/packages/stream_chat_flutter/lib/src/reactions/detail/reaction_detail_sheet.dart b/packages/stream_chat_flutter/lib/src/reactions/detail/reaction_detail_sheet.dart index 5d99d0d60c..1d3f5595b0 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/detail/reaction_detail_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/detail/reaction_detail_sheet.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar.dart'; import 'package:stream_chat_flutter/src/message_action/message_action.dart'; +import 'package:stream_chat_flutter/src/scroll_view/reaction_scroll_view/stream_reaction_list_view.dart'; +import 'package:stream_chat_flutter/src/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat_configuration.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart'; @@ -8,7 +10,10 @@ import 'package:stream_core_flutter/stream_core_flutter.dart'; /// A bottom sheet that displays detailed reaction information for a message. /// /// Shows the total reaction count, emoji filter chips for each reaction type, -/// and a scrollable list of users who reacted. +/// and a paginated scrollable list of users who reacted. +/// +/// Reactions are fetched from the server using [StreamReactionListController], +/// supporting cursor-based pagination for large reaction lists. /// /// Use [ReactionDetailSheet.show] to display the sheet. class ReactionDetailSheet extends StatefulWidget { @@ -77,7 +82,54 @@ class ReactionDetailSheet extends StatefulWidget { } class _ReactionDetailSheetState extends State { - late String? _selectedReactionType = widget.initialReactionType; + late StreamReactionListController _controller; + late String? _currentReactionType = widget.initialReactionType; + + @override + void initState() { + super.initState(); + _initializeController(); + } + + @override + void didUpdateWidget(covariant ReactionDetailSheet oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.message.id != widget.message.id) { + _controller.dispose(); // Dispose the old controller. + _initializeController(); // Initialize a new controller. + } + } + + void _initializeController() { + _controller = .new( + client: StreamChat.of(context).client, + messageId: widget.message.id, + sort: const [.desc(ReactionSortKey.createdAt)], + filter: switch (_currentReactionType) { + final type? => .equal('type', type), + _ => null, + }, + ); + } + + void _onReactionTypeSelected(String? type) { + if (type == _currentReactionType) return; + setState(() => _currentReactionType = type); + + final updatedFilter = switch (type) { + final type? => Filter.equal('type', type), + _ => null, + }; + + _controller.filter = updatedFilter; + _controller.doInitialLoad(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { @@ -87,19 +139,13 @@ class _ReactionDetailSheetState extends State { final config = StreamChatConfiguration.of(context); final resolver = config.reactionIconResolver; - final allReactions = widget.message.latestReactions ?? []; final ownReactions = [...?widget.message.ownReactions]; final ownReactionsMap = {for (final it in ownReactions) it.type: it}; final reactionGroups = widget.message.reactionGroups ?? {}; final currentUserId = StreamChatCore.of(context).currentUser?.id; - final filteredReactions = switch (_selectedReactionType) { - final type? => allReactions.where((r) => r.type == type).toList(), - _ => allReactions, - }; - - final visibleCount = switch (_selectedReactionType) { + final visibleCount = switch (_currentReactionType) { final type? => reactionGroups[type]?.count ?? 0, _ => reactionGroups.values.fold(0, (sum, g) => sum + g.count), }; @@ -109,7 +155,6 @@ class _ReactionDetailSheetState extends State { crossAxisAlignment: .stretch, children: [ Padding( - //visibleCount == 1 ? '1 Reaction' : '$visibleCount Reactions', padding: .symmetric(horizontal: spacing.sm), child: Text( switch (visibleCount) { @@ -122,8 +167,8 @@ class _ReactionDetailSheetState extends State { ), SizedBox(height: spacing.sm), StreamEmojiChipBar( - selected: _selectedReactionType, - onSelected: (type) => setState(() => _selectedReactionType = type), + selected: _currentReactionType, + onSelected: _onReactionTypeSelected, items: [ for (final MapEntry(:key, :value) in reactionGroups.entries) StreamEmojiChipItem( @@ -150,12 +195,12 @@ class _ReactionDetailSheetState extends State { ), SizedBox(height: spacing.md), Expanded( - child: ListView.builder( - controller: widget.scrollController, + child: StreamReactionListView( + controller: _controller, + scrollController: widget.scrollController, padding: .symmetric(horizontal: spacing.xxs), - itemCount: filteredReactions.length, - itemBuilder: (context, index) { - final reaction = filteredReactions[index]; + itemBuilder: (context, reactions, index) { + final reaction = reactions[index]; final user = reaction.user; if (user == null) return const SizedBox.shrink(); diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/reaction_scroll_view/stream_reaction_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/reaction_scroll_view/stream_reaction_list_view.dart new file mode 100644 index 0000000000..914dac212c --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/scroll_view/reaction_scroll_view/stream_reaction_list_view.dart @@ -0,0 +1,224 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_error_widget.dart'; +import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_load_more_error.dart'; +import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_load_more_indicator.dart'; +import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_loading_widget.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Default separator builder for [StreamReactionListView]. +Widget defaultReactionListViewSeparatorBuilder( + BuildContext context, + List reactions, + int index, +) => const SizedBox.shrink(); + +/// Signature for the item builder that creates the children of the +/// [StreamReactionListView]. +typedef StreamReactionListViewIndexedWidgetBuilder = PagedValueScrollViewIndexedWidgetBuilder; + +/// {@template streamReactionListView} +/// A [ListView] that shows a list of [Reaction]s. It uses a +/// [StreamReactionListController] to load the reactions in paginated form. +/// +/// Example: +/// +/// ```dart +/// StreamReactionListView( +/// controller: controller, +/// itemBuilder: (context, reactions, index) { +/// final reaction = reactions[index]; +/// return ListTile(title: Text(reaction.type)); +/// }, +/// ) +/// ``` +/// +/// See also: +/// * [StreamReactionListController] +/// {@endtemplate} +class StreamReactionListView extends StatelessWidget { + /// Creates a new instance of [StreamReactionListView]. + const StreamReactionListView({ + super.key, + required this.controller, + required this.itemBuilder, + this.separatorBuilder = defaultReactionListViewSeparatorBuilder, + this.emptyBuilder, + this.loadingBuilder, + this.errorBuilder, + this.loadMoreTriggerIndex = 3, + this.scrollDirection = Axis.vertical, + this.reverse = false, + this.scrollController, + this.primary, + this.physics, + this.shrinkWrap = false, + this.padding, + this.addAutomaticKeepAlives = true, + this.addRepaintBoundaries = true, + this.addSemanticIndexes = true, + this.cacheExtent, + this.dragStartBehavior = DragStartBehavior.start, + this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, + this.restorationId, + this.clipBehavior = Clip.hardEdge, + }); + + /// The [StreamReactionListController] used to control the list of reactions. + final StreamReactionListController controller; + + /// A builder that is called to build items in the [ListView]. + final StreamReactionListViewIndexedWidgetBuilder itemBuilder; + + /// A builder that is called to build the list separator. + final PagedValueScrollViewIndexedWidgetBuilder separatorBuilder; + + /// A builder that is called to build the empty state of the list. + final WidgetBuilder? emptyBuilder; + + /// A builder that is called to build the loading state of the list. + final WidgetBuilder? loadingBuilder; + + /// A builder that is called to build the error state of the list. + final Widget Function(BuildContext, StreamChatError)? errorBuilder; + + /// The index to take into account when triggering [controller.loadMore]. + final int loadMoreTriggerIndex; + + /// {@template flutter.widgets.scroll_view.scrollDirection} + /// The axis along which the scroll view scrolls. + /// + /// Defaults to [Axis.vertical]. + /// {@endtemplate} + final Axis scrollDirection; + + /// The amount of space by which to inset the children. + final EdgeInsetsGeometry? padding; + + /// Whether to wrap each child in an [AutomaticKeepAlive]. + /// + /// Defaults to true. + final bool addAutomaticKeepAlives; + + /// Whether to wrap each child in a [RepaintBoundary]. + /// + /// Defaults to true. + final bool addRepaintBoundaries; + + /// Whether to wrap each child in an [IndexedSemantics]. + /// + /// Defaults to true. + final bool addSemanticIndexes; + + /// {@template flutter.widgets.scroll_view.reverse} + /// Whether the scroll view scrolls in the reading direction. + /// + /// Defaults to false. + /// {@endtemplate} + final bool reverse; + + /// {@template flutter.widgets.scroll_view.controller} + /// An object that can be used to control the position to which this scroll + /// view is scrolled. + /// {@endtemplate} + final ScrollController? scrollController; + + /// {@template flutter.widgets.scroll_view.primary} + /// Whether this is the primary scroll view associated with the parent + /// [PrimaryScrollController]. + /// {@endtemplate} + final bool? primary; + + /// {@template flutter.widgets.scroll_view.shrinkWrap} + /// Whether the extent of the scroll view in the [scrollDirection] should be + /// determined by the contents being viewed. + /// + /// Defaults to false. + /// {@endtemplate} + final bool shrinkWrap; + + /// {@template flutter.widgets.scroll_view.physics} + /// How the scroll view should respond to user input. + /// {@endtemplate} + final ScrollPhysics? physics; + + /// {@macro flutter.rendering.RenderViewportBase.cacheExtent} + final double? cacheExtent; + + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + + /// {@template flutter.widgets.scroll_view.keyboardDismissBehavior} + /// [ScrollViewKeyboardDismissBehavior] the defines how this [ScrollView] will + /// dismiss the keyboard automatically. + /// {@endtemplate} + final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior; + + /// {@macro flutter.widgets.scrollable.restorationId} + final String? restorationId; + + /// {@macro flutter.material.Material.clipBehavior} + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + + @override + Widget build(BuildContext context) => PagedValueListView( + scrollDirection: scrollDirection, + padding: padding, + physics: physics, + reverse: reverse, + controller: controller, + scrollController: scrollController, + primary: primary, + shrinkWrap: shrinkWrap, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + keyboardDismissBehavior: keyboardDismissBehavior, + restorationId: restorationId, + dragStartBehavior: dragStartBehavior, + cacheExtent: cacheExtent, + clipBehavior: clipBehavior, + loadMoreTriggerIndex: loadMoreTriggerIndex, + separatorBuilder: separatorBuilder, + itemBuilder: itemBuilder, + emptyBuilder: (context) => + emptyBuilder?.call(context) ?? + Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: StreamScrollViewEmptyWidget( + emptyIcon: Icon( + context.streamIcons.emojiSmile, + size: 148, + color: StreamChatTheme.of(context).colorTheme.disabled, + ), + emptyTitle: Text( + 'No reactions yet', + style: StreamChatTheme.of(context).textTheme.headline, + ), + ), + ), + ), + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.list( + onTap: controller.retry, + error: const Text('Error loading reactions'), + ), + loadMoreIndicatorBuilder: (context) => const Center( + child: Padding( + padding: EdgeInsets.all(16), + child: StreamScrollViewLoadMoreIndicator(), + ), + ), + loadingBuilder: (context) => loadingBuilder?.call(context) ?? const Center(child: StreamScrollViewLoadingWidget()), + errorBuilder: (context, error) => + errorBuilder?.call(context, error) ?? + Center( + child: StreamScrollViewErrorWidget( + errorTitle: const Text('Error loading reactions'), + onRetryPressed: controller.refresh, + ), + ), + ); +} diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 007a41c14c..c2c86e280e 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -152,6 +152,7 @@ export 'src/scroll_view/photo_gallery/stream_photo_gallery_controller.dart'; export 'src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart'; export 'src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_tile.dart'; export 'src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_view.dart'; +export 'src/scroll_view/reaction_scroll_view/stream_reaction_list_view.dart'; export 'src/scroll_view/stream_scroll_view_empty_widget.dart'; export 'src/scroll_view/stream_scroll_view_indexed_widget_builder.dart'; export 'src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart'; diff --git a/packages/stream_chat_flutter/test/src/reactions/detail/reaction_detail_sheet_test.dart b/packages/stream_chat_flutter/test/src/reactions/detail/reaction_detail_sheet_test.dart index 3995c9d555..dde8d56a9f 100644 --- a/packages/stream_chat_flutter/test/src/reactions/detail/reaction_detail_sheet_test.dart +++ b/packages/stream_chat_flutter/test/src/reactions/detail/reaction_detail_sheet_test.dart @@ -9,6 +9,11 @@ import '../../mocks.dart'; void main() { late MockClient mockClient; + setUpAll(() { + registerFallbackValue(const PaginationParams()); + registerFallbackValue(Filter.equal('type', 'like')); + }); + setUp(() { mockClient = MockClient(); @@ -22,23 +27,37 @@ void main() { tearDown(() => reset(mockClient)); testWidgets('shows total reaction count and all reactions by default', (tester) async { + final reactions = [ + Reaction( + type: 'love', + messageId: 'test-message', + userId: 'user-1', + user: User(id: 'user-1', name: 'User 1'), + createdAt: DateTime.now(), + ), + Reaction( + type: 'like', + messageId: 'test-message', + userId: 'user-2', + user: User(id: 'user-2', name: 'User 2'), + createdAt: DateTime.now(), + ), + ]; + + when( + () => mockClient.queryReactions( + 'test-message', + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer( + (_) async => QueryReactionsResponse() + ..reactions = reactions + ..next = null, + ); + final message = _buildMessage( - latestReactions: [ - Reaction( - type: 'love', - messageId: 'test-message', - userId: 'user-1', - user: User(id: 'user-1', name: 'User 1'), - createdAt: DateTime.now(), - ), - Reaction( - type: 'like', - messageId: 'test-message', - userId: 'user-2', - user: User(id: 'user-2', name: 'User 2'), - createdAt: DateTime.now(), - ), - ], reactionGroups: { 'love': ReactionGroup(count: 1, sumScores: 1), 'like': ReactionGroup(count: 1, sumScores: 1), @@ -63,23 +82,30 @@ void main() { }); testWidgets('applies initial reaction filter when provided', (tester) async { + final loveReaction = Reaction( + type: 'love', + messageId: 'test-message', + userId: 'user-1', + user: User(id: 'user-1', name: 'User 1'), + createdAt: DateTime.now(), + ); + + // The controller is initialised with filter: type == 'love', so + // queryReactions will return only the love reaction. + when( + () => mockClient.queryReactions( + 'test-message', + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer( + (_) async => QueryReactionsResponse() + ..reactions = [loveReaction] + ..next = null, + ); + final message = _buildMessage( - latestReactions: [ - Reaction( - type: 'love', - messageId: 'test-message', - userId: 'user-1', - user: User(id: 'user-1', name: 'User 1'), - createdAt: DateTime.now(), - ), - Reaction( - type: 'like', - messageId: 'test-message', - userId: 'user-2', - user: User(id: 'user-2', name: 'User 2'), - createdAt: DateTime.now(), - ), - ], reactionGroups: { 'love': ReactionGroup(count: 1, sumScores: 1), 'like': ReactionGroup(count: 1, sumScores: 1), @@ -116,8 +142,20 @@ void main() { createdAt: DateTime.now(), ); + when( + () => mockClient.queryReactions( + 'test-message', + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer( + (_) async => QueryReactionsResponse() + ..reactions = [reaction] + ..next = null, + ); + final message = _buildMessage( - latestReactions: [reaction], ownReactions: [reaction], reactionGroups: { 'love': ReactionGroup(count: 1, sumScores: 1), @@ -149,16 +187,28 @@ void main() { testWidgets('does not pop when non-own reaction row is tapped', (tester) async { MessageAction? action; + final otherReaction = Reaction( + type: 'love', + messageId: 'test-message', + userId: 'user-1', + user: User(id: 'user-1', name: 'User 1'), + createdAt: DateTime.now(), + ); + + when( + () => mockClient.queryReactions( + 'test-message', + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer( + (_) async => QueryReactionsResponse() + ..reactions = [otherReaction] + ..next = null, + ); + final message = _buildMessage( - latestReactions: [ - Reaction( - type: 'love', - messageId: 'test-message', - userId: 'user-1', - user: User(id: 'user-1', name: 'User 1'), - createdAt: DateTime.now(), - ), - ], ownReactions: [ Reaction( type: 'love', @@ -196,35 +246,30 @@ void main() { }); group('ReactionDetailSheet Golden Tests', () { + final reactions = [ + Reaction( + type: 'love', + messageId: 'test-message', + userId: 'user-1', + user: User(id: 'user-1', name: 'User 1'), + createdAt: DateTime(2026, 1, 1, 10, 0), + ), + Reaction( + type: 'like', + messageId: 'test-message', + userId: 'user-2', + user: User(id: 'user-2', name: 'User 2'), + createdAt: DateTime(2026, 1, 1, 10, 1), + ), + ]; + final message = _buildMessage( - latestReactions: [ - Reaction( - type: 'love', - messageId: 'test-message', - userId: 'user-1', - user: User(id: 'user-1', name: 'User 1'), - createdAt: DateTime(2026, 1, 1, 10, 0), - ), - Reaction( - type: 'like', - messageId: 'test-message', - userId: 'user-2', - user: User(id: 'user-2', name: 'User 2'), - createdAt: DateTime(2026, 1, 1, 10, 1), - ), - ], reactionGroups: { 'love': ReactionGroup(count: 1, sumScores: 1), 'like': ReactionGroup(count: 1, sumScores: 1), }, ); - Future settleSheet(WidgetTester tester) async { - // Pump once to trigger post-frame modal opening, then settle animation. - await tester.pump(); - await tester.pumpAndSettle(const Duration(seconds: 1)); - } - for (final brightness in Brightness.values) { final theme = brightness.name; @@ -232,7 +277,23 @@ void main() { 'ReactionDetailSheet in $theme theme', fileName: 'reaction_detail_sheet_$theme', constraints: const BoxConstraints(maxWidth: 400, maxHeight: 700), - pumpBeforeTest: settleSheet, + pumpBeforeTest: (tester) async { + when( + () => mockClient.queryReactions( + 'test-message', + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer( + (_) async => QueryReactionsResponse() + ..reactions = reactions + ..next = null, + ); + // Pump once to trigger post-frame modal opening, then settle animation. + await tester.pump(); + await tester.pumpAndSettle(const Duration(seconds: 1)); + }, builder: () => _wrapWithMaterialApp( client: mockClient, brightness: brightness, @@ -244,7 +305,23 @@ void main() { 'ReactionDetailSheet filtered in $theme theme', fileName: 'reaction_detail_sheet_filtered_$theme', constraints: const BoxConstraints(maxWidth: 400, maxHeight: 700), - pumpBeforeTest: settleSheet, + pumpBeforeTest: (tester) async { + when( + () => mockClient.queryReactions( + 'test-message', + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer( + (_) async => QueryReactionsResponse() + ..reactions = reactions.where((r) => r.type == 'love').toList() + ..next = null, + ); + // Pump once to trigger post-frame modal opening, then settle animation. + await tester.pump(); + await tester.pumpAndSettle(const Duration(seconds: 1)); + }, builder: () => _wrapWithMaterialApp( client: mockClient, brightness: brightness, diff --git a/packages/stream_chat_flutter_core/lib/src/stream_reaction_list_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_reaction_list_controller.dart new file mode 100644 index 0000000000..2e7dc0dda6 --- /dev/null +++ b/packages/stream_chat_flutter_core/lib/src/stream_reaction_list_controller.dart @@ -0,0 +1,182 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:collection/collection.dart'; +import 'package:stream_chat/stream_chat.dart' hide Success; +import 'package:stream_chat_flutter_core/src/paged_value_notifier.dart'; + +/// The default reaction list page limit to load. +const defaultReactionPagedLimit = 25; + +const _kDefaultBackendPaginationLimit = 30; + +/// {@template streamReactionListController} +/// A controller for managing and displaying a paginated list of reactions. +/// +/// The `StreamReactionListController` extends [PagedValueNotifier] to handle +/// paginated data for reactions. It provides functionality for querying +/// reactions and managing filters and sorting. +/// +/// This controller uses cursor-based pagination via the `queryReactions` API, +/// which supports filtering by reaction type, user ID, or creation date. +/// +/// This controller is typically used in conjunction with UI components +/// to display and interact with a list of reactions for a message. +/// {@endtemplate} +class StreamReactionListController extends PagedValueNotifier { + /// {@macro streamReactionListController} + StreamReactionListController({ + required this.client, + required this.messageId, + this.filter, + this.sort, + this.limit = defaultReactionPagedLimit, + }) : _activeFilter = filter, + _activeSort = sort, + super(const PagedValue.loading()); + + /// Creates a [StreamReactionListController] from the passed [value]. + StreamReactionListController.fromValue( + super.value, { + required this.client, + required this.messageId, + this.filter, + this.sort, + this.limit = defaultReactionPagedLimit, + }) : _activeFilter = filter, + _activeSort = sort; + + /// The Stream chat client used to query reactions. + final StreamChatClient client; + + /// The ID of the message whose reactions are being listed. + final String messageId; + + /// The query filters to use. + /// + /// Supported filter fields: `type`, `user_id`, `created_at`. + final Filter? filter; + Filter? _activeFilter; + + /// The sorting used for the reactions matching the filters. + /// + /// Sorting is based on field and direction. The only backend-supported sort + /// field is `created_at` (see [ReactionSortKey]). + /// + /// Direction can be ascending or descending. + final SortOrder? sort; + SortOrder? _activeSort; + + /// The limit to apply to the reaction list. + /// + /// The default is set to [defaultReactionPagedLimit]. + final int limit; + + /// Allows for the change of filters used for reaction queries. + /// + /// Use this if you need to support runtime filter changes, + /// such as switching between reaction type tabs. + /// + /// Note: This will not trigger a new query. Make sure to call + /// [doInitialLoad] or [refresh] after setting a new filter. + set filter(Filter? value) => _activeFilter = value; + + /// Allows for the change of the query sort used for reaction queries. + /// + /// Use this if you need to support runtime sort changes, + /// through custom sort UI. + /// + /// Note: This will not trigger a new query. Make sure to call + /// [doInitialLoad] or [refresh] after setting a new sort. + set sort(SortOrder? value) => _activeSort = value; + + @override + set value(PagedValue newValue) { + super.value = switch (_activeSort) { + null => newValue, + final reactionSort => newValue.maybeMap( + orElse: () => newValue, + (success) => success.copyWith( + items: success.items.sorted(reactionSort.compare), + ), + ), + }; + } + + @override + Future doInitialLoad() async { + final limit = min( + this.limit * defaultInitialPagedLimitMultiplier, + _kDefaultBackendPaginationLimit, + ); + try { + final response = await client.queryReactions( + messageId, + filter: _activeFilter, + sort: _activeSort, + pagination: PaginationParams(limit: limit), + ); + + final reactions = response.reactions; + final next = response.next; + final nextKey = next != null && next.isNotEmpty ? next : null; + value = PagedValue( + items: reactions, + nextPageKey: nextKey, + ); + } on StreamChatError catch (error) { + value = PagedValue.error(error); + } catch (error) { + final chatError = StreamChatError(error.toString()); + value = PagedValue.error(chatError); + } + } + + @override + Future loadMore(String? nextPageKey) async { + final previousValue = value.asSuccess; + + try { + final response = await client.queryReactions( + messageId, + filter: _activeFilter, + sort: _activeSort, + pagination: PaginationParams(limit: limit, next: nextPageKey), + ); + + final reactions = response.reactions; + final previousItems = previousValue.items; + final newItems = previousItems + reactions; + final next = response.next; + final nextKey = next != null && next.isNotEmpty ? next : null; + value = PagedValue( + items: newItems, + nextPageKey: nextKey, + ); + } on StreamChatError catch (error) { + value = previousValue.copyWith(error: error); + } catch (error) { + final chatError = StreamChatError(error.toString()); + value = previousValue.copyWith(error: chatError); + } + } + + @override + Future refresh({bool resetValue = true}) { + if (resetValue) { + _activeFilter = filter; + _activeSort = sort; + } + return super.refresh(resetValue: resetValue); + } + + /// Replaces the previously loaded reactions with [reactions]. + set reactions(List reactions) { + if (value.isSuccess) { + final currentValue = value.asSuccess; + value = currentValue.copyWith(items: reactions); + } else { + value = PagedValue(items: reactions); + } + } +} diff --git a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart index 92e1e92751..2bdd25a06c 100644 --- a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart +++ b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart @@ -23,6 +23,7 @@ export 'src/stream_message_reminder_list_event_handler.dart'; export 'src/stream_message_search_list_controller.dart'; export 'src/stream_poll_controller.dart'; export 'src/stream_poll_vote_list_controller.dart'; +export 'src/stream_reaction_list_controller.dart'; export 'src/stream_thread_list_controller.dart'; export 'src/stream_thread_list_event_handler.dart'; export 'src/stream_user_list_controller.dart'; diff --git a/packages/stream_chat_flutter_core/test/stream_reaction_list_controller_test.dart b/packages/stream_chat_flutter_core/test/stream_reaction_list_controller_test.dart new file mode 100644 index 0000000000..983d82b09b --- /dev/null +++ b/packages/stream_chat_flutter_core/test/stream_reaction_list_controller_test.dart @@ -0,0 +1,579 @@ +// ignore_for_file: avoid_redundant_argument_values + +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/stream_chat.dart' hide Success; +import 'package:stream_chat_flutter_core/src/paged_value_notifier.dart'; +import 'package:stream_chat_flutter_core/src/stream_reaction_list_controller.dart'; + +import 'mocks.dart'; + +Reaction generateReaction({ + String? messageId, + String? type, + String? userId, + DateTime? createdAt, +}) { + return Reaction( + messageId: messageId ?? 'message_123', + type: type ?? 'like', + userId: userId ?? 'user_123', + createdAt: createdAt ?? DateTime.now(), + ); +} + +List generateReactions({ + int count = 2, + String? messageId, + List? types, + List? userIds, + int? startId, +}) { + final now = DateTime.now(); + final baseId = startId ?? 1; + + return List.generate(count, (index) { + final type = types != null && index < types.length ? types[index] : 'like'; + final userId = userIds != null && index < userIds.length ? userIds[index] : 'user_${baseId + index}'; + + return generateReaction( + messageId: messageId ?? 'message_123', + type: type, + userId: userId, + createdAt: now.subtract(Duration(minutes: index)), + ); + }); +} + +void main() { + const messageId = 'message_123'; + + final client = MockClient(); + + setUpAll(() { + registerFallbackValue(const PaginationParams()); + registerFallbackValue(Filter.equal('type', 'like')); + }); + + setUp(() { + when(client.on).thenAnswer((_) => const Stream.empty()); + }); + + tearDown(() { + reset(client); + }); + + group('Initialization', () { + test('should start in loading state when created with client', () { + final controller = StreamReactionListController( + client: client, + messageId: messageId, + ); + expect(controller.value, isA()); + }); + + test('should preserve provided value when created with fromValue', () { + final reactions = generateReactions(); + final value = PagedValue(items: reactions); + final controller = StreamReactionListController.fromValue( + value, + client: client, + messageId: messageId, + ); + + expect(controller.value, same(value)); + expect(controller.value.asSuccess.items, equals(reactions)); + }); + }); + + group('Initial loading', () { + test('successfully loads reactions from API', () async { + final reactions = generateReactions(); + final response = QueryReactionsResponse() + ..reactions = reactions + ..next = null; + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); + + final controller = StreamReactionListController( + client: client, + messageId: messageId, + ); + + await controller.doInitialLoad(); + await pumpEventQueue(); + + verify( + () => client.queryReactions( + messageId, + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).called(1); + + expect(controller.value, isA>()); + expect(controller.value.asSuccess.items, equals(reactions)); + }); + + test('sets next page key when API returns next cursor', () async { + const nextCursor = 'next_cursor_token'; + final reactions = generateReactions(); + final response = QueryReactionsResponse() + ..reactions = reactions + ..next = nextCursor; + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); + + final controller = StreamReactionListController( + client: client, + messageId: messageId, + ); + + await controller.doInitialLoad(); + await pumpEventQueue(); + + expect(controller.value.asSuccess.nextPageKey, equals(nextCursor)); + }); + + test('sets null next page key when API returns empty next cursor', () async { + final reactions = generateReactions(); + final response = QueryReactionsResponse() + ..reactions = reactions + ..next = ''; + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); + + final controller = StreamReactionListController( + client: client, + messageId: messageId, + ); + + await controller.doInitialLoad(); + await pumpEventQueue(); + + expect(controller.value.asSuccess.nextPageKey, isNull); + }); + + test('handles StreamChatError by transitioning to error state', () async { + const chatError = StreamChatError('Network error'); + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenThrow(chatError); + + final controller = StreamReactionListController( + client: client, + messageId: messageId, + ); + + await controller.doInitialLoad(); + await pumpEventQueue(); + + expect(controller.value, isA()); + expect((controller.value as Error).error, equals(chatError)); + }); + + test('wraps generic exceptions in StreamChatError', () async { + final exception = Exception('API unavailable'); + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenThrow(exception); + + final controller = StreamReactionListController( + client: client, + messageId: messageId, + ); + + await controller.doInitialLoad(); + await pumpEventQueue(); + + expect(controller.value, isA()); + expect( + (controller.value as Error).error.message, + contains('API unavailable'), + ); + }); + }); + + group('Pagination', () { + test('loadMore appends new reactions to existing items', () async { + const nextKey = 'next_page_token'; + final existingReactions = generateReactions(); + final additionalReactions = generateReactions( + count: 1, + userIds: ['user_999'], + startId: 999, + ); + + final response = QueryReactionsResponse() + ..reactions = additionalReactions + ..next = null; + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); + + final controller = StreamReactionListController.fromValue( + PagedValue( + items: existingReactions, + nextPageKey: nextKey, + ), + client: client, + messageId: messageId, + ); + + await controller.loadMore(nextKey); + await pumpEventQueue(); + + final mergedReactions = [...existingReactions, ...additionalReactions]; + + expect( + controller.value.asSuccess.items.length, + equals(mergedReactions.length), + ); + expect(controller.value.asSuccess.nextPageKey, isNull); + }); + + test('loadMore passes next cursor to API', () async { + const nextKey = 'cursor_page_2'; + final existingReactions = generateReactions(); + + final response = QueryReactionsResponse() + ..reactions = [] + ..next = null; + + PaginationParams? capturedPagination; + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((invocation) async { + capturedPagination = invocation.namedArguments[const Symbol('pagination')] as PaginationParams?; + return response; + }); + + final controller = StreamReactionListController.fromValue( + PagedValue( + items: existingReactions, + nextPageKey: nextKey, + ), + client: client, + messageId: messageId, + ); + + await controller.loadMore(nextKey); + await pumpEventQueue(); + + expect(capturedPagination?.next, equals(nextKey)); + }); + + test('loadMore preserves existing items on StreamChatError', () async { + const nextKey = 'next_page_token'; + final existingReactions = generateReactions(); + const chatError = StreamChatError('Network error'); + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenThrow(chatError); + + final controller = StreamReactionListController.fromValue( + PagedValue( + items: existingReactions, + nextPageKey: nextKey, + ), + client: client, + messageId: messageId, + ); + + await controller.loadMore(nextKey); + await pumpEventQueue(); + + expect(controller.value.isSuccess, isTrue); + expect(controller.value.asSuccess.items, equals(existingReactions)); + expect(controller.value.asSuccess.error, equals(chatError)); + }); + + test('loadMore preserves existing items on generic error', () async { + const nextKey = 'next_page_token'; + final existingReactions = generateReactions(); + final exception = Exception('Network error'); + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenThrow(exception); + + final controller = StreamReactionListController.fromValue( + PagedValue( + items: existingReactions, + nextPageKey: nextKey, + ), + client: client, + messageId: messageId, + ); + + await controller.loadMore(nextKey); + await pumpEventQueue(); + + expect(controller.value.isSuccess, isTrue); + expect(controller.value.asSuccess.items, equals(existingReactions)); + expect(controller.value.asSuccess.error, isNotNull); + expect( + controller.value.asSuccess.error!.message, + contains('Network error'), + ); + }); + }); + + group('reactions setter', () { + test('replaces reactions when in success state', () { + final initial = generateReactions(count: 3); + final replacement = generateReactions(count: 1, userIds: ['user_new']); + + final controller = StreamReactionListController.fromValue( + PagedValue(items: initial), + client: client, + messageId: messageId, + ); + + expect(controller.value.isSuccess, isTrue); + expect(controller.value.asSuccess.items, equals(initial)); + + controller.reactions = replacement; + + expect(controller.value.asSuccess.items, equals(replacement)); + expect(controller.value.asSuccess.items.length, equals(1)); + }); + + test('creates new success value when not in success state', () { + final reactions = generateReactions(); + final controller = StreamReactionListController( + client: client, + messageId: messageId, + ); + + // Controller is in loading state + expect(controller.value.isNotSuccess, isTrue); + + controller.reactions = reactions; + + expect(controller.value.isSuccess, isTrue); + expect(controller.value.asSuccess.items, equals(reactions)); + }); + + test('preserves nextPageKey when replacing reactions', () { + const nextKey = 'next_cursor'; + final initial = generateReactions(); + final replacement = generateReactions(count: 1, userIds: ['user_new']); + + final controller = StreamReactionListController.fromValue( + PagedValue(items: initial, nextPageKey: nextKey), + client: client, + messageId: messageId, + ); + + expect(controller.value.isSuccess, isTrue); + expect(controller.value.asSuccess.items, equals(initial)); + expect(controller.value.asSuccess.nextPageKey, equals(nextKey)); + + controller.reactions = replacement; + + expect(controller.value.asSuccess.items, equals(replacement)); + expect(controller.value.asSuccess.nextPageKey, equals(nextKey)); + }); + }); + + group('Filtering and sorting', () { + test('refresh resets filter and sort to initial values', () async { + final reactions = generateReactions(); + final initialFilter = Filter.equal('type', 'like'); + final sort = [const SortOption.desc(ReactionSortKey.createdAt)]; + + final apiCalls = >[]; + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((invocation) async { + apiCalls.add({ + 'filter': invocation.namedArguments[const Symbol('filter')], + 'sort': invocation.namedArguments[const Symbol('sort')], + }); + return QueryReactionsResponse() + ..reactions = reactions + ..next = null; + }); + + final controller = StreamReactionListController( + client: client, + messageId: messageId, + filter: initialFilter, + sort: sort, + ); + + await controller.doInitialLoad(); + await pumpEventQueue(); + + // Change filter and sort at runtime + controller + ..filter = Filter.equal('type', 'love') + ..sort = [const SortOption.asc(ReactionSortKey.createdAt)]; + + await controller.refresh(); + await pumpEventQueue(); + + expect(apiCalls.length, equals(2)); + + final refreshCall = apiCalls.last; + expect(refreshCall['filter'], equals(initialFilter)); + expect(refreshCall['sort'], equals(sort)); + }); + + test('refresh with resetValue=false preserves current filter and sort', () async { + final reactions = generateReactions(); + final initialFilter = Filter.equal('type', 'like'); + final initialSort = [const SortOption.desc(ReactionSortKey.createdAt)]; + final newFilter = Filter.equal('type', 'love'); + final newSort = [const SortOption.asc(ReactionSortKey.createdAt)]; + + final apiCalls = >[]; + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((invocation) async { + apiCalls.add({ + 'filter': invocation.namedArguments[const Symbol('filter')], + 'sort': invocation.namedArguments[const Symbol('sort')], + }); + return QueryReactionsResponse() + ..reactions = reactions + ..next = null; + }); + + final controller = StreamReactionListController( + client: client, + messageId: messageId, + filter: initialFilter, + sort: initialSort, + ); + + await controller.doInitialLoad(); + await pumpEventQueue(); + + controller + ..filter = newFilter + ..sort = newSort; + + await controller.refresh(resetValue: false); + await pumpEventQueue(); + + expect(apiCalls.length, equals(2)); + + final refreshCall = apiCalls.last; + expect(refreshCall['filter'], equals(newFilter)); + expect(refreshCall['sort'], equals(newSort)); + }); + + test('value setter sorts items when sort is provided', () async { + final now = DateTime.now(); + final older = generateReaction(userId: 'user_1', createdAt: now.subtract(const Duration(hours: 1))); + final newer = generateReaction(userId: 'user_2', createdAt: now); + + final response = QueryReactionsResponse() + ..reactions = [older, newer] + ..next = null; + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); + + final controller = StreamReactionListController( + client: client, + messageId: messageId, + sort: [const SortOption.desc(ReactionSortKey.createdAt)], + ); + + await controller.doInitialLoad(); + await pumpEventQueue(); + + // desc order: newer first + expect(controller.value.asSuccess.items.first.userId, equals('user_2')); + expect(controller.value.asSuccess.items.last.userId, equals('user_1')); + }); + }); + + group('Disposal', () { + test('dispose completes without errors', () { + final controller = StreamReactionListController( + client: client, + messageId: messageId, + )..doInitialLoad(); + + expect(controller.dispose, returnsNormally); + }); + }); +} From c4ea35a29f7d6724390baf5bfb4e715259cbf9e4 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Thu, 12 Mar 2026 14:31:52 +0100 Subject: [PATCH 21/33] multiple message list improvements (#2530) * multiple message list improvements * update date divider * update scrolledUnderElevation of channellist * feat(ui): enhance file and media pickers with improved layout and styling * feat(ui): update poll creation button label and styling for improved clarity * feat(poll): return result from onPollCreated callback for better handling * feat(ui): improve message input with DM checkbox and refined attachment picker logic * feat(sample): implement swipe-to-reply functionality for messages with visual feedback * feat(ui): enhance message composer with improved title and subtitle rendering * feat(ui): refine button sizes and colors for improved UI consistency * feat(ui): refine button sizes and colors for improved UI consistency * feat(llc): standardize connect and receive timeout values using constants * fix analysis issues * fix unit test * chore: Update Goldens --------- Co-authored-by: Sahil Kumar Co-authored-by: renefloor <15101411+renefloor@users.noreply.github.com> --- melos.yaml | 2 +- .../stream_chat/lib/src/client/client.dart | 4 +- .../core/http/stream_http_client_options.dart | 10 +- .../attachment_upload_state_builder.dart | 32 +++-- .../lib/src/channel/channel_list_header.dart | 5 + .../message_composer_input_header.dart | 22 ++- .../lib/src/localization/translations.dart | 4 +- .../options/stream_file_picker.dart | 94 +++++++----- .../options/stream_gallery_picker.dart | 34 +++-- .../options/stream_image_picker.dart | 84 ++++++----- .../options/stream_poll_creator.dart | 52 ++++--- .../options/stream_video_picker.dart | 80 +++++++---- .../stream_attachment_picker.dart | 16 ++- .../message_input/stream_message_input.dart | 134 ++++++++---------- .../message_list_view/message_list_view.dart | 69 ++++----- .../message_list_view/thread_separator.dart | 31 ++-- .../unread_messages_separator.dart | 16 ++- .../lib/src/message_widget/bottom_row.dart | 15 +- .../lib/src/message_widget/message_card.dart | 49 ++++--- .../lib/src/message_widget/message_text.dart | 4 +- .../src/message_widget/message_widget.dart | 2 +- .../message_widget_content.dart | 4 +- .../sending_indicator_builder.dart | 24 +--- .../lib/src/message_widget/text_bubble.dart | 38 ++--- .../lib/src/message_widget/username.dart | 6 +- .../lib/src/misc/date_divider.dart | 10 +- .../lib/src/theme/stream_chat_theme.dart | 2 - packages/stream_chat_flutter/pubspec.yaml | 2 +- .../message_list_view/bottom_row_test.dart | 51 +++---- .../goldens/ci/deleted_message_dark.png | Bin 749 -> 699 bytes .../goldens/ci/deleted_message_light.png | Bin 742 -> 688 bytes .../src/message_widget/username_test.dart | 2 +- .../example/lib/add_new_lang.dart | 4 +- .../lib/src/stream_chat_localizations_en.dart | 4 +- sample_app/lib/pages/channel_page.dart | 62 +++++++- 35 files changed, 564 insertions(+), 404 deletions(-) diff --git a/melos.yaml b/melos.yaml index 27ae7fc461..a2ef7e6846 100644 --- a/melos.yaml +++ b/melos.yaml @@ -95,7 +95,7 @@ command: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 0d5268a506dbc045fcdb8ac901bb2fc70367a3af + ref: 213dfb64b1d0c22a668a4a0924503703ff9a33e9 path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index e216c2061d..4f24364bb3 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -86,8 +86,8 @@ class StreamChatClient { RetryPolicy? retryPolicy, String? baseURL, String? baseWsUrl, - Duration connectTimeout = const Duration(seconds: 6), - Duration receiveTimeout = const Duration(seconds: 6), + Duration connectTimeout = kDefaultConnectTimeout, + Duration receiveTimeout = kDefaultReceiveTimeout, StreamChatApi? chatApi, WebSocket? ws, AttachmentFileUploaderProvider attachmentFileUploaderProvider = StreamAttachmentFileUploader.new, diff --git a/packages/stream_chat/lib/src/core/http/stream_http_client_options.dart b/packages/stream_chat/lib/src/core/http/stream_http_client_options.dart index ad18c2544f..a98a7904c2 100644 --- a/packages/stream_chat/lib/src/core/http/stream_http_client_options.dart +++ b/packages/stream_chat/lib/src/core/http/stream_http_client_options.dart @@ -2,13 +2,19 @@ part of 'stream_http_client.dart'; const _defaultBaseURL = 'https://chat.stream-io-api.com'; +/// The default connect timeout for the api request +const kDefaultConnectTimeout = Duration(seconds: 30); + +/// The default receive timeout for the api request +const kDefaultReceiveTimeout = Duration(seconds: 30); + /// Client options to modify [StreamHttpClient] class StreamHttpClientOptions { /// Instantiates a new [StreamHttpClientOptions] const StreamHttpClientOptions({ String? baseUrl, - this.connectTimeout = const Duration(seconds: 30), - this.receiveTimeout = const Duration(seconds: 30), + this.connectTimeout = kDefaultConnectTimeout, + this.receiveTimeout = kDefaultReceiveTimeout, this.queryParameters = const {}, this.headers = const {}, }) : baseUrl = baseUrl ?? _defaultBaseURL; diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart index 5631314a20..09548679f7 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart @@ -88,20 +88,22 @@ class _IconButton extends StatelessWidget { @override Widget build(BuildContext context) { - return SizedBox( - height: 24, - width: 24, - child: RawMaterialButton( - elevation: 0, - highlightElevation: 0, - focusElevation: 0, - hoverElevation: 0, - onPressed: onPressed, - fillColor: StreamChatTheme.of(context).colorTheme.overlayDark, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), + return SizedBox.square( + dimension: 20, + child: IconTheme.merge( + data: const IconThemeData(size: 16), + child: RawMaterialButton( + elevation: 0, + highlightElevation: 0, + focusElevation: 0, + hoverElevation: 0, + onPressed: onPressed, + fillColor: StreamChatTheme.of(context).colorTheme.overlayDark, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: icon, ), - child: icon, ), ); } @@ -203,7 +205,6 @@ class _FailedState extends StatelessWidget { _IconButton( icon: Icon( context.streamIcons.arrowRotateClockwise, - size: 14, color: theme.colorTheme.barsBg, ), onPressed: () { @@ -244,8 +245,9 @@ class _SuccessState extends StatelessWidget { alignment: Alignment.topRight, child: CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.overlayDark, - maxRadius: 12, + maxRadius: 10, child: Icon( + size: 16, context.streamIcons.checkmark2, color: StreamChatTheme.of(context).colorTheme.barsBg, ), diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart b/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart index 446a15936d..bf7c50ca69 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart @@ -55,6 +55,7 @@ class StreamChannelListHeader extends StatelessWidget implements PreferredSizeWi this.actions, this.backgroundColor, this.elevation = 0, + this.scrolledUnderElevation = 0, }); /// Use this if you don't have a [StreamChatClient] in your widget tree. @@ -99,6 +100,9 @@ class StreamChannelListHeader extends StatelessWidget implements PreferredSizeWi /// The elevation for this [StreamChannelListHeader]. final double elevation; + /// The scrolled under elevation for this [StreamChannelListHeader]. + final double scrolledUnderElevation; + @override Size get preferredSize => const Size.fromHeight(kToolbarHeight); @@ -139,6 +143,7 @@ class StreamChannelListHeader extends StatelessWidget implements PreferredSizeWi ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark, elevation: elevation, + scrolledUnderElevation: scrolledUnderElevation, backgroundColor: backgroundColor ?? channelListHeaderThemeData.color, centerTitle: centerTitle, shape: LinearBorder( diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart index f09be9611f..4f4a5e97db 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart @@ -152,8 +152,8 @@ class _EditMessageInHeader extends StatelessWidget { @override Widget build(BuildContext context) { return MessageComposerReplyAttachment( - title: context.translations.editMessageLabel, - subtitle: message.text ?? '', + title: Text(context.translations.editMessageLabel), + subtitle: StreamMessagePreviewText(message: message), onRemovePressed: onRemovePressed, style: ReplyStyle.outgoing, ); @@ -171,17 +171,29 @@ class _QuotedMessageInHeader extends StatelessWidget { final VoidCallback onRemovePressed; final String? currentUserId; + ImageProvider? _imageProvider(Message message) { + final attachments = message.attachments; + if (attachments.isEmpty || attachments.length > 1) return null; + + final attachment = attachments.first; + final imageUrl = attachment.imageUrl ?? attachment.thumbUrl ?? attachment.assetUrl; + + if (imageUrl == null) return null; + return CachedNetworkImageProvider(imageUrl); + } + @override Widget build(BuildContext context) { final isIncoming = currentUserId != quotedMessage.user?.id; return - // TODO: show image if available + // TODO: improve attachment and add trailing to the component instead. // TODO: localize strings MessageComposerReplyAttachment( - title: isIncoming ? 'Reply to ${quotedMessage.user?.name}' : 'You', - subtitle: quotedMessage.text ?? '', + title: Text(isIncoming ? 'Reply to ${quotedMessage.user?.name}' : 'You'), + subtitle: StreamMessagePreviewText(message: quotedMessage), onRemovePressed: onRemovePressed, + image: _imageProvider(quotedMessage), style: isIncoming ? .incoming : .outgoing, ); } diff --git a/packages/stream_chat_flutter/lib/src/localization/translations.dart b/packages/stream_chat_flutter/lib/src/localization/translations.dart index c96c11dac5..d870b51837 100644 --- a/packages/stream_chat_flutter/lib/src/localization/translations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/translations.dart @@ -656,8 +656,8 @@ class DefaultTranslations implements Translations { @override String threadSeparatorText(int replyCount) { - if (replyCount == 1) return '1 Reply'; - return '$replyCount Replies'; + if (replyCount == 1) return '1 reply'; + return '$replyCount replies'; } @override diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_file_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_file_picker.dart index 140b3d1989..69a0a3b60a 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_file_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_file_picker.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:stream_chat_flutter/src/attachment/handler/stream_attachment_handler.dart'; import 'package:stream_chat_flutter/src/message_input/attachment_picker/stream_attachment_picker.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/utils.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart'; @@ -57,56 +56,79 @@ class StreamFilePicker extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); + final spacing = context.streamSpacing; + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; + + Future onPickFile() async { + final pickedFile = await runInPermissionRequestLock(() { + return StreamAttachmentHandler.instance.pickFile( + dialogTitle: dialogTitle, + initialDirectory: initialDirectory, + type: type, + allowedExtensions: allowedExtensions, + onFileLoading: onFileLoading, + compressionQuality: compressionQuality, + withData: withData, + withReadStream: withReadStream, + lockParentWindow: lockParentWindow, + ); + }); + + return onFilePicked.call(pickedFile); + } + return OptionDrawer( child: EndOfFrameCallbackWidget( - child: Icon( - context.streamIcons.fileBend, - size: 240, - color: theme.colorTheme.disabled, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + size: 32, + context.streamIcons.fileBend, + color: colorScheme.textTertiary, + ), + SizedBox(height: spacing.xs), + Text( + 'Select files to share', + style: textTheme.bodyDefault.copyWith( + color: colorScheme.textSecondary, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: spacing.md), + StreamButton( + type: .outline, + style: .secondary, + onTap: onPickFile, + label: 'Open files', + ), + ], ), - onEndOfFrame: (_) async { - final pickedFile = await runInPermissionRequestLock(() { - return StreamAttachmentHandler.instance.pickFile( - dialogTitle: dialogTitle, - initialDirectory: initialDirectory, - type: type, - allowedExtensions: allowedExtensions, - onFileLoading: onFileLoading, - compressionQuality: compressionQuality, - withData: withData, - withReadStream: withReadStream, - lockParentWindow: lockParentWindow, - ); - }); - - onFilePicked.call(pickedFile); - }, + onEndOfFrame: (_) => onPickFile(), errorBuilder: (context, error, stacktrace) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( + size: 32, context.streamIcons.fileBend, - size: 240, - color: theme.colorTheme.disabled, + color: colorScheme.textTertiary, ), + SizedBox(height: spacing.xs), Text( context.translations.enablePhotoAndVideoAccessMessage, - style: theme.textTheme.body.copyWith( - color: theme.colorTheme.textLowEmphasis, + style: textTheme.bodyDefault.copyWith( + color: colorScheme.textSecondary, ), textAlign: TextAlign.center, ), - const SizedBox(height: 8), - TextButton( - onPressed: PhotoManager.openSetting, - child: Text( - context.translations.allowGalleryAccessMessage, - style: theme.textTheme.bodyBold.copyWith( - color: theme.colorTheme.accentPrimary, - ), - ), + SizedBox(height: spacing.md), + StreamButton( + type: .outline, + style: .secondary, + onTap: PhotoManager.openSetting, + label: context.translations.allowGalleryAccessMessage, ), ], ); diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart index f81a8cd094..aab1050944 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart @@ -9,7 +9,6 @@ import 'package:stream_chat_flutter/src/message_input/attachment_picker/stream_a import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/scroll_view/photo_gallery/stream_photo_gallery.dart'; import 'package:stream_chat_flutter/src/scroll_view/photo_gallery/stream_photo_gallery_controller.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/utils.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart'; @@ -80,9 +79,9 @@ class _StreamGalleryPickerState extends State { builder: (context, snapshot) { if (!snapshot.hasData) return const Empty(); - final theme = StreamChatTheme.of(context); - final textTheme = theme.textTheme; - final colorTheme = theme.colorTheme; + final spacing = context.streamSpacing; + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; // Available on both Android and iOS. final isAuthorized = snapshot.data == PermissionState.authorized; @@ -92,10 +91,11 @@ class _StreamGalleryPickerState extends State { final isPermissionGranted = isAuthorized || isLimited; return OptionDrawer( + margin: .zero, actions: [ if (isLimited) IconButton( - color: colorTheme.accentPrimary, + color: colorScheme.accentPrimary, icon: const Icon(Icons.add_circle_outline_rounded), onPressed: () async { await PhotoManager.presentLimited(); @@ -110,26 +110,24 @@ class _StreamGalleryPickerState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( + size: 32, context.streamIcons.images1Alt, - size: 240, - color: colorTheme.disabled, + color: colorScheme.textTertiary, ), + SizedBox(height: spacing.xs), Text( context.translations.enablePhotoAndVideoAccessMessage, - style: textTheme.body.copyWith( - color: colorTheme.textLowEmphasis, + style: textTheme.bodyDefault.copyWith( + color: colorScheme.textSecondary, ), textAlign: TextAlign.center, ), - const SizedBox(height: 8), - TextButton( - onPressed: PhotoManager.openSetting, - child: Text( - context.translations.allowGalleryAccessMessage, - style: textTheme.bodyBold.copyWith( - color: colorTheme.accentPrimary, - ), - ), + SizedBox(height: spacing.md), + StreamButton( + type: .outline, + style: .secondary, + onTap: PhotoManager.openSetting, + label: context.translations.allowGalleryAccessMessage, ), ], ); diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_image_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_image_picker.dart index 0448a2c894..35968f2a3b 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_image_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_image_picker.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// Widget used to pick images from the device. class StreamImagePicker extends StatelessWidget { @@ -36,52 +37,71 @@ class StreamImagePicker extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); + final spacing = context.streamSpacing; + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; + + Future onPickImage() async { + final pickedImage = await runInPermissionRequestLock(() { + return StreamAttachmentHandler.instance.pickImage( + source: source, + maxWidth: maxWidth, + maxHeight: maxHeight, + imageQuality: imageQuality, + preferredCameraDevice: preferredCameraDevice, + ); + }); + + return onImagePicked.call(pickedImage); + } + return OptionDrawer( child: EndOfFrameCallbackWidget( - child: Icon( - context.streamIcons.camera1, - size: 240, - color: theme.colorTheme.disabled, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + size: 32, + context.streamIcons.camera1, + color: colorScheme.textTertiary, + ), + SizedBox(height: spacing.xs), + Text( + 'Take a photo and share', + style: textTheme.bodyDefault.copyWith(color: colorScheme.textSecondary), + textAlign: TextAlign.center, + ), + SizedBox(height: spacing.md), + StreamButton( + type: .outline, + style: .secondary, + onTap: onPickImage, + label: 'Open camera', + ), + ], ), - onEndOfFrame: (_) async { - final pickedImage = await runInPermissionRequestLock(() { - return StreamAttachmentHandler.instance.pickImage( - source: source, - maxWidth: maxWidth, - maxHeight: maxHeight, - imageQuality: imageQuality, - preferredCameraDevice: preferredCameraDevice, - ); - }); - - onImagePicked.call(pickedImage); - }, + onEndOfFrame: (_) => onPickImage(), errorBuilder: (context, error, stacktrace) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( + size: 32, context.streamIcons.camera1, - size: 240, - color: theme.colorTheme.disabled, + color: colorScheme.textTertiary, ), + SizedBox(height: spacing.xs), Text( context.translations.enablePhotoAndVideoAccessMessage, - style: theme.textTheme.body.copyWith( - color: theme.colorTheme.textLowEmphasis, - ), + style: textTheme.bodyDefault.copyWith(color: colorScheme.textSecondary), textAlign: TextAlign.center, ), - const SizedBox(height: 8), - TextButton( - onPressed: PhotoManager.openSetting, - child: Text( - context.translations.allowGalleryAccessMessage, - style: theme.textTheme.bodyBold.copyWith( - color: theme.colorTheme.accentPrimary, - ), - ), + SizedBox(height: spacing.md), + StreamButton( + type: .outline, + style: .secondary, + onTap: PhotoManager.openSetting, + label: context.translations.allowGalleryAccessMessage, ), ], ); diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_poll_creator.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_poll_creator.dart index 122ef662bc..4f1703e673 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_poll_creator.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_poll_creator.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// Widget used to create a poll. class StreamPollCreator extends StatelessWidget { @@ -22,7 +23,9 @@ class StreamPollCreator extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); + final spacing = context.streamSpacing; + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; Future _openCreatePollFlow() async { final result = await showStreamPollCreatorDialog( @@ -31,15 +34,33 @@ class StreamPollCreator extends StatelessWidget { config: config, ); - onPollCreated?.call(result); + return onPollCreated?.call(result); } return OptionDrawer( child: EndOfFrameCallbackWidget( - child: Icon( - context.streamIcons.chart5, - size: 180, - color: theme.colorTheme.disabled, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + size: 32, + context.streamIcons.chart5, + color: colorScheme.textTertiary, + ), + SizedBox(height: spacing.xs), + Text( + 'Create a poll and let everyone vote!', + style: textTheme.bodyDefault.copyWith(color: colorScheme.textSecondary), + textAlign: TextAlign.center, + ), + SizedBox(height: spacing.md), + StreamButton( + type: .outline, + style: .secondary, + onTap: _openCreatePollFlow, + label: context.translations.createPollLabel(), + ), + ], ), onEndOfFrame: (_) => _openCreatePollFlow(), errorBuilder: (context, error, stacktrace) { @@ -47,19 +68,16 @@ class StreamPollCreator extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( + size: 32, context.streamIcons.chart5, - size: 240, - color: theme.colorTheme.disabled, + color: colorScheme.textTertiary, ), - const SizedBox(height: 8), - TextButton( - onPressed: _openCreatePollFlow, - child: Text( - context.translations.createPollLabel(isNew: true), - style: theme.textTheme.bodyBold.copyWith( - color: theme.colorTheme.accentPrimary, - ), - ), + SizedBox(height: spacing.md), + StreamButton( + type: .outline, + style: .secondary, + onTap: _openCreatePollFlow, + label: context.translations.createPollLabel(isNew: true), ), ], ); diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_video_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_video_picker.dart index 59db3a1cae..ee67cadcce 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_video_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_video_picker.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// Widget used to capture video using the device camera. class StreamVideoPicker extends StatelessWidget { @@ -28,50 +29,71 @@ class StreamVideoPicker extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); + final spacing = context.streamSpacing; + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; + + Future onPickVideo() async { + final pickedVideo = await runInPermissionRequestLock(() { + return StreamAttachmentHandler.instance.pickVideo( + source: source, + preferredCameraDevice: preferredCameraDevice, + maxDuration: maxDuration, + ); + }); + + return onVideoPicked.call(pickedVideo); + } + return OptionDrawer( child: EndOfFrameCallbackWidget( - child: Icon( - context.streamIcons.video, - size: 240, - color: theme.colorTheme.disabled, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + size: 32, + context.streamIcons.video, + color: colorScheme.textTertiary, + ), + SizedBox(height: spacing.xs), + Text( + 'Take a video and share', + style: textTheme.bodyDefault.copyWith(color: colorScheme.textSecondary), + textAlign: TextAlign.center, + ), + SizedBox(height: spacing.md), + StreamButton( + type: .outline, + style: .secondary, + onTap: onPickVideo, + label: 'Open camera', + ), + ], ), - onEndOfFrame: (_) async { - final pickedVideo = await runInPermissionRequestLock(() { - return StreamAttachmentHandler.instance.pickVideo( - source: source, - preferredCameraDevice: preferredCameraDevice, - maxDuration: maxDuration, - ); - }); - - onVideoPicked.call(pickedVideo); - }, + onEndOfFrame: (_) => onPickVideo(), errorBuilder: (context, error, stacktrace) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( + size: 32, context.streamIcons.video, - size: 240, - color: theme.colorTheme.disabled, + color: colorScheme.textTertiary, ), + SizedBox(height: spacing.xs), Text( context.translations.enablePhotoAndVideoAccessMessage, - style: theme.textTheme.body.copyWith( - color: theme.colorTheme.textLowEmphasis, + style: textTheme.bodyDefault.copyWith( + color: colorScheme.textSecondary, ), textAlign: TextAlign.center, ), - const SizedBox(height: 8), - TextButton( - onPressed: PhotoManager.openSetting, - child: Text( - context.translations.allowGalleryAccessMessage, - style: theme.textTheme.bodyBold.copyWith( - color: theme.colorTheme.accentPrimary, - ), - ), + SizedBox(height: spacing.md), + StreamButton( + type: .outline, + style: .secondary, + onTap: PhotoManager.openSetting, + label: context.translations.allowGalleryAccessMessage, ), ], ); diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart index 1becd80611..aec6681760 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart @@ -283,7 +283,7 @@ class OptionDrawer extends StatelessWidget { required this.child, this.color, this.elevation = 2, - this.margin = EdgeInsets.zero, + this.margin, this.clipBehavior = Clip.hardEdge, this.shape = _kDefaultOptionDrawerShape, this.title, @@ -304,9 +304,7 @@ class OptionDrawer extends StatelessWidget { final double elevation; /// The margin of the options card. - /// - /// The default value is [EdgeInsets.zero]. - final EdgeInsetsGeometry margin; + final EdgeInsetsGeometry? margin; /// The clip behavior of the options card. /// @@ -343,6 +341,9 @@ class OptionDrawer extends StatelessWidget { trailing = const Empty(); } + final spacing = context.streamSpacing; + final effectiveMargin = margin ?? .symmetric(horizontal: spacing.md, vertical: spacing.xxxl); + return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -355,7 +356,12 @@ class OptionDrawer extends StatelessWidget { ], ), ), - Expanded(child: child), + Expanded( + child: Container( + margin: effectiveMargin, + child: child, + ), + ), ], ); } diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart index 96c744809a..efe4829af3 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart @@ -524,8 +524,8 @@ class StreamMessageInputState extends State with Restoration bool get _commandEnabled => _effectiveController.message.command != null; bool _actionsShrunk = false; - bool _isPickerVisible = false; + bool get _isPickerVisible => _pickerController != null; StreamAttachmentPickerController? _pickerController; bool _isSyncingControllers = false; @@ -651,10 +651,7 @@ class StreamMessageInputState extends State with Restoration void _focusNodeListener() { if (_effectiveFocusNode.hasFocus && _isPickerVisible) { - setState(() { - _isPickerVisible = false; - _closePicker(); - }); + _hidePicker(); } } @@ -702,12 +699,12 @@ class StreamMessageInputState extends State with Restoration elevation: elevation ?? 8, child: DecoratedBox( decoration: BoxDecoration( - color: _messageInputTheme.inputBackgroundColor, + color: context.streamColorScheme.backgroundElevation1, boxShadow: [if (shadow != null) shadow], ), child: SimpleSafeArea( enabled: !_isPickerVisible && (widget.enableSafeArea ?? _messageInputTheme.enableSafeArea ?? true), - minimum: EdgeInsets.only(bottom: _isPickerVisible ? 0 : spacing.md), + minimum: _isPickerVisible ? .zero : .only(bottom: spacing.md), child: Center(heightFactor: 1, child: messageInput), ), ), @@ -794,13 +791,16 @@ class StreamMessageInputState extends State with Restoration onSendPressed: sendMessage, audioRecorderController: _audioRecorderController, ), - if (_isPickerVisible && _pickerController != null) _buildInlineAttachmentPicker(context), - ], + _buildDmCheckbox(context), + _buildInlineAttachmentPicker(context), + ].nonNulls.toList(), ), ); } - Widget _buildInlineAttachmentPicker(BuildContext context) { + Widget? _buildInlineAttachmentPicker(BuildContext context) { + if (!_isPickerVisible) return null; + final allowedTypes = _getAllowedAttachmentPickerTypes(); final messageInputTheme = StreamMessageInputTheme.of(context); @@ -812,16 +812,13 @@ class StreamMessageInputState extends State with Restoration widget.useSystemAttachmentPicker || (messageInputTheme.useSystemAttachmentPicker ?? false) || isWebOrDesktop; final builder = switch (useSystemPicker) { - true => () => systemAttachmentPickerBuilder( - context: context, - controller: _pickerController!, - allowedTypes: allowedTypes, - pollConfig: widget.pollConfig, - optionsBuilder: widget.attachmentPickerOptionsBuilder, - onError: _onPickerError, - onPollCreated: _onPollCreated, - ), - false => () => tabbedAttachmentPickerBuilder( + true => systemAttachmentPickerBuilder, + false => tabbedAttachmentPickerBuilder, + }; + + return SizedBox( + height: 333, + child: builder.call( context: context, controller: _pickerController!, allowedTypes: allowedTypes, @@ -830,11 +827,6 @@ class StreamMessageInputState extends State with Restoration onError: _onPickerError, onPollCreated: _onPollCreated, ), - }; - - return SizedBox( - height: MediaQuery.of(context).size.height * 0.4, - child: builder(), ); } @@ -871,7 +863,9 @@ class StreamMessageInputState extends State with Restoration return DmCheckboxListTile( value: _effectiveController.showInChannel, - contentPadding: const EdgeInsets.symmetric(horizontal: 8), + contentPadding: EdgeInsets.symmetric( + horizontal: context.streamSpacing.md, + ), onChanged: (value) => _effectiveController.showInChannel = value, ); } @@ -1030,13 +1024,7 @@ class StreamMessageInputState extends State with Restoration } Future _onPollCreated(Poll poll) async { - // Close the picker after poll creation. - if (_isPickerVisible) { - setState(() { - _isPickerVisible = false; - _closePicker(); - }); - } + _hidePicker(); final channel = StreamChannel.maybeOf(context)?.channel; if (channel == null) return; @@ -1064,60 +1052,66 @@ class StreamMessageInputState extends State with Restoration } /// Toggles the inline attachment picker visibility. - void _onAttachmentButtonPressed() { + void _onAttachmentButtonPressed() => _isPickerVisible ? _hidePicker() : _showPicker(); + + void _showPicker() { + if (_isPickerVisible) return; + setState(() { - _isPickerVisible = !_isPickerVisible; - if (_isPickerVisible) { - _openPicker(); - } else { - _closePicker(); + _pickerController = StreamAttachmentPickerController( + initialAttachments: _effectiveController.attachments, + initialPoll: _effectiveController.poll, + ); + _pickerController!.addListener(_syncPickerToMessage); + _effectiveController.addListener(_syncMessageToPicker); + + if (_effectiveFocusNode.hasFocus) { + _effectiveFocusNode.unfocus(); } }); } - Future _openPicker() async { - if (_effectiveFocusNode.hasFocus) { - _effectiveFocusNode.unfocus(); - await Future.delayed(const Duration(milliseconds: 30)); - } - _pickerController = StreamAttachmentPickerController( - initialAttachments: _effectiveController.attachments, - initialPoll: _effectiveController.poll, - ); - _pickerController!.addListener(_onPickerControllerChanged); - _effectiveController.addListener(_onMessageInputControllerChanged); + void _hidePicker() { + if (!_isPickerVisible) return; + setState(_disposePickerResources); } - void _closePicker() { - _pickerController?.removeListener(_onPickerControllerChanged); - _effectiveController.removeListener(_onMessageInputControllerChanged); + void _disposePickerResources() { + _pickerController?.removeListener(_syncPickerToMessage); + _effectiveController.removeListener(_syncMessageToPicker); _pickerController?.dispose(); _pickerController = null; } - void _onPickerControllerChanged() { + /// Copies picker attachments into the message controller when the user + /// selects or removes items in the picker. + void _syncPickerToMessage() { if (_isSyncingControllers) return; _isSyncingControllers = true; + try { - final pickerAttachments = _pickerController?.value.attachments ?? []; - _effectiveController.attachments = pickerAttachments; + _effectiveController.attachments = _pickerController?.value.attachments ?? []; } finally { _isSyncingControllers = false; } } - void _onMessageInputControllerChanged() { + /// Removes picker selections that the user deleted from the composer preview. + void _syncMessageToPicker() { if (_isSyncingControllers) return; - _isSyncingControllers = true; - try { - final pickerController = _pickerController; - if (pickerController == null) return; - final messageAttachmentIds = _effectiveController.attachments.map((a) => a.id).toSet(); - final pickerAttachmentIds = pickerController.value.attachments.map((a) => a.id).toSet(); + final pickerController = _pickerController; + if (pickerController == null) return; + + final messageIds = _effectiveController.attachments.map((a) => a.id).toSet(); + final pickerIds = pickerController.value.attachments.map((a) => a.id).toSet(); - // Remove attachments from picker that were removed from the composer. - for (final id in pickerAttachmentIds.difference(messageAttachmentIds)) { + final removedIds = pickerIds.difference(messageIds); + if (removedIds.isEmpty) return; + + _isSyncingControllers = true; + try { + for (final id in removedIds) { pickerController.removeAttachmentById(id); } } finally { @@ -1558,13 +1552,7 @@ class StreamMessageInputState extends State with Restoration if (_effectiveController.isSlowModeActive) return; if (!widget.validator(_effectiveController.message)) return; - // Close the picker when sending a message. - if (_isPickerVisible) { - setState(() { - _isPickerVisible = false; - _closePicker(); - }); - } + _hidePicker(); final streamChannel = StreamChannel.maybeOf(context); if (streamChannel == null) return; @@ -1723,7 +1711,7 @@ class StreamMessageInputState extends State with Restoration @override void dispose() { - _closePicker(); + _disposePickerResources(); _effectiveController.removeListener(_onChangedDebounced); _controller?.dispose(); _effectiveFocusNode.removeListener(_focusNodeListener); diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart index 021ef9bfdd..c76785084f 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart @@ -15,6 +15,7 @@ import 'package:stream_chat_flutter/src/message_list_view/unread_messages_separa import 'package:stream_chat_flutter/src/message_widget/ephemeral_message.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// Spacing Types (These are properties of a message to help inform the decision /// of how much space / which widget to build after it) @@ -84,7 +85,7 @@ class StreamMessageListView extends StatefulWidget { const StreamMessageListView({ super.key, this.showScrollToBottom = true, - this.showUnreadCountOnScrollToBottom = false, + this.showUnreadCountOnScrollToBottom = true, this.scrollToBottomBuilder, this.showUnreadIndicator = true, this.unreadIndicatorBuilder, @@ -1019,7 +1020,6 @@ class _StreamMessageListViewState extends State { showDeleteMessage: false, showEditMessage: false, showMarkUnreadMessage: false, - padding: const EdgeInsets.all(8), showSendingIndicator: false, attachmentPadding: EdgeInsets.all( hasUrlAttachment @@ -1047,8 +1047,8 @@ class _StreamMessageListViewState extends State { bottomRight: isMyMessage ? Radius.zero : const Radius.circular(16), ), textPadding: EdgeInsets.symmetric( - vertical: 8, - horizontal: isOnlyEmoji ? 0 : 16.0, + vertical: context.streamSpacing.xs, + horizontal: isOnlyEmoji ? 0 : context.streamSpacing.sm, ), borderSide: borderSide, showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show, @@ -1099,45 +1099,30 @@ class _StreamMessageListViewState extends State { clipBehavior: Clip.none, children: [ FloatingActionButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), backgroundColor: _streamTheme.colorTheme.barsBg, onPressed: () async { return scrollToBottomDefaultTapAction(unreadCount); }, child: widget.reverse ? Icon( - context.streamIcons.chevronDown, + context.streamIcons.arrowDown, color: _streamTheme.colorTheme.textHighEmphasis, ) : Icon( - context.streamIcons.chevronTop, + context.streamIcons.arrowUp, color: _streamTheme.colorTheme.textHighEmphasis, ), ), if (showUnreadCount && widget.showUnreadCountOnScrollToBottom) Positioned( - left: 0, - right: 0, - top: -10, - child: Center( - child: Material( - borderRadius: BorderRadius.circular(8), - color: StreamChatTheme.of(context).colorTheme.accentPrimary, - child: Padding( - padding: const EdgeInsets.only( - left: 5, - right: 5, - top: 2, - bottom: 2, - ), - child: Text( - '${unreadCount > 99 ? '99+' : unreadCount}', - style: const TextStyle( - fontSize: 11, - color: Colors.white, - ), - ), - ), - ), + right: -4, + top: -4, + child: StreamBadgeNotification( + label: '${unreadCount > 99 ? '99+' : unreadCount}', + size: StreamBadgeNotificationSize.sm, ), ), ], @@ -1248,13 +1233,13 @@ class _StreamMessageListViewState extends State { final isOnlyEmoji = message.text?.isOnlyEmoji ?? false; final borderSide = isOnlyEmoji ? BorderSide.none : null; + final defaultBorderRadius = context.streamRadius.xxl; Widget messageWidget = StreamMessageWidget( message: message, reverse: isMyMessage, showReactions: !message.isDeleted && !message.state.isDeletingFailed, showReactionPicker: !message.isDeleted && !message.state.isDeletingFailed, - padding: const EdgeInsets.symmetric(horizontal: 8), showInChannelIndicator: showInChannelIndicator, showThreadReplyIndicator: showThreadReplyIndicator, showUsername: showUsername, @@ -1319,22 +1304,18 @@ class _StreamMessageListViewState extends State { : 2, ), borderRadiusGeometry: BorderRadius.only( - topLeft: const Radius.circular(16), - bottomLeft: isMyMessage - ? const Radius.circular(16) - : Radius.circular( - (hasTimeDiff || !isNextUserSame) && !(hasReplies || isThreadMessage) ? 0 : 16, - ), - topRight: const Radius.circular(16), - bottomRight: isMyMessage - ? Radius.circular( - (hasTimeDiff || !isNextUserSame) && !(hasReplies || isThreadMessage) ? 0 : 16, - ) - : const Radius.circular(16), + topLeft: defaultBorderRadius, + bottomLeft: isMyMessage || !((hasTimeDiff || !isNextUserSame) && !(hasReplies || isThreadMessage)) + ? defaultBorderRadius + : Radius.zero, + topRight: defaultBorderRadius, + bottomRight: isMyMessage && (hasTimeDiff || !isNextUserSame) && !(hasReplies || isThreadMessage) + ? Radius.zero + : defaultBorderRadius, ), textPadding: EdgeInsets.symmetric( - vertical: 8, - horizontal: isOnlyEmoji ? 0 : 16.0, + vertical: context.streamSpacing.xs, + horizontal: isOnlyEmoji ? 0 : context.streamSpacing.sm, ), messageTheme: isMyMessage ? _streamTheme.ownMessageTheme : _streamTheme.otherMessageTheme, onMessageTap: widget.onMessageTap, diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/thread_separator.dart b/packages/stream_chat_flutter/lib/src/message_list_view/thread_separator.dart index 186c4f75a6..39bf67e6b6 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/thread_separator.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/thread_separator.dart @@ -18,16 +18,27 @@ class ThreadSeparator extends StatelessWidget { @override Widget build(BuildContext context) { final replyCount = parentMessage!.replyCount!; - return DecoratedBox( - decoration: BoxDecoration( - gradient: StreamChatTheme.of(context).colorTheme.bgGradient, - ), - child: Padding( - padding: const EdgeInsets.all(8), - child: Text( - context.translations.threadSeparatorText(replyCount), - textAlign: TextAlign.center, - style: StreamChannelHeaderTheme.of(context).subtitleStyle, + final spacing = context.streamSpacing; + final colorScheme = context.streamColorScheme; + final textTheme = context.streamTextTheme; + + return Padding( + padding: EdgeInsets.symmetric(vertical: spacing.xs), + child: DecoratedBox( + decoration: BoxDecoration( + color: colorScheme.backgroundSurfaceSubtle, + border: Border( + top: BorderSide(color: colorScheme.borderSubtle), + bottom: BorderSide(color: colorScheme.borderSubtle), + ), + ), + child: Padding( + padding: EdgeInsets.all(spacing.xs), + child: Text( + context.translations.threadSeparatorText(replyCount), + textAlign: TextAlign.center, + style: textTheme.metadataEmphasis, + ), ), ), ); diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/unread_messages_separator.dart b/packages/stream_chat_flutter/lib/src/message_list_view/unread_messages_separator.dart index b32c36d8eb..d3dd550f38 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/unread_messages_separator.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/unread_messages_separator.dart @@ -15,18 +15,26 @@ class UnreadMessagesSeparator extends StatelessWidget { @override Widget build(BuildContext context) { + final spacing = context.streamSpacing; + final colorScheme = context.streamColorScheme; + final textTheme = context.streamTextTheme; + return Padding( - padding: const EdgeInsets.symmetric(vertical: 8), + padding: EdgeInsets.symmetric(vertical: spacing.xs), child: DecoratedBox( decoration: BoxDecoration( - gradient: StreamChatTheme.of(context).colorTheme.bgGradient, + color: colorScheme.backgroundSurfaceSubtle, + border: Border( + top: BorderSide(color: colorScheme.borderSubtle), + bottom: BorderSide(color: colorScheme.borderSubtle), + ), ), child: Padding( - padding: const EdgeInsets.all(8), + padding: EdgeInsets.all(spacing.xs), child: Text( context.translations.unreadMessagesSeparatorText(), textAlign: TextAlign.center, - style: StreamChannelHeaderTheme.of(context).subtitleStyle, + style: textTheme.metadataEmphasis, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart b/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart index 42d8fe05d0..16f92ce7b1 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart @@ -155,14 +155,17 @@ class BottomRow extends StatelessWidget { return deletedBottomRowBuilder(context, message); } } - + final textTheme = context.streamTextTheme; + final textStyle = textTheme.metadataDefault.copyWith( + color: context.streamColorScheme.textTertiary, + ); final threadParticipants = message.threadParticipants?.take(2); final showThreadParticipants = threadParticipants?.isNotEmpty == true; final replyCount = message.replyCount; final isEdited = message.messageTextUpdatedAt != null; var msg = context.translations.threadReplyLabel; - if (showThreadReplyIndicator && replyCount! > 1) { + if (showThreadReplyIndicator && replyCount! > 0) { msg = context.translations.threadReplyCountText(replyCount); } @@ -199,18 +202,18 @@ class BottomRow extends StatelessWidget { _ => Username( key: usernameKey, message: message, - messageTheme: messageTheme, + textStyle: textStyle, ), }, if (showEditedLabel && isEdited) Text( context.translations.editedMessageLabel, - style: messageTheme.createdAtStyle, + style: textStyle, ), if (showTimeStamp) StreamTimestamp( date: message.createdAt.toLocal(), - style: messageTheme.createdAtStyle, + style: textStyle, formatter: (context, date) { if (messageTheme.createdAtFormatter case final formatter?) { return formatter.call(context, date); @@ -234,7 +237,7 @@ class BottomRow extends StatelessWidget { bottom: context.textScaleFactor * ((messageTheme.repliesStyle?.fontSize ?? 1) / 2), ), child: CustomPaint( - size: const Size(16, 32) * context.textScaleFactor, + size: const Size(16, 40) * context.textScaleFactor, painter: ThreadReplyPainter( context: context, color: messageTheme.messageBorderColor, diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_card.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_card.dart index 07ad1a8c72..071feadbf4 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_card.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_card.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; /// {@template messageCard} /// The widget containing a quoted message. @@ -151,6 +152,10 @@ class _MessageCardState extends State { Widget build(BuildContext context) { final onQuotedMessageTap = widget.onQuotedMessageTap; final quotedMessageBuilder = widget.quotedMessageBuilder; + final coreTheme = context.streamMessageTheme.mergeWithDefaults(context); + final messageStyle = widget.reverse ? coreTheme.outgoing! : coreTheme.incoming!; + final currentUser = StreamChat.maybeOf(context)?.currentUser; + final colorScheme = context.streamColorScheme; return Container( constraints: const BoxConstraints().copyWith(maxWidth: widthLimit), @@ -159,7 +164,7 @@ class _MessageCardState extends State { start: !widget.reverse && widget.isFailedState ? 12.0 : 0.0, ), clipBehavior: Clip.hardEdge, - decoration: _buildDecoration(widget.messageTheme), + decoration: _buildDecoration(messageStyle, widget.messageTheme), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -169,16 +174,26 @@ class _MessageCardState extends State { onTap: !widget.message.quotedMessage!.isDeleted && onQuotedMessageTap != null ? () => onQuotedMessageTap(widget.message.quotedMessageId) : null, - child: - quotedMessageBuilder?.call( - context, - widget.message.quotedMessage!, - ) ?? - QuotedMessage( - message: widget.message, - textBuilder: widget.textBuilder, - hasNonUrlAttachments: widget.hasNonUrlAttachments, + child: core.StreamMessageTheme( + data: core.StreamMessageThemeData( + incoming: core.StreamMessageStyle( + backgroundColor: colorScheme.backgroundSurfaceStrong, ), + outgoing: core.StreamMessageStyle( + backgroundColor: colorScheme.brand.shade150, + ), + ), + child: + quotedMessageBuilder?.call( + context, + widget.message.quotedMessage!, + ) ?? + core.MessageComposerReplyAttachment( + title: Text(widget.message.quotedMessage!.user?.name ?? ''), + subtitle: StreamMessagePreviewText(message: widget.message.quotedMessage!), + style: currentUser?.id == widget.message.quotedMessage!.user?.id ? .outgoing : .incoming, + ), + ), ), ParseAttachments( key: attachmentsKey, @@ -193,7 +208,7 @@ class _MessageCardState extends State { attachmentActionsModalBuilder: widget.attachmentActionsModalBuilder, ), TextBubble( - messageTheme: widget.messageTheme, + messageStyle: messageStyle, message: widget.message, textPadding: widget.textPadding, textBuilder: widget.textBuilder, @@ -208,9 +223,9 @@ class _MessageCardState extends State { ); } - ShapeDecoration _buildDecoration(StreamMessageThemeData theme) { + ShapeDecoration _buildDecoration(core.StreamMessageStyle messageStyle, StreamMessageThemeData theme) { final gradient = _getBackgroundGradient(theme); - final color = gradient == null ? _getBackgroundColor(theme) : null; + final color = gradient == null ? _getBackgroundColor(messageStyle) : null; final borderColor = theme.messageBorderColor ?? Colors.transparent; final borderRadius = widget.borderRadiusGeometry ?? BorderRadius.zero; @@ -231,20 +246,20 @@ class _MessageCardState extends State { ); } - Color? _getBackgroundColor(StreamMessageThemeData theme) { + Color? _getBackgroundColor(core.StreamMessageStyle theme) { if (widget.hasQuotedMessage) { - return theme.messageBackgroundColor; + return theme.backgroundColor; } final containsOnlyUrlAttachment = widget.hasUrlAttachments && !widget.hasNonUrlAttachments; if (containsOnlyUrlAttachment) { - return theme.urlAttachmentBackgroundColor; + return theme.backgroundAttachmentColor; } if (widget.isOnlyEmoji) return null; - return theme.messageBackgroundColor; + return theme.backgroundColor; } Gradient? _getBackgroundGradient(StreamMessageThemeData theme) { diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_text.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_text.dart index 789e2ed7e2..60975daf1d 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_text.dart @@ -10,7 +10,7 @@ class StreamMessageText extends StatelessWidget { const StreamMessageText({ super.key, required this.message, - required this.messageTheme, + this.messageTheme, this.onMentionTap, this.onLinkTap, }); @@ -25,7 +25,7 @@ class StreamMessageText extends StatelessWidget { final void Function(String)? onLinkTap; /// [StreamMessageThemeData] whose text theme is to be applied - final StreamMessageThemeData messageTheme; + final StreamMessageThemeData? messageTheme; @override Widget build(BuildContext context) { diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart index 78978ed681..889271d245 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart @@ -699,7 +699,7 @@ class _StreamMessageWidgetState extends State false => AlignmentDirectional.centerStart, }, child: Padding( - padding: widget.padding ?? const EdgeInsets.all(8), + padding: widget.padding ?? const EdgeInsets.symmetric(horizontal: 16), child: MessageWidgetContent( streamChatTheme: theme, showUsername: showUsername, diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart index 7f9a1a93ff..1079cedd83 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart @@ -231,6 +231,8 @@ class MessageWidgetContent extends StatelessWidget { @override Widget build(BuildContext context) { + final hasThreadParticipants = message.threadParticipants?.isNotEmpty == true; + return Column( crossAxisAlignment: reverse ? CrossAxisAlignment.end : CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, @@ -321,7 +323,7 @@ class MessageWidgetContent extends StatelessWidget { ], if (showBottomRow) SizedBox( - height: context.textScaleFactor * 18.0, + height: context.textScaleFactor * (hasThreadParticipants ? 24.0 : 18.0), ), ], ), diff --git a/packages/stream_chat_flutter/lib/src/message_widget/sending_indicator_builder.dart b/packages/stream_chat_flutter/lib/src/message_widget/sending_indicator_builder.dart index cb42429007..919f216c45 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/sending_indicator_builder.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/sending_indicator_builder.dart @@ -40,7 +40,6 @@ class SendingIndicatorBuilder extends StatelessWidget { Widget build(BuildContext context) { final style = messageTheme.createdAtStyle; final channel = this.channel ?? StreamChannel.of(context).channel; - final memberCount = channel.memberCount ?? 0; if (hasNonUrlAttachments && message.state.isOutgoing) { final totalAttachments = message.attachments.length; @@ -69,31 +68,12 @@ class SendingIndicatorBuilder extends StatelessWidget { final deliveriesList = data.deliveriesOf(message: message); final isMessageDelivered = deliveriesList.isNotEmpty; - Widget child = StreamSendingIndicator( + return StreamSendingIndicator( message: message, isMessageRead: isMessageRead, isMessageDelivered: isMessageDelivered, - size: style?.fontSize, + size: 16, ); - - if (isMessageRead) { - child = Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (memberCount > 2) - Text( - readList.length.toString(), - style: style?.copyWith( - color: streamChatTheme.colorTheme.accentPrimary, - ), - ), - const SizedBox(width: 2), - child, - ], - ); - } - - return child; }, ); } diff --git a/packages/stream_chat_flutter/lib/src/message_widget/text_bubble.dart b/packages/stream_chat_flutter/lib/src/message_widget/text_bubble.dart index 7874319ab7..4f3ad434ad 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/text_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/text_bubble.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; /// {@template textBubble} /// The bubble around a [StreamMessageText]. @@ -14,7 +15,7 @@ class TextBubble extends StatelessWidget { required this.message, required this.isOnlyEmoji, required this.textPadding, - required this.messageTheme, + required this.messageStyle, required this.hasUrlAttachments, required this.hasQuotedMessage, this.textBuilder, @@ -40,8 +41,8 @@ class TextBubble extends StatelessWidget { /// {@macro onMentionTap} final void Function(User)? onMentionTap; - /// {@macro messageTheme} - final StreamMessageThemeData messageTheme; + /// TODO: merge with messageTheme + final core.StreamMessageStyle messageStyle; /// {@macro hasUrlAttachments} final bool hasUrlAttachments; @@ -52,22 +53,21 @@ class TextBubble extends StatelessWidget { @override Widget build(BuildContext context) { if (message.text?.trim().isEmpty ?? true) return const Empty(); - return Padding( - padding: isOnlyEmoji ? EdgeInsets.zero : textPadding, - child: textBuilder != null - ? textBuilder!(context, message) - : StreamMessageText( - onLinkTap: onLinkTap, - message: message, - onMentionTap: onMentionTap, - messageTheme: isOnlyEmoji - ? messageTheme.copyWith( - messageTextStyle: messageTheme.messageTextStyle!.copyWith( - fontSize: 42, - ), - ) - : messageTheme, - ), + return DefaultTextStyle( + style: context.streamTextTheme.bodyDefault.copyWith( + color: messageStyle.textColor, + fontSize: isOnlyEmoji ? 42 : null, + ), + child: Padding( + padding: isOnlyEmoji ? EdgeInsets.zero : textPadding, + child: textBuilder != null + ? textBuilder!(context, message) + : StreamMessageText( + onLinkTap: onLinkTap, + message: message, + onMentionTap: onMentionTap, + ), + ), ); } } diff --git a/packages/stream_chat_flutter/lib/src/message_widget/username.dart b/packages/stream_chat_flutter/lib/src/message_widget/username.dart index fad0ff6fb1..32f412ed65 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/username.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/username.dart @@ -9,14 +9,14 @@ class Username extends StatelessWidget { const Username({ super.key, required this.message, - required this.messageTheme, + required this.textStyle, }); /// {@macro message} final Message message; /// {@macro messageTheme} - final StreamMessageThemeData messageTheme; + final TextStyle textStyle; @override Widget build(BuildContext context) { @@ -24,7 +24,7 @@ class Username extends StatelessWidget { message.user?.name ?? '', maxLines: 1, key: key, - style: messageTheme.messageAuthorStyle, + style: textStyle, overflow: TextOverflow.ellipsis, ); } diff --git a/packages/stream_chat_flutter/lib/src/misc/date_divider.dart b/packages/stream_chat_flutter/lib/src/misc/date_divider.dart index b927fca54f..7726f612c8 100644 --- a/packages/stream_chat_flutter/lib/src/misc/date_divider.dart +++ b/packages/stream_chat_flutter/lib/src/misc/date_divider.dart @@ -26,19 +26,19 @@ class StreamDateDivider extends StatelessWidget { @override Widget build(BuildContext context) { - final chatThemeData = StreamChatTheme.of(context); + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; + return Center( child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 1), decoration: BoxDecoration( - color: chatThemeData.colorTheme.overlayDark, + color: colorScheme.backgroundSurfaceSubtle, borderRadius: BorderRadius.circular(8), ), child: StreamTimestamp( date: dateTime.toLocal(), - style: chatThemeData.textTheme.footnote.copyWith( - color: chatThemeData.colorTheme.barsBg, - ), + style: textTheme.metadataEmphasis.copyWith(color: colorScheme.textSecondary), formatter: (context, date) { if (formatter case final formatter?) { final timestamp = formatter.call(context, date); diff --git a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart index 25f6016190..5cd19d8a0f 100644 --- a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart @@ -210,7 +210,6 @@ class StreamChatThemeData { createdAtStyle: textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), repliesStyle: textTheme.footnoteBold.copyWith(color: accentColor), messageBackgroundColor: colorTheme.inputBg, - messageBorderColor: colorTheme.borders, reactionsBackgroundColor: colorTheme.barsBg, reactionsBorderColor: colorTheme.borders, reactionsMaskColor: colorTheme.appBg, @@ -243,7 +242,6 @@ class StreamChatThemeData { repliesStyle: textTheme.footnoteBold.copyWith(color: accentColor), messageLinksStyle: TextStyle(color: accentColor), messageBackgroundColor: colorTheme.barsBg, - messageBorderColor: colorTheme.borders, avatarTheme: StreamAvatarThemeData( borderRadius: BorderRadius.circular(20), constraints: const BoxConstraints.tightFor( diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index b0e39d39d2..ab86210cd0 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 0d5268a506dbc045fcdb8ac901bb2fc70367a3af + ref: 213dfb64b1d0c22a668a4a0924503703ff9a33e9 path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 diff --git a/packages/stream_chat_flutter/test/src/message_list_view/bottom_row_test.dart b/packages/stream_chat_flutter/test/src/message_list_view/bottom_row_test.dart index 960411932f..93cd4b110f 100644 --- a/packages/stream_chat_flutter/test/src/message_list_view/bottom_row_test.dart +++ b/packages/stream_chat_flutter/test/src/message_list_view/bottom_row_test.dart @@ -30,31 +30,34 @@ void main() { final onThreadTap = MockValueChanged(); await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: Center( - child: StreamChannel( - channel: channel, - child: BottomRow( - message: Message( - parentId: 'parentId', + StreamChatTheme( + data: theme, + child: MaterialApp( + home: Scaffold( + body: Center( + child: StreamChannel( + channel: channel, + child: BottomRow( + message: Message( + parentId: 'parentId', + ), + isDeleted: false, + showThreadReplyIndicator: false, + showUsername: false, + showInChannel: true, + showTimeStamp: false, + showEditedLabel: false, + reverse: false, + showSendingIndicator: false, + hasUrlAttachments: false, + isGiphy: false, + isOnlyEmoji: false, + messageTheme: theme.otherMessageTheme, + streamChatTheme: theme, + hasNonUrlAttachments: false, + streamChat: StreamChatState(), + onThreadTap: onThreadTap, ), - isDeleted: false, - showThreadReplyIndicator: false, - showUsername: false, - showInChannel: true, - showTimeStamp: false, - showEditedLabel: false, - reverse: false, - showSendingIndicator: false, - hasUrlAttachments: false, - isGiphy: false, - isOnlyEmoji: false, - messageTheme: theme.otherMessageTheme, - streamChatTheme: theme, - hasNonUrlAttachments: false, - streamChat: StreamChatState(), - onThreadTap: onThreadTap, ), ), ), diff --git a/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_dark.png b/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_dark.png index e1af3e4ee3f3044adae8e260ee8a5708a44fec61..b69e980bc0ea7d35b3a680c6bebe2a935cd9005b 100644 GIT binary patch literal 699 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yu@pObhHwBu4M$1`kk42ghy94_vEmvTrcNin!rB4S=+#9WRv?QG_vpB)C;b1omK zKKYcDxp+o#oY@0+^VQ-F*=hnDOpOY|gfIx>wVS_x+xPzSp^tyMzw_IETjIFt=2#5h zoxbPz@6hARpI?dIc=+~f>Q}D)&zc+rI9M795Ef@F^|jf4i+At+)6E+0r69n<zopr07+t&DgXcg literal 749 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yu@pObhHwBu4M$1`kk42g9&>Uz*>L3X|KjXU-lr&z1RBJa1}d zV~^pnZ{N(~;{TtEX_S9gVbi+h$J=x7zjsgDjp4gv z_ZL zKzGjO#_wv=wf?nRMPC1E&`@4GdoP2GaifC(2g@)aS}sR6Pk&l^=kH(hY@7HKPuG9m zX#3kP_1hMQV-m@mZ}!OF-aJ#g;K{t{(^r0u-KfC9(&PXm4qZ9FXMXAD=VjlxY7Tt8 z`u?W&A9HnL0;XkhX)M1noBrw7JL+##erx}-XHQS}=lPfK9W81?3EGyG=l{L={(EC} v&8(eFOEUjEKF}^W+N2=B!PGchh!1Q9J2cGdm#$3#CR7GbS3j3^P6 { ), ); - return Container( + final child = Container( color: reminder != null ? colorTheme.accentPrimary.withOpacity(.1) : null, child: Column( mainAxisSize: MainAxisSize.min, @@ -337,6 +340,63 @@ class _ChannelPageState extends State { ], ), ); + + // We do not support quoting deleted messages. + if (message.isDeleted || message.state.isFailed) return child; + + // The threshold after which the message is considered swiped. + const threshold = 0.2; + + final isMyMessage = details.isMyMessage; + // The direction in which the message can be swiped. + final swipeDirection = details.isMyMessage ? SwipeDirection.endToStart : SwipeDirection.startToEnd; + + return Swipeable( + key: ValueKey(details.message.id), + direction: swipeDirection, + swipeThreshold: threshold, + onSwiped: (_) => _reply(details.message), + backgroundBuilder: (context, details) { + // The alignment of the swipe action. + final alignment = isMyMessage ? AlignmentDirectional.centerEnd : AlignmentDirectional.centerStart; + + // The progress of the swipe action. + final progress = math.min(details.progress, threshold) / threshold; + + // The offset for the reply icon. + var offset = Offset.lerp(const Offset(-24, 0), const Offset(12, 0), progress)!; + + // If the message is mine, we need to flip the offset. + if (isMyMessage) offset = Offset(-offset.dx, -offset.dy); + + return Align( + alignment: alignment, + child: Transform.translate( + offset: offset, + child: Opacity( + opacity: progress, + child: SizedBox.square( + dimension: 30, + child: CustomPaint( + painter: AnimatedCircleBorderPainter( + progress: progress, + color: context.streamColorScheme.borderDefault, + ), + child: Center( + child: Icon( + context.streamIcons.arrowShareLeft, + size: lerpDouble(0, 18, progress), + color: context.streamColorScheme.accentPrimary, + ), + ), + ), + ), + ), + ), + ); + }, + child: child, + ); } Future _editReminder( From 82627724294e40317101794c03e5d0474d154857 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Fri, 13 Mar 2026 15:49:23 +0100 Subject: [PATCH 22/33] feat(ui): composer also send to channel (#2537) * Improve layout for 'send as dm' * Remove unused imports * remove use of default color * Changed hideSendAsDm in canAlsoSendToChannelFromThread * fix typo --- melos.yaml | 2 +- .../stream_chat_message_composer.dart | 32 ++++++++++++++- .../lib/src/localization/translations.dart | 2 +- .../message_input/dm_checkbox_list_tile.dart | 41 +++++-------------- .../message_input/stream_message_input.dart | 27 +++++------- packages/stream_chat_flutter/pubspec.yaml | 2 +- .../src/message_input/message_input_test.dart | 2 +- .../example/lib/add_new_lang.dart | 2 +- .../lib/src/stream_chat_localizations_en.dart | 2 +- 9 files changed, 58 insertions(+), 54 deletions(-) diff --git a/melos.yaml b/melos.yaml index a2ef7e6846..70727ccd9b 100644 --- a/melos.yaml +++ b/melos.yaml @@ -95,7 +95,7 @@ command: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 213dfb64b1d0c22a668a4a0924503703ff9a33e9 + ref: 2959ed4323d189c1c43930964726f8d219e5dd94 path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart index 86f57388c0..00eb5cb53c 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart @@ -7,6 +7,7 @@ import 'package:stream_chat_flutter/src/components/message_composer/message_comp import 'package:stream_chat_flutter/src/components/message_composer/message_composer_recording_locked.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_recording_ongoing.dart'; import 'package:stream_chat_flutter/src/components/message_composer/message_composer_trailing.dart'; +import 'package:stream_chat_flutter/src/message_input/dm_checkbox_list_tile.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart' as core; @@ -33,6 +34,7 @@ class StreamChatMessageComposer extends StatefulWidget { StreamAudioRecorderController? audioRecorderController, bool sendVoiceRecordingAutomatically = false, AudioRecorderFeedback feedback = const AudioRecorderFeedback(), + bool canAlsoSendToChannel = false, }) : props = MessageComposerProps( controller: controller, isFloating: false, @@ -46,6 +48,7 @@ class StreamChatMessageComposer extends StatefulWidget { audioRecorderController: audioRecorderController, sendVoiceRecordingAutomatically: sendVoiceRecordingAutomatically, feedback: feedback, + canAlsoSendToChannel: canAlsoSendToChannel, ); /// The controller for the message composer. @@ -176,6 +179,7 @@ class MessageComposerProps { this.audioRecorderController, this.sendVoiceRecordingAutomatically = false, this.feedback = const AudioRecorderFeedback(), + this.canAlsoSendToChannel = false, }); /// The controller for the message composer. @@ -216,6 +220,10 @@ class MessageComposerProps { /// The feedback for the audio recorder. final AudioRecorderFeedback feedback; + + /// Whether the user can also send the message as a direct message. + /// Usually used in threads. + final bool canAlsoSendToChannel; } extension on StreamAudioRecorderController { @@ -290,7 +298,29 @@ class DefaultStreamChatMessageComposer extends StatelessWidget { inputLeading: StreamMessageComposerInputLeading( props: componentProps, ), - inputBody: body, + inputBody: + body ?? + Column( + mainAxisSize: MainAxisSize.min, + children: [ + core.StreamMessageComposerInputField( + controller: inputController.textFieldController, + placeholder: props.placeholder, + focusNode: props.focusNode, + ), + if (props.canAlsoSendToChannel) + DmCheckboxListTile( + value: props.controller?.showInChannel ?? false, + // height of list tile is 34px, height of checkbox is 16px, so we need to subtract 8px to make the spacing correct. + contentPadding: EdgeInsets.only( + right: context.streamSpacing.md, + left: context.streamSpacing.md, + bottom: context.streamSpacing.md - 8, + ), + onChanged: (value) => props.controller?.showInChannel = value, + ), + ], + ), ); } diff --git a/packages/stream_chat_flutter/lib/src/localization/translations.dart b/packages/stream_chat_flutter/lib/src/localization/translations.dart index d870b51837..71eb3844f6 100644 --- a/packages/stream_chat_flutter/lib/src/localization/translations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/translations.dart @@ -670,7 +670,7 @@ class DefaultTranslations implements Translations { String get reconnectingLabel => 'Reconnecting...'; @override - String get alsoSendAsDirectMessageLabel => 'Also send as direct message'; + String get alsoSendAsDirectMessageLabel => 'Also send in Channel'; @override String get addACommentOrSendLabel => 'Add a comment or send'; diff --git a/packages/stream_chat_flutter/lib/src/message_input/dm_checkbox_list_tile.dart b/packages/stream_chat_flutter/lib/src/message_input/dm_checkbox_list_tile.dart index 6f3c41d08d..7307c66ee8 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/dm_checkbox_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/dm_checkbox_list_tile.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; -import 'package:stream_chat_flutter/src/utils/extensions.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template dmCheckboxListTile} /// A widget that represents a checkbox list tile for direct message input. @@ -25,9 +25,7 @@ class DmCheckboxListTile extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); - final textTheme = theme.textTheme; - final colorTheme = theme.colorTheme; + final textTheme = context.streamTextTheme; const visualDensity = VisualDensity( vertical: VisualDensity.minimumDensity, @@ -35,27 +33,13 @@ class DmCheckboxListTile extends StatelessWidget { ); final checkbox = ExcludeFocus( - child: CheckboxTheme( - data: CheckboxThemeData( - overlayColor: WidgetStatePropertyAll( - colorTheme.accentPrimary.withAlpha(kRadialReactionAlpha), - ), - ), - child: Checkbox( - value: value, - visualDensity: visualDensity, - activeColor: colorTheme.accentPrimary, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - side: BorderSide(width: 2, color: colorTheme.textLowEmphasis), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(3)), - onChanged: switch (onChanged) { - final onChanged? => (value) { - if (value == null) return; - return onChanged.call(value); - }, - _ => null, - }, - ), + child: StreamCheckbox( + value: value, + size: StreamCheckboxSize.sm, + onChanged: switch (onChanged) { + final onChanged? => onChanged.call, + _ => null, + }, ), ); @@ -70,10 +54,7 @@ class DmCheckboxListTile extends StatelessWidget { contentPadding: contentPadding, title: Text( context.translations.alsoSendAsDirectMessageLabel, - style: textTheme.footnote.copyWith( - // ignore: deprecated_member_use - color: colorTheme.textHighEmphasis.withOpacity(0.5), - ), + style: textTheme.metadataDefault, ), ), ); diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart index efe4829af3..70e8d02101 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart @@ -11,7 +11,6 @@ import 'package:flutter_portal/flutter_portal.dart'; import 'package:stream_chat_flutter/platform_widget_builder/src/platform_widget_builder.dart'; import 'package:stream_chat_flutter/src/message_input/attachment_button.dart'; import 'package:stream_chat_flutter/src/message_input/command_button.dart'; -import 'package:stream_chat_flutter/src/message_input/dm_checkbox_list_tile.dart'; import 'package:stream_chat_flutter/src/message_input/quoting_message_top_area.dart'; import 'package:stream_chat_flutter/src/message_input/stream_message_input_icon_button.dart'; import 'package:stream_chat_flutter/src/message_input/tld.dart'; @@ -127,7 +126,7 @@ class StreamMessageInput extends StatefulWidget { this.focusNode, this.sendButtonLocation = SendButtonLocation.outside, this.autofocus = false, - this.hideSendAsDm = false, + this.canAlsoSendToChannelFromThread = true, this.enableVoiceRecording = false, this.sendVoiceRecordingAutomatically = false, this.voiceRecordingFeedback = const AudioRecorderFeedback(), @@ -219,8 +218,10 @@ class StreamMessageInput extends StatefulWidget { /// Use this property to hide/show the commands button. final bool showCommandsButton; - /// Hide send as dm checkbox. - final bool hideSendAsDm; + /// Show the checkbox to send the message as a direct message to the channel. + /// + /// Defaults to true. + final bool canAlsoSendToChannelFromThread; /// If true the voice recording button will be displayed. /// @@ -789,9 +790,9 @@ class StreamMessageInputState extends State with Restoration placeholder: _getHint(context) ?? '', focusNode: focusNode, onSendPressed: sendMessage, - audioRecorderController: _audioRecorderController, + canAlsoSendToChannel: _shouldShowSendToChannelCheckbox(), + audioRecorderController: widget.enableVoiceRecording ? _audioRecorderController : null, ), - _buildDmCheckbox(context), _buildInlineAttachmentPicker(context), ].nonNulls.toList(), ), @@ -855,19 +856,11 @@ class StreamMessageInputState extends State with Restoration return null; } - Widget? _buildDmCheckbox(BuildContext context) { - if (widget.hideSendAsDm) return null; + bool _shouldShowSendToChannelCheckbox() { + if (!widget.canAlsoSendToChannelFromThread) return false; final insideThread = _effectiveController.message.parentId != null; - if (!insideThread) return null; - - return DmCheckboxListTile( - value: _effectiveController.showInChannel, - contentPadding: EdgeInsets.symmetric( - horizontal: context.streamSpacing.md, - ), - onChanged: (value) => _effectiveController.showInChannel = value, - ); + return insideThread; } Widget _buildNoPermissionMessage(BuildContext context) { diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index ab86210cd0..423a9c1096 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 213dfb64b1d0c22a668a4a0924503703ff9a33e9 + ref: 2959ed4323d189c1c43930964726f8d219e5dd94 path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 diff --git a/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart b/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart index f9db632b4c..ae3649eba5 100644 --- a/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart @@ -539,7 +539,7 @@ void main() { channel: channel, child: const Scaffold( bottomNavigationBar: StreamMessageInput( - hideSendAsDm: true, + canAlsoSendToChannelFromThread: false, ), ), ), diff --git a/packages/stream_chat_localizations/example/lib/add_new_lang.dart b/packages/stream_chat_localizations/example/lib/add_new_lang.dart index fb5df3fe51..2df5e2e888 100644 --- a/packages/stream_chat_localizations/example/lib/add_new_lang.dart +++ b/packages/stream_chat_localizations/example/lib/add_new_lang.dart @@ -132,7 +132,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get reconnectingLabel => 'Reconnecting...'; @override - String get alsoSendAsDirectMessageLabel => 'Also send as direct message'; + String get alsoSendAsDirectMessageLabel => 'Also send in Channel'; @override String get addACommentOrSendLabel => 'Add a comment or send'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart index 2f8607ac3d..9e3e4b75ee 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart @@ -112,7 +112,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get reconnectingLabel => 'Reconnecting...'; @override - String get alsoSendAsDirectMessageLabel => 'Also send as direct message'; + String get alsoSendAsDirectMessageLabel => 'Also send in Channel'; @override String get addACommentOrSendLabel => 'Add a comment or send'; From f3a978030663339670b4843a9473082d70afab35 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Fri, 13 Mar 2026 16:15:51 +0100 Subject: [PATCH 23/33] fix autocomplete tap actions (#2540) --- .../message_input/stream_message_input.dart | 25 ++++----- .../lib/src/stream_chat.dart | 55 ++++++++++--------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart index 70e8d02101..28f030e01a 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart @@ -7,7 +7,6 @@ import 'dart:math'; import 'package:desktop_drop/desktop_drop.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_portal/flutter_portal.dart'; import 'package:stream_chat_flutter/platform_widget_builder/src/platform_widget_builder.dart'; import 'package:stream_chat_flutter/src/message_input/attachment_button.dart'; import 'package:stream_chat_flutter/src/message_input/command_button.dart'; @@ -695,19 +694,17 @@ class StreamMessageInputState extends State with Restoration final elevation = widget.elevation ?? _messageInputTheme.elevation; final spacing = context.streamSpacing; - return Portal( - child: Material( - elevation: elevation ?? 8, - child: DecoratedBox( - decoration: BoxDecoration( - color: context.streamColorScheme.backgroundElevation1, - boxShadow: [if (shadow != null) shadow], - ), - child: SimpleSafeArea( - enabled: !_isPickerVisible && (widget.enableSafeArea ?? _messageInputTheme.enableSafeArea ?? true), - minimum: _isPickerVisible ? .zero : .only(bottom: spacing.md), - child: Center(heightFactor: 1, child: messageInput), - ), + return Material( + elevation: elevation ?? 8, + child: DecoratedBox( + decoration: BoxDecoration( + color: context.streamColorScheme.backgroundElevation1, + boxShadow: [if (shadow != null) shadow], + ), + child: SimpleSafeArea( + enabled: !_isPickerVisible && (widget.enableSafeArea ?? _messageInputTheme.enableSafeArea ?? true), + minimum: _isPickerVisible ? .zero : .only(bottom: spacing.md), + child: Center(heightFactor: 1, child: messageInput), ), ), ); diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index 158980f0ad..b24fdbdca2 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:flutter_portal/flutter_portal.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/video/vlc/vlc_manager.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -154,34 +155,36 @@ class StreamChatState extends State { @override Widget build(BuildContext context) { final theme = _getTheme(context, widget.streamChatThemeData); - return StreamChatConfiguration( - data: streamChatConfigData, - child: StreamChatTheme( - data: theme, - child: Builder( - builder: (context) { - final materialTheme = Theme.of(context); - final streamTheme = StreamChatTheme.of(context); - return Theme( - data: materialTheme.copyWith( - primaryIconTheme: streamTheme.primaryIconTheme, - colorScheme: materialTheme.colorScheme.copyWith( - secondary: streamTheme.colorTheme.accentPrimary, + return Portal( + child: StreamChatConfiguration( + data: streamChatConfigData, + child: StreamChatTheme( + data: theme, + child: Builder( + builder: (context) { + final materialTheme = Theme.of(context); + final streamTheme = StreamChatTheme.of(context); + return Theme( + data: materialTheme.copyWith( + primaryIconTheme: streamTheme.primaryIconTheme, + colorScheme: materialTheme.colorScheme.copyWith( + secondary: streamTheme.colorTheme.accentPrimary, + ), ), - ), - child: StreamChatCore( - client: client, - onBackgroundEventReceived: widget.onBackgroundEventReceived, - backgroundKeepAlive: widget.backgroundKeepAlive, - connectivityStream: widget.connectivityStream, - child: Builder( - builder: (context) { - return widget.child ?? const Empty(); - }, + child: StreamChatCore( + client: client, + onBackgroundEventReceived: widget.onBackgroundEventReceived, + backgroundKeepAlive: widget.backgroundKeepAlive, + connectivityStream: widget.connectivityStream, + child: Builder( + builder: (context) { + return widget.child ?? const Empty(); + }, + ), ), - ), - ); - }, + ); + }, + ), ), ), ); From ec41292f6f38b1c862fe5e2c009538346c3ad7a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Bra=C5=BCewicz?= Date: Tue, 17 Mar 2026 17:12:22 +0100 Subject: [PATCH 24/33] fix(ui): fix keystroke reporting (#2545) * fix keystroke reporting * tweak * introduced throttle to keystroke * fixes --- .../message_input/stream_message_input.dart | 22 ++++++++++++++++--- .../src/message_input/message_input_test.dart | 6 +++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart index 28f030e01a..d23df1b207 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart @@ -558,7 +558,9 @@ class StreamMessageInputState extends State with Restoration void _initialiseEffectiveController() { _effectiveController + ..removeListener(_onChangedThrottled) ..removeListener(_onChangedDebounced) + ..addListener(_onChangedThrottled) ..addListener(_onChangedDebounced); } @@ -1292,7 +1294,7 @@ class StreamMessageInputState extends State with Restoration ).merge(passedDecoration); } - late final _onChangedDebounced = debounce( + late final _onChangedThrottled = throttle( () { if (!mounted) return; @@ -1301,13 +1303,24 @@ class StreamMessageInputState extends State with Restoration final value = _effectiveController.text.trim(); if (value.isNotEmpty && channel.canUseTypingEvents) { - // Notify the server that the user started typing. channel.keyStroke(_effectiveController.message.parentId).onError( (error, stackTrace) { widget.onError?.call(error!, stackTrace); }, ); } + }, + const Duration(milliseconds: 350), + ); + + late final _onChangedDebounced = debounce( + () { + if (!mounted) return; + + final channel = StreamChannel.maybeOf(context)?.channel; + if (channel == null) return; + + final value = _effectiveController.text.trim(); int actionsLength; if (widget.actionsBuilder != null) { @@ -1702,11 +1715,14 @@ class StreamMessageInputState extends State with Restoration @override void dispose() { _disposePickerResources(); - _effectiveController.removeListener(_onChangedDebounced); + _effectiveController + ..removeListener(_onChangedThrottled) + ..removeListener(_onChangedDebounced); _controller?.dispose(); _effectiveFocusNode.removeListener(_focusNodeListener); _focusNode?.dispose(); _onChangedDebounced.cancel(); + _onChangedThrottled.cancel(); _audioRecorderController.dispose(); _draftStreamSubscription?.cancel(); super.dispose(); diff --git a/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart b/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart index ae3649eba5..e24351704e 100644 --- a/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart @@ -443,7 +443,8 @@ void main() { await tester.pumpAndSettle(); await key.currentState!.sendMessage(); - await tester.pumpAndSettle(); + // Pump past the debounce/throttle timers (350ms) + await tester.pump(const Duration(seconds: 1)); verify(() => channel.updateMessage(any())).called(1); verifyNever(() => channel.sendMessage(any())); @@ -484,7 +485,8 @@ void main() { await tester.pumpAndSettle(); await key.currentState!.sendMessage(); - await tester.pumpAndSettle(); + // Pump past the debounce/throttle timers (350ms) + await tester.pump(const Duration(seconds: 1)); verify(() => channel.sendMessage(any())).called(1); verifyNever(() => channel.updateMessage(any())); From ae2f42cdb15d12253b2e73a27be14af072c40b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Bra=C5=BCewicz?= Date: Tue, 17 Mar 2026 17:12:39 +0100 Subject: [PATCH 25/33] search bar design (#2539) --- sample_app/lib/widgets/channel_list.dart | 2 +- sample_app/lib/widgets/search_text_field.dart | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/sample_app/lib/widgets/channel_list.dart b/sample_app/lib/widgets/channel_list.dart index bde901e5df..f1511eeedf 100644 --- a/sample_app/lib/widgets/channel_list.dart +++ b/sample_app/lib/widgets/channel_list.dart @@ -116,13 +116,13 @@ class _ChannelListDefault extends StatelessWidget { @override Widget build(BuildContext context) { + final chatTheme = StreamChatTheme.of(context); return SlidableAutoCloseBehavior( child: RefreshIndicator( onRefresh: channelListController.refresh, child: StreamChannelListView( controller: channelListController, itemBuilder: (context, channels, index, defaultWidget) { - final chatTheme = StreamChatTheme.of(context); final channel = channels[index]; final backgroundColor = chatTheme.colorTheme.inputBg; final canDeleteChannel = channel.canDeleteChannel; diff --git a/sample_app/lib/widgets/search_text_field.dart b/sample_app/lib/widgets/search_text_field.dart index a5bcf6f1df..a01b3edbb9 100644 --- a/sample_app/lib/widgets/search_text_field.dart +++ b/sample_app/lib/widgets/search_text_field.dart @@ -25,7 +25,7 @@ class SearchTextField extends StatelessWidget { final textTheme = context.streamTextTheme; return Container( - height: 36, + height: 44, decoration: BoxDecoration( color: Colors.transparent, border: Border.all( @@ -33,9 +33,11 @@ class SearchTextField extends StatelessWidget { ), borderRadius: BorderRadius.circular(24), ), - margin: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 8, + margin: EdgeInsets.only( + top: spacing.md, + bottom: spacing.xs, + left: spacing.md, + right: spacing.md, ), child: Row( children: [ @@ -45,7 +47,7 @@ class SearchTextField extends StatelessWidget { controller: controller, onChanged: onChanged, decoration: InputDecoration( - prefixIconConstraints: BoxConstraints.tight(const Size(40, 24)), + prefixIconConstraints: BoxConstraints.tight(const Size(36, 24)), prefixIcon: Padding( padding: EdgeInsets.only(left: spacing.md), child: Icon( @@ -58,7 +60,6 @@ class SearchTextField extends StatelessWidget { hintStyle: textTheme.bodyDefault.copyWith( color: colorScheme.textTertiary, ), - contentPadding: EdgeInsets.zero, border: OutlineInputBorder( borderSide: BorderSide.none, borderRadius: BorderRadius.circular(24), From 5a9cc186fe1eaf8a20150ad80433dca91aee7262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Bra=C5=BCewicz?= Date: Wed, 18 Mar 2026 11:38:18 +0100 Subject: [PATCH 26/33] feat(ui): preview formatter redesigned (#2542) * preview formatter redesigned * improved poll preview * draft message preview * translations change * core bump * melos fix * tests fix * chore: Update Goldens --------- Co-authored-by: Brazol <5622717+Brazol@users.noreply.github.com> --- melos.yaml | 2 +- .../lib/src/localization/translations.dart | 24 + .../stream_channel_list_item.dart | 28 +- .../src/utils/message_preview_formatter.dart | 555 +++++---- packages/stream_chat_flutter/pubspec.yaml | 2 +- ...m_voice_recording_attachment_idle_dark.png | Bin 4702 -> 4544 bytes ..._voice_recording_attachment_idle_light.png | Bin 4354 -> 4385 bytes ...oice_recording_attachment_playing_dark.png | Bin 5381 -> 5258 bytes ...ice_recording_attachment_playing_light.png | Bin 4963 -> 4997 bytes ...ice_recording_attachment_playlist_dark.png | Bin 10776 -> 10625 bytes ...ce_recording_attachment_playlist_light.png | Bin 10405 -> 10666 bytes .../src/avatars/goldens/ci/user_avatar_0.png | Bin 2747 -> 2731 bytes .../stream_message_preview_text_test.dart | 1105 +++++++++++------ ...io_recorder_button_recording_hold_dark.png | Bin 2703 -> 2704 bytes ..._recorder_button_recording_locked_dark.png | Bin 4183 -> 4184 bytes ...recorder_button_recording_locked_light.png | Bin 4056 -> 4263 bytes ...recorder_button_recording_stopped_dark.png | Bin 4238 -> 4128 bytes ...ecorder_button_recording_stopped_light.png | Bin 3977 -> 4149 bytes .../ci/stream_message_actions_modal_dark.png | Bin 3526 -> 3531 bytes ...am_message_actions_modal_reversed_dark.png | Bin 3559 -> 3566 bytes ...ons_modal_reversed_with_reactions_dark.png | Bin 6059 -> 6068 bytes ...sage_actions_modal_with_reactions_dark.png | Bin 6051 -> 6060 bytes .../stream_message_reactions_modal_dark.png | Bin 8707 -> 8707 bytes ..._message_reactions_modal_reversed_dark.png | Bin 8753 -> 8756 bytes ...ream_audio_waveform_slider_custom_dark.png | Bin 3725 -> 3731 bytes .../ci/stream_audio_waveform_slider_dark.png | Bin 2918 -> 2822 bytes ...tream_audio_waveform_slider_empty_dark.png | Bin 1123 -> 1054 bytes ...ream_audio_waveform_slider_empty_light.png | Bin 944 -> 947 bytes ...am_audio_waveform_slider_inverted_dark.png | Bin 2924 -> 2838 bytes ...m_audio_waveform_slider_inverted_light.png | Bin 2758 -> 2894 bytes ...m_audio_waveform_slider_less_data_dark.png | Bin 2853 -> 2784 bytes ..._audio_waveform_slider_less_data_light.png | Bin 2663 -> 2746 bytes .../ci/stream_audio_waveform_slider_light.png | Bin 2738 -> 2873 bytes ...am_audio_waveform_slider_progress_dark.png | Bin 3573 -> 3540 bytes ...m_audio_waveform_slider_progress_light.png | Bin 3215 -> 3255 bytes .../goldens/ci/reaction_detail_sheet_dark.png | Bin 6150 -> 6160 bytes .../reaction_detail_sheet_filtered_dark.png | Bin 5743 -> 5750 bytes .../reaction_detail_sheet_filtered_light.png | Bin 6028 -> 6093 bytes .../ci/stream_reaction_indicator_dark.png | Bin 1660 -> 1449 bytes ...tream_reaction_indicator_fallback_dark.png | Bin 1523 -> 1385 bytes .../ci/stream_reaction_indicator_own_dark.png | Bin 1523 -> 1385 bytes .../ci/stream_reaction_picker_dark.png | Bin 4053 -> 4055 bytes .../stream_reaction_picker_selected_dark.png | Bin 4706 -> 4709 bytes .../stream_reaction_picker_selected_light.png | Bin 5317 -> 5318 bytes .../ci/stream_reaction_picker_subset_dark.png | Bin 3785 -> 3785 bytes .../ci/stream_draft_list_tile_dark.png | Bin 1252 -> 1241 bytes .../ci/stream_draft_list_tile_light.png | Bin 1226 -> 1236 bytes .../example/lib/add_new_lang.dart | 18 + .../lib/src/stream_chat_localizations_ca.dart | 12 + .../lib/src/stream_chat_localizations_de.dart | 12 + .../lib/src/stream_chat_localizations_en.dart | 12 + .../lib/src/stream_chat_localizations_es.dart | 12 + .../lib/src/stream_chat_localizations_fr.dart | 12 + .../lib/src/stream_chat_localizations_hi.dart | 12 + .../lib/src/stream_chat_localizations_it.dart | 12 + .../lib/src/stream_chat_localizations_ja.dart | 12 + .../lib/src/stream_chat_localizations_ko.dart | 12 + .../lib/src/stream_chat_localizations_no.dart | 12 + .../lib/src/stream_chat_localizations_pt.dart | 12 + .../test/translations_test.dart | 4 + 60 files changed, 1247 insertions(+), 623 deletions(-) diff --git a/melos.yaml b/melos.yaml index 70727ccd9b..5fb9b8f50f 100644 --- a/melos.yaml +++ b/melos.yaml @@ -95,7 +95,7 @@ command: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 2959ed4323d189c1c43930964726f8d219e5dd94 + ref: 57785868e62299901361affbddd06f71253e872f path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 diff --git a/packages/stream_chat_flutter/lib/src/localization/translations.dart b/packages/stream_chat_flutter/lib/src/localization/translations.dart index 71eb3844f6..2ed97aa956 100644 --- a/packages/stream_chat_flutter/lib/src/localization/translations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/translations.dart @@ -537,6 +537,18 @@ abstract class Translations { /// The text for video attachment in channel list preview String get videoAttachmentText; + /// The text for file attachment in channel list preview + String get fileAttachmentText; + + /// The text for multiple files attachment in channel list preview + String filesAttachmentCountText(int count); + + /// The text for multiple photos attachment in channel list preview + String photosAttachmentCountText(int count); + + /// The text for multiple videos attachment in channel list preview + String videosAttachmentCountText(int count); + /// The text for poll when current user voted String get pollYouVotedText; @@ -1208,6 +1220,18 @@ Attachment limit exceeded: it's not possible to add more than $limit attachments @override String get videoAttachmentText => 'Video'; + @override + String get fileAttachmentText => 'File'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'File' : '$count files'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'Photo' : '$count photos'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'Video' : '$count videos'; + @override String get pollYouVotedText => 'You voted'; diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart index f29bacaf0f..8df6f3c196 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart @@ -454,7 +454,7 @@ class _ChannelListDeliveryStatus extends StatelessWidget { @override Widget build(BuildContext context) { - final colorTheme = StreamChatTheme.of(context).colorTheme; + final colorTheme = context.streamMessageTheme.mergeWithDefaults(context); final colorScheme = context.streamColorScheme; return BetterStreamBuilder>( @@ -466,28 +466,28 @@ class _ChannelListDeliveryStatus extends StatelessWidget { final Widget icon; if (isRead) { - icon = StreamSvgIcon( + icon = Icon( + context.streamIcons.doupleCheckmark1Small, size: 16, - icon: StreamSvgIcons.checkAll, - color: colorTheme.accentPrimary, + color: colorTheme.outgoing?.textReadColor ?? colorScheme.accentPrimary, ); } else if (isDelivered) { - icon = StreamSvgIcon( + icon = Icon( + context.streamIcons.doupleCheckmark1Small, size: 16, - icon: StreamSvgIcons.checkAll, - color: colorScheme.textTertiary, + color: colorTheme.outgoing?.textTimestampColor ?? colorScheme.textTertiary, ); } else if (message.state.isCompleted) { - icon = StreamSvgIcon( + icon = Icon( + context.streamIcons.checkmark1Small, size: 16, - icon: StreamSvgIcons.check, - color: colorScheme.textTertiary, + color: colorTheme.outgoing?.textTimestampColor ?? colorScheme.textTertiary, ); } else if (message.state.isOutgoing) { - icon = StreamSvgIcon( + icon = Icon( + context.streamIcons.clock, size: 16, - icon: StreamSvgIcons.time, - color: colorScheme.textTertiary, + color: colorTheme.outgoing?.textTimestampColor ?? colorScheme.textTertiary, ); } else { return const Empty(); @@ -647,7 +647,7 @@ class _ChannelLastMessageWithStatusState extends State<_ChannelLastMessageWithSt if (latestLastMessage == null) { return Text( context.translations.emptyMessagesText, - style: widget.textStyle, + style: widget.textStyle?.copyWith(color: context.streamColorScheme.textTertiary), maxLines: 1, overflow: TextOverflow.ellipsis, ); diff --git a/packages/stream_chat_flutter/lib/src/utils/message_preview_formatter.dart b/packages/stream_chat_flutter/lib/src/utils/message_preview_formatter.dart index b43a9c298d..88b178ce18 100644 --- a/packages/stream_chat_flutter/lib/src/utils/message_preview_formatter.dart +++ b/packages/stream_chat_flutter/lib/src/utils/message_preview_formatter.dart @@ -1,7 +1,5 @@ import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; -import 'package:stream_chat_flutter/src/utils/extensions.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@template messagePreviewFormatter} /// Formats message previews for display. @@ -46,13 +44,14 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// ```dart /// class CustomFormatter extends StreamMessagePreviewFormatter { /// @override -/// String formatGroupMessage( +/// TextSpan? formatGroupMessage( /// BuildContext context, /// User? messageAuthor, -/// String messageText, +/// User? currentUser, /// ) { -/// if (messageAuthor == null) return messageText; -/// return '${messageAuthor.name} says: $messageText'; +/// final name = messageAuthor?.name; +/// if (name == null || name.isEmpty) return null; +/// return TextSpan(text: '$name says: '); /// } /// } /// ``` @@ -93,25 +92,29 @@ abstract interface class MessagePreviewFormatter { /// /// This formatter applies context-aware formatting based on message type, /// sender identity, and channel configuration. It handles various message -/// types including regular text, attachments, polls, system messages, and -/// deleted messages. +/// types including regular text, attachments, polls, locations, system +/// messages, and deleted messages. /// /// ## Message Type Handling /// /// The formatter handles messages differently based on their type: /// -/// * **Deleted messages** - Shows "Message deleted" +/// * **Deleted messages** - Shows a ban icon with localized deleted label /// * **System messages** - Shows the message text directly -/// * **Poll messages** - Shows poll emoji with voter/creator info +/// * **Poll messages** - Shows a poll icon with the poll name +/// * **Location messages** - Shows a map pin icon with location label or +/// message caption /// * **Regular messages** - Shows text with optional attachment previews /// /// ## Sender Context /// -/// The formatting adapts based on who sent the message: +/// In group channels (member count > 2), [formatGroupMessage] prepends +/// a sender prefix: +/// +/// * **Current user** - Adds bold "You:" prefix +/// * **Other users** - Adds bold first-name prefix /// -/// * **Current user** - Adds "You:" prefix -/// * **Direct messages (1-on-1)** - No prefix -/// * **Group messages** - Adds sender name prefix +/// In direct (1-on-1) channels, no sender prefix is added. /// /// ## Customization /// @@ -120,19 +123,22 @@ abstract interface class MessagePreviewFormatter { /// ```dart /// class ShortFormatter extends StreamMessagePreviewFormatter { /// @override -/// String formatCurrentUserMessage(BuildContext context, String text) { -/// // Remove "You:" prefix for cleaner display. -/// return text; +/// TextSpan? formatGroupMessage( +/// BuildContext context, +/// User? messageAuthor, +/// User? currentUser, +/// ) { +/// // Remove sender prefix for cleaner display. +/// return null; /// } /// /// @override -/// String formatPollMessage( +/// TextSpan formatPollMessage( /// BuildContext context, /// Poll poll, /// User? currentUser, /// ) { -/// // Always show just the poll name. -/// return poll.name.isEmpty ? '📊 Poll' : '📊 ${poll.name}'; +/// return TextSpan(text: poll.name.isEmpty ? 'Poll' : poll.name); /// } /// } /// ``` @@ -143,18 +149,17 @@ abstract interface class MessagePreviewFormatter { /// /// **Content Extraction:** /// * [formatRegularMessage] - Extracts message content (text + attachments) -/// * [formatMessageAttachments] - Formats attachment previews +/// * [formatMessageAttachments] - Formats attachment previews with icons /// /// **Message Types:** /// * [formatDeletedMessage] - Formats deleted messages /// * [formatSystemMessage] - Formats system messages /// * [formatEmptyMessage] - Formats empty messages /// * [formatPollMessage] - Formats poll messages +/// * [formatLocationMessage] - Formats shared location messages /// /// **Sender Context:** -/// * [formatCurrentUserMessage] - Formats messages from current user -/// * [formatDirectMessage] - Formats messages in 1-on-1 channels -/// * [formatGroupMessage] - Formats messages in group channels +/// * [formatGroupMessage] - Adds sender prefix in group channels /// /// **Draft Messages:** /// * [getDraftPrefix] - Returns the draft message prefix text @@ -167,6 +172,7 @@ class StreamMessagePreviewFormatter implements MessagePreviewFormatter { TextSpan formatMessage( BuildContext context, Message message, { + bool showCaption = true, ChannelModel? channel, User? currentUser, TextStyle? textStyle, @@ -176,41 +182,19 @@ class StreamMessagePreviewFormatter implements MessagePreviewFormatter { message, channel, currentUser, + showCaption: showCaption, ); - final mentionedUsers = message.mentionedUsers; - if (mentionedUsers.isEmpty) { - return TextSpan(text: previewText, style: textStyle); - } - - final mentionedUsersRegex = RegExp( - mentionedUsers.map((it) => '@${RegExp.escape(it.name)}').join('|'), - ); - - final children = [ - ...previewText.splitByRegExp(mentionedUsersRegex).map( - (text) { - if (mentionedUsers.any((it) => '@${it.name}' == text)) { - return TextSpan( - text: text, - style: textStyle?.copyWith(fontWeight: FontWeight.bold), - ); - } - - return TextSpan(text: text, style: textStyle); - }, - ), - ]; - - return TextSpan(children: children); + return TextSpan(children: [previewText], style: textStyle); } - String _buildPreviewText( + TextSpan _buildPreviewText( BuildContext context, Message message, ChannelModel? channel, - User? currentUser, - ) { + User? currentUser, { + bool showCaption = true, + }) { if (message.isDeleted) { return formatDeletedMessage(context, message); } @@ -223,285 +207,420 @@ class StreamMessagePreviewFormatter implements MessagePreviewFormatter { return formatPollMessage(context, poll, currentUser); } + TextSpan? messageSpan; + if (message.sharedLocation case final location?) { - return formatLocationMessage(context, location); + messageSpan = formatLocationMessage( + context, + message, + location, + showCaption: showCaption, + ); + } else { + messageSpan = formatRegularMessage( + context, + message, + showCaption: showCaption, + ); } - final messagePreviewText = formatRegularMessage(context, message); - if (messagePreviewText == null) return formatEmptyMessage(context, message); + if (messageSpan == null) return formatEmptyMessage(context, message); - if (channel == null) return messagePreviewText; + if (channel == null) return messageSpan; - if (message.user?.id == currentUser?.id) { - return formatCurrentUserMessage(context, messagePreviewText); - } + return TextSpan( + children: [ + if (channel.memberCount > 2) ?formatGroupMessage(context, message.user, currentUser), + messageSpan, + ], + ); + } - if (channel.memberCount > 2) { - return formatGroupMessage(context, message.user, messagePreviewText); - } + TextSpan _textSpanWithMentions(String text, List mentionedUsers) { + if (mentionedUsers.isEmpty) return TextSpan(text: text); + + final mentionRegex = RegExp( + mentionedUsers.map((it) => '@${RegExp.escape(it.name)}').join('|'), + ); - return formatDirectMessage(context, messagePreviewText); + final parts = text.splitByRegExp(mentionRegex); + if (parts.length <= 1) return TextSpan(text: text); + + return TextSpan( + children: parts.map((part) { + if (mentionedUsers.any((it) => '@${it.name}' == part)) { + return TextSpan( + text: part, + style: const TextStyle(fontWeight: FontWeight.bold), + ); + } + return TextSpan(text: part); + }).toList(), + ); } - /// The text content of a regular [message], including attachment previews. + /// The content of a regular [message] as a [TextSpan], including attachment + /// previews. /// /// Extracts the message text and formats any attachments using - /// [formatMessageAttachments]. Returns `null` if the message has no text - /// or attachments. + /// [formatMessageAttachments]. Mentions within the text are bolded. + /// Returns `null` if the message has no text or attachments. + /// + /// When [showCaption] is `true` and the message has both text and + /// attachments, the text is shown alongside the attachment icon. /// /// Override to customize how message content is extracted: /// /// ```dart /// @override - /// String? formatRegularMessage(BuildContext context, Message message) { - /// // Only show text, ignore attachments - /// return message.text; + /// TextSpan? formatRegularMessage( + /// BuildContext context, + /// Message message, { + /// bool showCaption = true, + /// }) { + /// final text = message.text; + /// if (text == null || text.isEmpty) return null; + /// return TextSpan(text: text); /// } /// ``` @protected - String? formatRegularMessage(BuildContext context, Message message) { + TextSpan? formatRegularMessage( + BuildContext context, + Message message, { + bool showCaption = true, + }) { final messageText = switch (message.text?.trim()) { final text? when text.isNotEmpty => text, _ => null, }; final attachments = message.attachments; - if (attachments.isEmpty) return messageText; - - return formatMessageAttachments(context, messageText, message.attachments); - } + final mentionedUsers = message.mentionedUsers; - /// The preview text for a deleted [message]. - @protected - String formatDeletedMessage(BuildContext context, Message message) { - return context.translations.messageDeletedLabel; - } + if (attachments.isEmpty) { + return messageText != null ? _textSpanWithMentions(messageText, mentionedUsers) : null; + } - /// The preview text for a system [message]. - @protected - String formatSystemMessage(BuildContext context, Message message) { - if (message.text case final text? when text.isNotEmpty) return text; - return context.translations.systemMessageLabel; + return formatMessageAttachments( + context, + messageText, + message.attachments, + mentionedUsers: mentionedUsers, + showCaption: showCaption, + ); } - /// The preview text for an empty [message]. + /// The preview [TextSpan] for a deleted [message]. + /// + /// Shows a ban icon followed by the localized deleted message label, + /// both styled with the tertiary text color. @protected - String formatEmptyMessage(BuildContext context, Message message) { - return context.translations.emptyMessagePreviewText; + TextSpan formatDeletedMessage(BuildContext context, Message message) { + return TextSpan( + children: [ + WidgetSpan( + child: Icon( + context.streamIcons.circleBanSign, + size: 16, + color: context.streamColorScheme.textTertiary, + ), + ), + WidgetSpan( + child: SizedBox(width: context.streamSpacing.xxs), + ), + TextSpan( + text: context.translations.messageDeletedLabel, + style: TextStyle(color: context.streamColorScheme.textTertiary), + ), + ], + ); } - /// The formatted [messageText] with "You:" prefix for the current user. + /// The preview [TextSpan] for a system [message]. /// - /// Override this to customize how messages from the current user are - /// displayed: - /// - /// ```dart - /// @override - /// String formatCurrentUserMessage( - /// BuildContext context, - /// String messageText, - /// ) { - /// return messageText; // Remove prefix - /// } - /// ``` + /// Returns the message text if available, otherwise a localized + /// system message label. @protected - String formatCurrentUserMessage(BuildContext context, String messageText) { - return '${context.translations.youText}: $messageText'; + TextSpan formatSystemMessage(BuildContext context, Message message) { + if (message.text case final text? when text.isNotEmpty) return TextSpan(text: text); + return TextSpan(text: context.translations.systemMessageLabel); } - /// The [messageText] without prefix for 1-on-1 channels. - /// - /// No prefix is added since the other user's identity is clear from the - /// channel itself. Override to add context if needed: + /// The preview [TextSpan] for an empty [message]. /// - /// ```dart - /// @override - /// String formatDirectMessage(BuildContext context, String messageText) { - /// return '💬 $messageText'; - /// } - /// ``` + /// Returns the localized empty message preview text, styled with the + /// tertiary text color. @protected - String formatDirectMessage(BuildContext context, String messageText) { - return messageText; + TextSpan formatEmptyMessage(BuildContext context, Message message) { + return TextSpan( + text: context.translations.emptyMessagePreviewText, + style: TextStyle(color: context.streamColorScheme.textTertiary), + ); } - /// The formatted [messageText] with [messageAuthor] name prefix for groups. + /// A bold sender prefix [TextSpan] for group channel previews. /// - /// Adds the author's name as a prefix. Returns [messageText] without - /// prefix if [messageAuthor] is `null`. + /// Returns a "You: " prefix when [messageAuthor] matches [currentUser], + /// or the author's first name followed by ": " for other users. Returns + /// `null` if the author name is unavailable. /// - /// Override to customize author name formatting: + /// Override to customize the sender prefix: /// /// ```dart /// @override - /// String formatGroupMessage( + /// TextSpan? formatGroupMessage( /// BuildContext context, /// User? messageAuthor, - /// String messageText, + /// User? currentUser, /// ) { - /// if (messageAuthor == null) return messageText; - /// return '${messageAuthor.name} says: $messageText'; + /// final name = messageAuthor?.name; + /// if (name == null || name.isEmpty) return null; + /// return TextSpan(text: '$name: '); /// } /// ``` @protected - String formatGroupMessage( + TextSpan? formatGroupMessage( BuildContext context, User? messageAuthor, - String messageText, + User? currentUser, ) { - final authorName = messageAuthor?.name; - if (authorName == null || authorName.isEmpty) return messageText; + if (messageAuthor?.id == currentUser?.id) { + return TextSpan( + text: '${context.translations.youText}: ', + style: TextStyle( + fontWeight: FontWeight.bold, + color: context.streamColorScheme.textTertiary, + ), + ); + } + + final authorName = messageAuthor?.name.split(' ')[0]; + if (authorName == null || authorName.isEmpty) return null; - return '$authorName: $messageText'; + return TextSpan( + text: '$authorName: ', + style: TextStyle( + fontWeight: FontWeight.bold, + color: context.streamColorScheme.textTertiary, + ), + ); } - /// The formatted preview for the first attachment in [attachments]. + /// The formatted preview [TextSpan] for [attachments]. /// - /// Formats each attachment type with an emoji icon and title. The - /// [messageText] is used as fallback for certain types. Returns - /// [messageText] if no attachments are present or the type is unsupported. + /// Renders an icon prefix based on the attachment type, followed by either + /// the [messageText] (when [showCaption] is `true`) or a descriptive suffix + /// (attachment name, count, or duration). [mentionedUsers] in the message + /// text are bolded. /// - /// Supported types: Audio (🎧), File (📄), Image (📷), Video (📹), - /// Giphy (/giphy), and Voice Recording (🎤). + /// Returns `null` if [attachments] is empty and [messageText] is `null`. + /// + /// When attachments have mixed types, a generic file icon is used with the + /// total file count. For uniform types, a type-specific icon is shown: + /// Audio/Voice Recording (microphone), Image (camera), Video (video), + /// Giphy (/giphy), and File (file). /// /// Override to handle custom attachment types: /// /// ```dart /// @override - /// String? formatMessageAttachments( + /// TextSpan? formatMessageAttachments( /// BuildContext context, /// String? messageText, - /// Iterable attachments, - /// ) { + /// Iterable attachments, { + /// List mentionedUsers = const [], + /// bool showCaption = true, + /// }) { /// final attachment = attachments.firstOrNull; /// if (attachment?.type == 'product') { - /// return '🛍️ ${attachment?.extraData['title'] ?? "Product"}'; + /// return TextSpan(text: '🛍️ Product'); /// } /// return super.formatMessageAttachments( /// context, /// messageText, /// attachments, + /// mentionedUsers: mentionedUsers, + /// showCaption: showCaption, /// ); /// } /// ``` @protected - String? formatMessageAttachments( + TextSpan? formatMessageAttachments( BuildContext context, String? messageText, - Iterable attachments, - ) { - final translations = context.translations; + Iterable attachments, { + List mentionedUsers = const [], + bool showCaption = true, + }) { final attachment = attachments.firstOrNull; - if (attachment == null) return messageText; - - // If the message contains some attachments, we will show the first one - // and the text if it exists. - final attachmentIcon = switch (attachment.type) { - AttachmentType.audio => '🎧', - AttachmentType.file => '📄', - AttachmentType.image => '📷', - AttachmentType.video => '📹', - AttachmentType.giphy => '/giphy', - AttachmentType.voiceRecording => '🎤', - _ => null, - }; + if (attachment == null) { + return messageText != null ? _textSpanWithMentions(messageText, mentionedUsers) : null; + } - final attachmentTitle = switch (attachment.type) { - AttachmentType.audio => messageText ?? translations.audioAttachmentText, - AttachmentType.file => attachment.title ?? messageText, - AttachmentType.image => messageText ?? translations.imageAttachmentText, - AttachmentType.video => messageText ?? translations.videoAttachmentText, - AttachmentType.giphy => messageText, - AttachmentType.voiceRecording => translations.voiceRecordingText, - _ => null, + final mixedTypes = attachments.any((it) => it.type != attachment.type); + final prefix = _attachmentPrefix(context, mixedTypes ? null : attachment.type); + + if (showCaption && messageText != null) { + return TextSpan( + children: [ + prefix, + WidgetSpan(child: SizedBox(width: context.streamSpacing.xxs)), + _textSpanWithMentions(messageText, mentionedUsers), + ], + ); + } + + final suffix = _attachmentSuffix( + context, + attachment, + count: attachments.length, + isMixed: mixedTypes, + ); + + return TextSpan( + children: [ + prefix, + WidgetSpan(child: SizedBox(width: context.streamSpacing.xxs)), + ?suffix, + ], + ); + } + + InlineSpan _attachmentPrefix(BuildContext context, String? type) { + final icons = context.streamIcons; + return switch (type) { + AttachmentType.audio || AttachmentType.voiceRecording => WidgetSpan(child: Icon(icons.microphone, size: 16)), + AttachmentType.image => WidgetSpan(child: Icon(icons.camera1, size: 16)), + AttachmentType.video => WidgetSpan(child: Icon(icons.video, size: 16)), + AttachmentType.giphy => const TextSpan(text: '/giphy'), + _ => WidgetSpan(child: Icon(icons.fileBend, size: 16)), }; + } - if (attachmentIcon != null || attachmentTitle != null) { - return [attachmentIcon, attachmentTitle].nonNulls.join(' '); - } + TextSpan? _attachmentSuffix( + BuildContext context, + Attachment attachment, { + required int count, + required bool isMixed, + }) { + final translations = context.translations; + + if (isMixed) return TextSpan(text: translations.filesAttachmentCountText(count)); - return messageText; + return switch (attachment.type) { + AttachmentType.audio => TextSpan(text: translations.audioAttachmentText), + AttachmentType.voiceRecording => TextSpan( + text: '${translations.voiceRecordingText} (${attachment.duration.toMinutesAndSeconds()})', + ), + AttachmentType.file => TextSpan( + text: (count == 1 ? attachment.file?.name : null) ?? translations.filesAttachmentCountText(count), + ), + AttachmentType.image => TextSpan(text: translations.photosAttachmentCountText(count)), + AttachmentType.video => TextSpan(text: translations.videosAttachmentCountText(count)), + _ => null, + }; } - /// The formatted preview for a [poll] message with voter or creator info. + /// The formatted preview [TextSpan] for a [poll] message. /// - /// Shows the latest voter and poll name if the poll has votes, otherwise - /// shows the creator and poll name. If the poll has no votes or creator, - /// shows just the poll name. Actions by [currentUser] show as "You", - /// while actions by other users show their name. + /// Shows a poll chart icon followed by the latest vote activity when + /// available, or the poll name as a fallback. Specifically: + /// + /// - If the [currentUser] cast the latest vote, shows "You voted: {answer}". + /// - If another user cast the latest vote, shows "{name} voted: {answer}". + /// - Otherwise, falls back to displaying the [poll] name (trimmed). If the + /// name is empty, only the icon is shown. /// /// Override to customize poll formatting: /// /// ```dart /// @override - /// String formatPollMessage( + /// TextSpan formatPollMessage( /// BuildContext context, /// Poll poll, /// User? currentUser, /// ) { - /// return poll.name.isEmpty ? '📊 Poll' : '📊 ${poll.name}'; + /// return TextSpan( + /// text: poll.name.isEmpty ? 'Poll' : poll.name, + /// ); /// } /// ``` @protected - String formatPollMessage( + TextSpan formatPollMessage( BuildContext context, Poll poll, User? currentUser, ) { final translations = context.translations; + TextSpan? latestVoterSpan; - // If the poll already contains some votes, we will preview the latest voter - // and the poll name - if (poll.latestVotes.firstOrNull?.user case final latestVoter?) { - if (latestVoter.id == currentUser?.id) { + if (poll.latestVotes.firstOrNull case final latestVote?) { + if (latestVote.user?.id == currentUser?.id) { final youVoted = translations.pollYouVotedText; - return '📊 $youVoted: "${poll.name}"'; + latestVoterSpan = TextSpan(text: '$youVoted: ${latestVote.answerText}'); + } else if (latestVote.user case final latestVoter?) { + if (latestVote.answerText != null) { + final someoneVoted = translations.pollSomeoneVotedText(latestVoter.name.split(' ')[0]); + latestVoterSpan = TextSpan(text: '$someoneVoted: ${latestVote.answerText}'); + } } - - final someoneVoted = translations.pollSomeoneVotedText(latestVoter.name); - return '📊 $someoneVoted: "${poll.name}"'; - } - - // Otherwise, we will show the creator of the poll and the poll name - if (poll.createdBy case final creator?) { - if (creator.id == currentUser?.id) { - final youCreated = translations.pollYouCreatedText; - return '📊 $youCreated: "${poll.name}"'; - } - - final someoneCreated = translations.pollSomeoneCreatedText(creator.name); - return '📊 $someoneCreated: "${poll.name}"'; - } - - // Otherwise, we will show the poll name if it exists. - if (poll.name.trim() case final pollName when pollName.isNotEmpty) { - return '📊 $pollName'; } - // If nothing else, we will show the default poll emoji. - return '📊'; + return TextSpan( + children: [ + WidgetSpan(child: Icon(context.streamIcons.chart5, size: 16)), + if (latestVoterSpan case final latestVoterSpan?) ...[ + WidgetSpan(child: SizedBox(width: context.streamSpacing.xxs)), + latestVoterSpan, + ] else if (poll.name.trim() case final pollName when pollName.isNotEmpty) ...[ + WidgetSpan(child: SizedBox(width: context.streamSpacing.xxs)), + TextSpan(text: pollName), + ], + ], + ); } - /// The formatted preview for a shared [location] message. + /// The formatted preview [TextSpan] for a shared [location] message. + /// + /// Shows a map pin icon followed by the [message] text (when [showCaption] + /// is `true` and text is available) or a localized location label. Live + /// locations use a distinct label from static ones. /// /// Override to customize shared location formatting: /// /// ```dart /// @override - /// String formatLocationMessage( + /// TextSpan formatLocationMessage( /// BuildContext context, - /// Location location, - /// ) { - /// return '📍 (${location.latitude}, ${location.longitude})'; + /// Message message, + /// Location location, { + /// bool showCaption = true, + /// }) { + /// return TextSpan( + /// text: '📍 (${location.latitude}, ${location.longitude})', + /// ); /// } /// ``` @protected - String formatLocationMessage( + TextSpan formatLocationMessage( BuildContext context, - Location location, - ) { - final translations = context.translations; - return translations.locationLabel(isLive: location.isLive); + Message message, + Location location, { + bool showCaption = true, + }) { + return TextSpan( + children: [ + WidgetSpan(child: Icon(context.streamIcons.mapPin, size: 16)), + WidgetSpan( + child: SizedBox(width: context.streamSpacing.xxs), + ), + if (message.text?.trim() case final messageText? when messageText.isNotEmpty && showCaption) ...[ + _textSpanWithMentions(messageText, message.mentionedUsers), + ] else ...[ + TextSpan(text: context.translations.locationLabel(isLive: location.isLive)), + ], + ], + ); } @override @@ -510,16 +629,16 @@ class StreamMessagePreviewFormatter implements MessagePreviewFormatter { DraftMessage draftMessage, { TextStyle? textStyle, }) { - final theme = StreamChatTheme.of(context); - final colorTheme = theme.colorTheme; + final colorScheme = context.streamColorScheme; return TextSpan( - text: getDraftPrefix(context), - style: textStyle?.copyWith( - fontWeight: FontWeight.bold, - color: colorTheme.accentPrimary, - ), children: [ + TextSpan( + text: getDraftPrefix(context), + style: (textStyle ?? context.streamTextTheme.captionEmphasis).copyWith( + color: colorScheme.accentPrimary, + ), + ), const TextSpan(text: ' '), // Space between prefix and message TextSpan(text: draftMessage.text, style: textStyle), ], diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 423a9c1096..4120ef87c8 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 2959ed4323d189c1c43930964726f8d219e5dd94 + ref: 57785868e62299901361affbddd06f71253e872f path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_dark.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_dark.png index f657f9c2a858bee97fc55264706d47417e218dbf..3a811f6109dbc03f311b4ce33a8b551dbca5f57e 100644 GIT binary patch literal 4544 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 diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_light.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_light.png index 8bbcbee122052aaf8adc3498f11d9739ebae4577..e751233e68397942944fde16fecdcf4510a68fb8 100644 GIT binary patch literal 4385 zcmd^C_ghoh);itheVJjN|6$Q0VWg~3y2^=LNNvv zr349=K&TQBr6>Xl2_;mACcT6NNl5rkxX<^@58wCPf8d@U&N=Ts>s@=Vwb!%GyAy4! z%%%1m*#iK86!P*VTL9QW0N>jrc7gXHRi^;(A&N0U+Dm}fpAy~=LA(QFYikz`D{l9M7!D?PG1x>@x~c4nxA%fyctm)FTU1p3=xlvx z)}aLp^;_J9nOWkJ_^y{DMP&02eza>rU0#!sL`eYZkUA2B+7fgG&7y+dz0~J$c8v(y zyAY+NL$zdacR7^i#3@ujhI__A3CVA99&F#Ao9__JQwDLd<;USh-F$Zn^!=;-L_Q+{~YyS;pM5&ZgMN)s+B4(tgaZo{!D6(PLQXn5(t7|@uU z#TI(aI$o|IWa`{-%mYc+cXfE)k&p(y6uLZ;e!nB`@HxwG$6)|;{KhtUs!?N`OgJ62 zZIyPyTbDJFo-o^nYOdR`?1bqyRJRa9^iI={!2N?7O2j*+oQ1&v)RSvn)pN zi$;b=6)lBp9Lpv>zJL4O>gWDf!u_=lr~u-!t74~91RL_BUvv3C%(=*NgJq6;j3AOP z@HG}rk~=?EzrJSezQw&Pj4Mv^upXRq$xIB$I$x|GG;2A^#S4C7a(hZk-tK*8^nM3` z?mxY47{6Cu7>QgzbQ`|hdt@6jm9}9TT2~kol=4WJaiHbzbm1-nUaXMq&t=?yhtY4O z4!<(88_3E!b!t-?oIVjQ+&JLMkl^`wkbixbz7zl?E5QKZBp6_$1UMt~F6i*zLl$|o z3kV4Yd{Hgg>)zG3x*aV#+CZbdK}rc_nlxYIr$KjEsZSdTdOaO{OtFFvpS}5}>;=gk z@6MTkDd_HKG`!q%g%7nwNQ8a$^Y zL{d!T*wG!p)J3YTqg<;Oa5uAr}^34x(@is&sL{uD>W@E13!fj(%JGu zxPWK)oxi2Y7{mBVw);$N&qUrv5>dFx8cLvLr4JHEF?0e+tScb|(!n)vl4Bd3~lKu^}+>rqE~KzsM9=e4VZ zz0}}+z;)ewu%?*@Y2CU#CMDV{SWUXx?AlH-59QJ2u%p1w7v+%mOR>!tJ3el{#*MdW zMX$Cm&LE5;7uRBkt+ApWGU`a%lVt{WrCh%V9_4LTk3W!q!s-{JA%C?b9P9L3UD?UZ zYZ-)Z_&8+unxR|kb7DBebrtR8r0+Ok6%v_UwCfbo4h-ONbIuA@i>?MYTr9l9otFl( zx&x@fy7R|iPbfxNrsPFYUq!L`kW~+vxkbLYYoLbTn=&W|>-Nz;wkNzcUiJ{lZg9)C zK0mw^LFJ27qro%`i>Dq`Ni^nf=6}=C@abX{O)$La8SSF>;?Y4Mc{O zmYqg3;?md^lTqJ6yhX|fwgPYTykvj32XcZTxlbNR z(I}eY&0>mm<96SwWUjWm9rwiKJFYlz(0ZxN7LGy2nCMmIDqXhxL}v!t+(XVfS`L`9 zAt=LdQp0=#BbgUBo~X~YGZBW4xa4qoj_F~DRgRA>*<|>zV<@3#_iyF5AYzS#$m;0A zk`A`7Alt{qtbsT=HFZCnlXF0zN`MMbh@^BA!??8zM3k+}sh(rI;tYOiPJC6}-^I%? zasv1B+%m6gbH7!N?Cc%p;jXZ!uNA%na=9)wB9*qBWOOUhBv{)ed?kOeN&_|Xum=e= zrr%J3#SG|Zx4vGv_*kRe=MWTD8K~OcvaTdE*9J&5T~>emJ=5iEvsRs7CH%l>>dHxi zM%O8A`L)7;cDOs`2+(p|!fRvlZ?^^mohox!X0v|-n`^(ml@QAtv0sM(ir$+N1%>9n zm>R9_gw|a-YLNVyPW_ej{xFM*3sIe=ssQEfT6KGFjJ*uCD-a~-+F}$6o*bgHG~^?t zl3ooWZqA&K<)t!*gVZEWl$2`DF`V4GgNCAJHlw8>DCP*QKp7dfz;w7)vr16D6%k>* zzO>Xv(F`1}CNmfZk=+T;A4FBNE)%C`r#dt>HJ_uE>Z2o2(b3T)`(iF#Qs1G(F=M)B zWTUwb9#C2FFPpwcS~R6DmdvTdn3c{t`0Xl&vun3-tTjYNG=2oTPOcp$J32Y76m%(jKjTr=gnG-PdCiZRQJgHq0;X{yFIb1I_vU?rVxd}HW9Og zBwU3}xnlC*mSFxU|5L7@W~2sV-MT=21?x?CP{*BH;y)6s1_{1z@;Dq0V~#Cj`f_BD z79se4J(@>sg4%pt^5>0UXV$Vw(Sz$Ut!dzL!o!bQ;JAF+G2=fAm63E~_5!=*kYXQV(_LHa?)XH<*Dm{rmBW#@ZZiv)Mg#> z{x`VaoED9@u2>VWAde6f#r(`uw8R~G{D#$6uInIUnv_1V8Eb>w!#(9M!REcUGcV@% z^hGN=dlkmQwIbqDTm&-2mRGsxO$f^PXDt7HtOZV1cJ{uR7+M-^QPlc!-WNnW zYv@N`dBD>A%xp_=!CrB_49sQPw5saYt5&}hsC6~guYQPHULJ@Y$fd*m)RR0N)D9Y8 zVl*Y8U~>Wc)a*CG_lgIr$06%EuI?_{;ruB0`xT=wUVKvU+;3o6?RuHU%IpdKevm)K z0w7tn87+64pl9fwsbFh4ZB_Bkd+}`xos4rTrbX4yY%SOEZKyjnlXQ3Y>FXP>L?5w~ z1l#FVnX?57&>g5}bIhP)hfcsjYW;_kf!DAc%IIA z?HR9cc{nOQe>xhofESE62&TQX(1OLF7%LooL3T^l>bk58{!K|o7}&y!7T94vShyr* zyR9D>9vZ6tIgolbdluU$$w*3J4i_zK74|SpsM=KiGuu)Jwz)gcXhTq*I^l+$oMici zNJB;uT>y?CC2OsgL2rC`N4+ zxK$Br+*>;PTa7p&|I)D3FAZ#CS#URo#m$!7B%3ZH8d$&4rhivChnpCpVR;-S`FXw6 z3L523{L-d3_}{M7U?;Tw+-bUS+dXFBS$|2~=*Hsc?-N+9Sx5sXqd zUZ(kT##~=4JCiM5x|Kp({%mReXTck{QmuAM*mw(-Ro83r1{YONLw7NHAJ35-)hU4} zd|+|kT(Rv6Xb`p$<3gTO`&~!u!VA}Wvb;X|Q`ohPb{{d1LTGYDMMdFUU+Je1g$zE0 z%l|C|che`*<=HbQpTSBn+#X#9m&@F^7&%EPa6{POrTPG2x&w^E4R+ehl ztmR+i)-%q(xNt6&4lywed8N^1wKzdFKQDW^+rD0j9#T*WDyCG3Ytk!cumVU9Ef_Q3 zELrD`xK#eu{PRHmXRSafcxCFrztV8JI^llB!;%xAik+7BT(trY?Qc!wdc0OjmicRC zX6eH>avom6JZ2!>J$y3L$l=%J?@?K@GM<$`$_qjUhiS5Y=|;_rMb0FiknJq9yKxgU zj6+v>d;4YIQvZTJ1M;Tvcm5d4Zkr&w;OCaBPj+eUmj|-$nkalHs78}5Qu4;Ftl!}B zT$}p}`>3miPF0F(zuSSzsAbA>{HZKpSOPVN`6{$JdX?qlyow|5ycIl|)=AfOKs=-y z&K6GeySf&3uqOzB&7FJjrpWG3Vp`UqL@kI8jYyovPv8kXb+EUr>4^W;V^;}kNnQhZ z%~1@M>72!h-2=R6dv9Q8Q2*gXyEd3f7&0S2jiP~YH=tK)3{NGfblTxgtTV-(+ zc;S2(dSv#(=JC52O1Km-7P_a&nHHo3Ykw3*O*#LW5-|3B2XF(GXyV)q{{_0619*)77$k zoI{_Q@^V;qdA~k$(_`?tyZ&6VtV|=3CLCOUT<*0?!0)XvmA`MGWSU`3CwY&xEu(mD z97KgpJfD8*Ajm6O_`;kgw*B@*>PczjvUVTdy*t#O-f+L4IPvgC*2_&ys0*yNez4m8 z#^ylgFoR3mW+Zd7a(xiKNJm!|Nb_<4#ng5B#4f`(TxTYniE3-BSNyq+c#CmFw4*H$ zd&DM7d%LU;B13)6K{R3&Qh*Z}Y}xI@5;a&z$8d%ts&OLp%c?*FD~#S)=L4TM!4!!4 zw#)dsxZHOY6YAoK!k5-mE}ag5h&|x~B(D!HIj|-d=Sj5FqO`wx_<_`cHwAO;o4D$T z{mr6h{2>3-IfK9rOo!$0A5g&5eDloVIAxPV_-OMK8!9@VFVOJ=ob?An13?=DFKlt_ zk!KYv*erz@_iL(0egoXcD)r(iK)R_c zB+UrAmk0LHi9TC`tH}dPAgVcFovakGo;@>`kJ+x@OQ!PEobHR{ws6IR$34o(2@I znV9^7frQgRhuM`>rb zUs1A?YhiaXy{!@Qo;coL@eenc9fm!_<6BlY8wt6@t~)i?0{z}e6EP(f!Wy;LNBB8_ zdN0=Le$NI0A^r8w6Z~R1#I9HJvcxymU|74Q7 zuSxa?^uWqx`_tSqH(E_GH-;Nfwsj~Y+?<+iM_1K{Mx+do3R{j*eF1jbi>zP7PS-%h3v#H^l*1*wJ<94FW+y5kVI_ep*8ewy002 z<184_m&S$xR(Ih?G+q2O^IwMwJzQUf|XY}1F67MmN}i$CQO zpB|jaQ`oN-mqN;%zrE|0XK$8MIC`Et>gFk_vf0APA?8kA1-PIhj}H_hpQgAKspbX` z1c()#HGl%W9@Z;Sj%(_dNVO#=a~#qg+sl}8{no*~%Mt(uS$`_0laVlR4Y_A}Y3TS@ z$rCt~ec$G~I!}-4bpR~&M*%U33 z4Nw2sORHH<%&y7Z8Ij@yd$Gz4T`qb#=B+FV3t5>QCKA&<8?u}*J8t8&AR)b1jOj3wu&^-C8|Ol|#^DRh zcD%$eeWGdHr>UW}wGxhqu)@tfR1jHWF=pUR6XR5Bxf6Op2xeX~I-GkeL@@4^Jfgi) zRK-UjxYy^wk8a*@lU$$3LexF56ccynJyujOPv@YG$-gDU36AAI!XCh7|LgK&r$uGQCk*MMVr$5Y{HKYE1>w z@~g-O1+38HY6mXG&b!~v;p8UF%krou^>-WHhUiK;s|RZxLQ>)^fqg{eT0Xu|pRhM{ z0;r$gAnT#LeKZ+idS)W5$9ajL#2lUMF)NH-)aAt&oI@6N1%#X%ALoZU+cpTHzm|3m zzT^`%pq3D+a_frnR6pt;&eq`n2K*|iPfz8u%XHY-sG?su!LcLH*pJhsO{?_!q^hZ4 zY^ckF`MrhLIiY$TJ8GR=#Xil=rP79m4PRVq#4j70%Q=>cPAra9F$YQ>FY8BoY|lNdT|bKD592!8u2O5y(ZR~?$o@dfBZ^0E^$+c#Q9-u8li~Cw z;*f4EFY9|vM}cN2jlr&c_04(~=3qX$X|9FnK(eYKr7WV~Vpwm%YWAK;ceAB#g@SAP z)_*SU$%PlRw}zvZFs+qjv#X*0^uTZ8GBJF`<`P<@VzucJ*|^qSxe(wt12s( zdV~?Fs0rRDw^^dwgC^z(}x8IjfY z$u#SPyK;)CqRf8DHFdO)9y51E_g{>GO>JgwwOW(5H{i@`=>IfefZ<>TG062nq+tnU z?Z4`sNSCqNZCZu0{bQw8iJxBf%E$qat&uoC+)Z4SJE0isS`qYl*!iE$Ok_IpWUr@K zpq0P3qfOQx9gkbx2qU-ewpcJy)kRR%S9YlLzu zdv(L8Vf|seC=Ymux-)uLH2YTHhPH5aVzN{BW$gX=t(+zXnXI`)oi*3#8otgc`-mIZ z{NtWttbc))NZGi?*huVKqP(&JrcLpv0iN5XFu0^d8YbBDtS-Up7N8&id7r-8yrB;N z)bsX?{u?t2(u1EL07|-cuKusn#D6x+|2<&*e;0VvT0htzOu}#Scy|Ke1=BOut=4va F@;_(45(WSO diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_dark.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_dark.png index 7ba9af2dd8d19a1d4438a8ff3b5794630d845573..b49426e1aa97b96bdfc25783cba7e81b170c3cbd 100644 GIT binary patch literal 5258 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 ArvLx| diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_light.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_light.png index 537754dfb760d2434210163c352c6f549c7673fd..635539b0f524068b23f95cc10a0b4002d7423d8f 100644 GIT binary patch literal 4997 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 Y;gEVL+3Ml68~|Qs#+H9pTy?+yU&Li%@Bjb+ literal 4963 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 O0KIbka=Fp%$NvP@{zs1h diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_dark.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_dark.png index b20197c953a42c95fa42e840cd59ab49a4ed8541..d06d94aab5000384e5838ca2a3421e7bc14836d8 100644 GIT binary patch literal 10625 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 V6NjFfb3x!o>zeM>g3Grb{Ri+p#v%X! diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_light.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_light.png index 94b4e1b26ba73c64ca71de8e477da72059748ed9..e3a71314eb204612e46711e9bb60fb68702aa7a9 100644 GIT binary patch literal 10666 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 diff --git a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_0.png b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_0.png index 0c6fc5447fd9248600871c9bb7e198184b082921..48e867a1400a608fcad7d7854d3f4c2d486ab636 100644 GIT binary patch literal 2731 zcmeHJiBpr;7Eg-`PpHskOc7pq;#Zgsv>?HU8U(Ra49N0|ippxsViZKy2nnRcqE@hI zK9CpSGi?R(C`%$+z>qK@0un+pfPiEzLX06K6|%GC#XqAny)$QibIzQ(XMX40d(QbO zCMxWM-TQXKV6YF+r$WDh!QS(JCw9B6mZFlM3#_ms?*#gco%P`DQtw#x_wv38I|1w9 zyDDL@-DlCEA!lwhs3oGR24mvrs(`nm;T1a?G{R@se(T1}G-6JLMCI9t?;CF247Jz- z_xqY$JMQ0owzEvUT6O$CXJeY~)F1yavdJm;a_mQ~fF9#01r;={1HAs|r>F~~suj&# z3A2L?$*_%+ID(dk6m;L>EC8^b-Bmc)_qfl_A9pU>S-t;n_-m%%V1d{sMs$NpX7l9^ z>#dlR4AP3^G?GOEFxd3zx}!A)UFK6}O}>IpLC!Gr^Z>(pk$Ob#&C{*T_sfB?Ca5QK zPxkBiX?X6s#nm1?wQtXDa1l}Vtp_KQ%Bo@WH%OJ(X>}Au1Vu4sq8QWR6y{)#TR>#p zDlpMFmf{;u@I8&YvR@JlUwhm#j4xe%(4gYW3V!n1Y9Ht-IwttySmCqipTX0#J}h{t za8AaXT^5m&-=y`Tz?K%iXYrj$>EYm)5T8oWsL;ghLE}Z1ei^F;)mF`iN1mXw*5b`j z7#U5(Tb3F9eY!W4x2gX-MLYa5^}kGsg}*KjK#JE?qc;l$t~`JY_=KG;{kTA8ne$H};14SIc8uEo| zPUDs2>~zc0dNR`>a2w3`C|bQ&O9Uh>%CD&7oDiDH4iqPB8666#3Z9Zd{&didoY;3C zFtdfxj)8_5y%7YWc|O53eM=;*uXTI9)mcB+InZr7?uPxnhd^YBf=qYoJV4R*t{oMq z6?GDqKZ0`1&(P=9_EG^ft5Zj^soI)x4EiS?o1ydU_OeKC~cx?mhG-VTyhm*xt2T@XIYi4 z>9x`F;R@Mv^443o&H&6cRQm5Td|ARK6R+L`=Uw&M^8T8Qixs!4R5Vje9x|?U4QdD; z#Q)J`Z**w`iZ&fB|00;-gj&En`Fdu+G!kLf2&B*RTR}LVa8QO{FAwZDMWBhb)a}Cp zpArpg?$v_(d2wfnI(Q0^Jh3Te!!Zbt^XRoOnS~OVV=y z3CX;knwOJnuD%b9rECzU)Q<+dxHu-sp-_s_96Y>#M+GoO8!U9cIRlaSZ(gtGRI&fD z|2%-MW0nfrL1XS^dOXIEaC?{q8h6Xj`!9wFAr7J69CDQ|a;~x+J+{m9$CL*E#;}X7 z`J|Ybe^l*XHf$Ru^{QUXd0)0?$f7tKPHdDY)5ZSA>r0dSs_MH@l10SLk5AA}1=*cL z1{Eb2U#@%In=D*_*T1_6Ta5AW%)UM24|cE6Oy65gB+@aEosuv9PJGGNH7&hR**E&f zk51m!w`v}pa&xG{RibnooP5P5558e#gU%@4ujVJ5#4Ro&0!v$PO&+Wxsl; z5g%}F_$Bci!8aVcJ@k&LwQhccn@yQndL+r!LMaEw01@gY&xfGw3tQM>{WdQ&X_w?< znb*_^BQP%(P7HD3;OwNBT(M7_Q!R=#jqsMNq+pjhAHHxYF#6Ti6yFPL*Jp=B? zz#lyFHf+T7wY(8!dwD9^LS4xmfNJE!wx#YYOkF~~iLiFTiED)cQtqsfK_aV~=ODgL z=eYr7`wJrozFb38u>E-S(hCxWXlRMfN=1BGHtZSToQl)y9ePXan&%^^0icFwZ8+zI z>cXSY7QJ9G2W-Yn`GXF(9$V|yjha?|8OS2+#EoZtXG+~s@s+dVoZrhLpU3sBm=R8; z@fK!egWiI{Hcyl4&bXJC~*I V1@tttlXcsKp}&p_?KqKA_J8Oi4=exx delta 1818 zcmV+#2j%#y6}uIXL4RdQL_t(|obBDokEHiq$MH{9b$9hXGu<=s%y=O-;2FpEfmjhF z;Q$vRFx)snqzJ>63kM?p0S*Xm93mmXjhv8>kb^`HY#~u1WJH9GjT0}iJzi#d@$^

edbVy227_?`3TRJy00MwpvB1Fre>Ld=sBN_0 zOMmCR^m}V*hPxT>@1+^-B}v+yxwNL{)1ID7cXl=1h12Ow&0c%$c7HFgUVJ+X-MO4S zelkgNVe?Y9db>IIsfV-JotxO_;%I6O27~dWhoG48AlqxNq`z|^^&J++kX{F%J9Kc9zJ&*a4X@=cexwEbb;-uNI- zp8oV9e>jdZ!Q$492ie_tIa_N#$l}s$=DIVPn(ExvTMYL{d2u~?=KQZFN&fMb7xL%- z^f!5H{rx1#!z&Nu_kZ~}^GlEaT$1EJ-+DEl5iPC~EDkmvWPkUye{8(>a^_~Ivb4OA z)*Zgp)^MC=@$*?(Sk7O3>+kYs-~9SbxBKdsemlSOg=e$2^I_5?%d_)$^%1x~IOxs+ zn)Ehb&W9hooKq*4vwRn^N|OKia5gIo%lYo>FMVvRlH^bR;ji=U*ZwQ33&-;6#kcNa zMeZkp#m5^Dvv=t`e;Mq&nNuf^rQN=pR9!JMyP72V+Q0nmCoer7=db?l`75uPnz`$B zxjzUNA7h-oweO|Bdnw0{E#9+tJ$vG8lH|n;uN}DUEARg(N%GK%Q}=9rY7;DOs<-j| z414QYIdSu|cz0^5edS7;>8a@hw{6B(4zG6Fw|8Kl`%>+ijg)s=g@XZ2${dmBlT-}}Yi_{8m6 ztyZ4<;n~-#JAL2*_TPQ_*YD{4m(M?ur~m%D`NCsQ<*Q%% za{lz2U(cu+e_gjtr`^dP{>pFV^N&51?fzaKIrgbLT8sOS>#+DpvzPaO_>G)Cy>j5C z3wIi8*A_8FTeeK{`J-GXS&nLFFg8Wp8Mjn`Sj|UB*}lg`QLf! z?9bfsTHId*i)*$o{(IV^53;y$@I8EY6RafpgMWTLe-EGe`J7m|`A^S{oy+;*kG_?k zKE9g8lb^k-wYY!yNys(*-L;%pKAdZwo?IDcd;2^2-n&a#m^+!XCr&3xE^NG)?cIwx zzr3BNR+7!Fi&;E*C?ipTV38yl4Yx8L?PhA~_@S-2INi!KE89u7linLilI6K1b4k)l zGL>e3eqjzJ z-I?hOuU-lPf<=;~+3%&@I(lD+cDt2kzc=YII3kYTHcW74bokBaMELNLeXu=U{>4)@cZT5umB*9aEZbQc~-{|N16_4>nf7tc(342}@N;+n;k^V!Q)R z)s^LYejvVSnp|4DoTZh=(mnpPzoC30!Qz&T_TJ3;yDwz6JDugF`LtShw-+=Xk8^oz zH~s!FC(e8>ow}GfKm29oQk;R2>X1mjwp1!>^dBfp;di`OxcY0Y` zc{KCK&n4~opV-LUUj&QWH|}S9Z0)7=x86$A>}Pg%Cf%9owA-z;+wF8ZSAN!wMopTg zNz;te9}Ls$4btvRr@QpXl`Gg=9vj2$5iIV?Xt19C&N~_It)&?aGTI-c8C`iyO}jIl z&eTlW?QW*$SJPd1Fr68Xis8-)7Ep#$GP3~ze*u#)0Yeu53+4?ZkA7N6FaQ7m07*qo IM6N<$g8x|QY5)KL diff --git a/packages/stream_chat_flutter/test/src/channel/stream_message_preview_text_test.dart b/packages/stream_chat_flutter/test/src/channel/stream_message_preview_text_test.dart index d886fb8271..eb1ab51f47 100644 --- a/packages/stream_chat_flutter/test/src/channel/stream_message_preview_text_test.dart +++ b/packages/stream_chat_flutter/test/src/channel/stream_message_preview_text_test.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; @@ -19,7 +21,6 @@ void main() { when(() => clientState.currentUser).thenReturn(currentUser); }); - // Helper to pump the message preview widget Future pumpMessagePreview( WidgetTester tester, Message message, { @@ -57,7 +58,7 @@ void main() { await tester.pump(); } - group('StreamMessagePreviewText', () { + group('Message types', () { testWidgets('renders regular text message', (tester) async { final message = Message( text: 'Hello, world!', @@ -67,9 +68,10 @@ void main() { await pumpMessagePreview(tester, message); expect(find.text('Hello, world!'), findsOneWidget); + expect(_findIcons(tester), isEmpty); }); - testWidgets('renders deleted message', (tester) async { + testWidgets('renders deleted message with ban icon', (tester) async { final message = Message( text: 'Original message', type: MessageType.deleted, @@ -79,10 +81,19 @@ void main() { await pumpMessagePreview(tester, message); - expect(find.text('Message deleted'), findsOneWidget); + final icons = _findIcons(tester); + expect(icons, hasLength(1)); + expect(icons.first.size, 16); + + expect(_extractText(tester), 'Message deleted'); + + final span = _getPreviewSpan(tester); + final styledSpans = _findTextSpans(span); + final deletedSpan = styledSpans.firstWhere((s) => s.text == 'Message deleted'); + expect(deletedSpan.style?.color, isNotNull); }); - testWidgets('renders system message', (tester) async { + testWidgets('renders system message with text', (tester) async { final message = Message( text: 'User joined the channel', type: MessageType.system, @@ -91,9 +102,10 @@ void main() { await pumpMessagePreview(tester, message); expect(find.text('User joined the channel'), findsOneWidget); + expect(_findIcons(tester), isEmpty); }); - testWidgets('renders empty system message', (tester) async { + testWidgets('renders system message without text as fallback label', (tester) async { final message = Message(type: MessageType.system); await pumpMessagePreview(tester, message); @@ -101,7 +113,7 @@ void main() { expect(find.text('System Message'), findsOneWidget); }); - testWidgets('renders empty message with no attachments', (tester) async { + testWidgets('renders empty message with no text or attachments', (tester) async { final message = Message( text: '', user: User(id: 'other-user-id', name: 'Other User'), @@ -109,10 +121,35 @@ void main() { await pumpMessagePreview(tester, message); - expect(find.text(''), findsOneWidget); + expect(find.byType(StreamMessagePreviewText), findsOneWidget); + expect(_findIcons(tester), isEmpty); + }); + + testWidgets('renders null text message as empty', (tester) async { + final message = Message( + user: User(id: 'other-user-id', name: 'Other User'), + ); + + await pumpMessagePreview(tester, message); + + expect(find.byType(StreamMessagePreviewText), findsOneWidget); }); - testWidgets('renders message with mentioned users in bold', (tester) async { + testWidgets('trims whitespace-only text as empty', (tester) async { + final message = Message( + text: ' ', + user: User(id: 'other-user-id', name: 'Other User'), + ); + + await pumpMessagePreview(tester, message); + + expect(find.byType(StreamMessagePreviewText), findsOneWidget); + expect(_findIcons(tester), isEmpty); + }); + }); + + group('Mentions', () { + testWidgets('renders mentioned users with bold styling', (tester) async { final mentionedUser = User(id: 'mentioned-id', name: 'Mentioned User'); final message = Message( text: 'Hello @Mentioned User, how are you?', @@ -124,303 +161,421 @@ void main() { expect(find.text('Hello @Mentioned User, how are you?'), findsOneWidget); - // Find the rich text and verify that it contains a valid TextSpan - final textWidget = tester.widget(find.byType(Text).last); - expect(textWidget.textSpan, isNotNull); + final span = _getPreviewSpan(tester); + final mentionSpan = _findTextSpans(span).firstWhere( + (s) => s.text == '@Mentioned User', + ); + expect(mentionSpan.style?.fontWeight, FontWeight.bold); }); - group('Attachments', () { - testWidgets('renders image attachment', (tester) async { - final message = Message( - text: '', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: AttachmentType.image, - ), - ], - ); + testWidgets('renders multiple mentions with bold styling', (tester) async { + final user1 = User(id: 'user-1', name: 'Alice'); + final user2 = User(id: 'user-2', name: 'Bob'); + final message = Message( + text: 'Hey @Alice and @Bob!', + user: User(id: 'other-user-id', name: 'Other User'), + mentionedUsers: [user1, user2], + ); - await pumpMessagePreview(tester, message); + await pumpMessagePreview(tester, message, textStyle: const TextStyle()); - expect(find.text('📷 Image'), findsOneWidget); - }); + expect(find.text('Hey @Alice and @Bob!'), findsOneWidget); - testWidgets('renders image attachment with text', (tester) async { - final message = Message( - text: 'Check this out', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: AttachmentType.image, - ), - ], - ); + final span = _getPreviewSpan(tester); + final textSpans = _findTextSpans(span); + final aliceSpan = textSpans.firstWhere((s) => s.text == '@Alice'); + final bobSpan = textSpans.firstWhere((s) => s.text == '@Bob'); + expect(aliceSpan.style?.fontWeight, FontWeight.bold); + expect(bobSpan.style?.fontWeight, FontWeight.bold); + }); - await pumpMessagePreview(tester, message); + testWidgets('renders message without matching mention as plain text', (tester) async { + final mentionedUser = User(id: 'mentioned-id', name: 'NoMatch'); + final message = Message( + text: 'Hello @SomeoneElse', + user: User(id: 'other-user-id', name: 'Other User'), + mentionedUsers: [mentionedUser], + ); - expect(find.text('📷 Check this out'), findsOneWidget); - }); + await pumpMessagePreview(tester, message); - testWidgets('renders video attachment', (tester) async { - final message = Message( - text: '', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: AttachmentType.video, - ), - ], - ); + expect(find.text('Hello @SomeoneElse'), findsOneWidget); + }); + }); - await pumpMessagePreview(tester, message); + group('Single attachments', () { + testWidgets('image attachment shows camera icon and "Photo" label', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: AttachmentType.image)], + ); - expect(find.text('📹 Video'), findsOneWidget); - }); + await pumpMessagePreview(tester, message); - testWidgets('renders video attachment with text', (tester) async { - final message = Message( - text: 'Check this out', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: AttachmentType.video, - ), - ], - ); + final icons = _findIcons(tester); + expect(icons, hasLength(1)); + expect(icons.first.size, 16); + expect(_extractText(tester), 'Photo'); + }); - await pumpMessagePreview(tester, message); + testWidgets('image attachment with caption shows icon and caption', (tester) async { + final message = Message( + text: 'Check this out', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: AttachmentType.image)], + ); - expect(find.text('📹 Check this out'), findsOneWidget); - }); + await pumpMessagePreview(tester, message); - testWidgets('renders file attachment', (tester) async { - final message = Message( - text: '', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: AttachmentType.file, - title: 'document.pdf', - ), - ], - ); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Check this out'); + }); - await pumpMessagePreview(tester, message); + testWidgets('video attachment shows video icon and "Video" label', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: AttachmentType.video)], + ); - expect(find.text('📄 document.pdf'), findsOneWidget); - }); + await pumpMessagePreview(tester, message); - testWidgets('renders audio attachment', (tester) async { - final message = Message( - text: '', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: AttachmentType.audio, - ), - ], - ); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Video'); + }); + + testWidgets('video attachment with caption shows icon and caption', (tester) async { + final message = Message( + text: 'Watch this', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: AttachmentType.video)], + ); - await pumpMessagePreview(tester, message); + await pumpMessagePreview(tester, message); - expect(find.text('🎧 Audio'), findsOneWidget); - }); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Watch this'); + }); - testWidgets('renders audio attachment with text', (tester) async { - final message = Message( - text: 'Check this out', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: AttachmentType.audio, - ), - ], - ); + testWidgets('file attachment shows file icon and "File" fallback label', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: AttachmentType.file)], + ); - await pumpMessagePreview(tester, message); + await pumpMessagePreview(tester, message); - expect(find.text('🎧 Check this out'), findsOneWidget); - }); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'File'); + }); - testWidgets('renders giphy attachment with text', (tester) async { - final message = Message( - text: 'Check this out', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: AttachmentType.giphy, - ), - ], - ); + testWidgets('file attachment with file name shows the file name', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [ + Attachment( + type: AttachmentType.file, + file: AttachmentFile(size: 100, bytes: Uint8List(100), name: 'report.pdf'), + ), + ], + ); - await pumpMessagePreview(tester, message); + await pumpMessagePreview(tester, message); - expect(find.text('/giphy Check this out'), findsOneWidget); - }); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'report.pdf'); + }); - testWidgets('renders voice recording attachment', (tester) async { - final message = Message( - text: '', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: AttachmentType.voiceRecording, - ), - ], - ); + testWidgets('audio attachment shows microphone icon and "Audio" label', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: AttachmentType.audio)], + ); - await pumpMessagePreview(tester, message); + await pumpMessagePreview(tester, message); - expect(find.text('🎤 Voice Recording'), findsOneWidget); - }); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Audio'); + }); - testWidgets('renders unknown attachment type with text', (tester) async { - final message = Message( - text: 'Some text', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: 'unknown', - ), - ], - ); + testWidgets('audio attachment with caption shows icon and caption', (tester) async { + final message = Message( + text: 'New podcast episode', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: AttachmentType.audio)], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'New podcast episode'); + }); + + testWidgets('voice recording shows microphone icon with duration', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: AttachmentType.voiceRecording)], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), contains('Voice Recording')); + expect(_extractText(tester), contains('00:00')); + }); + + testWidgets('voice recording with duration shows formatted time', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [ + Attachment( + type: AttachmentType.voiceRecording, + extraData: const {'duration': 125}, + ), + ], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), contains('Voice Recording')); + expect(_extractText(tester), contains('02:05')); + }); + + testWidgets('giphy attachment shows /giphy text prefix (no icon)', (tester) async { + final message = Message( + text: 'funny cat', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: AttachmentType.giphy)], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), isEmpty); + expect(_extractText(tester), contains('/giphy')); + expect(_extractText(tester), contains('funny cat')); + }); + + testWidgets('unknown attachment type shows file icon with text', (tester) async { + final message = Message( + text: 'Some text', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: 'custom_type')], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Some text'); + }); + + testWidgets('unknown attachment type without text shows file icon only', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: 'custom_type')], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), isEmpty); + }); + }); + + group('Multiple same-type attachments', () { + testWidgets('multiple images show camera icon and photo count', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [ + Attachment(type: AttachmentType.image), + Attachment(type: AttachmentType.image), + Attachment(type: AttachmentType.image), + ], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), '3 photos'); + }); + + testWidgets('multiple videos show video icon and video count', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [ + Attachment(type: AttachmentType.video), + Attachment(type: AttachmentType.video), + ], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), '2 videos'); + }); + + testWidgets('multiple files show file icon and file count', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [ + Attachment(type: AttachmentType.file), + Attachment(type: AttachmentType.file), + Attachment(type: AttachmentType.file), + Attachment(type: AttachmentType.file), + ], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), '4 files'); + }); + + testWidgets('multiple images with caption show icon and caption text', (tester) async { + final message = Message( + text: 'Vacation photos', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [ + Attachment(type: AttachmentType.image), + Attachment(type: AttachmentType.image), + ], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Vacation photos'); + }); + }); + + group('Mixed-type attachments', () { + testWidgets('mixed types show generic file icon and total count', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [ + Attachment(type: AttachmentType.image), + Attachment(type: AttachmentType.video), + ], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), '2 files'); + }); + + testWidgets('three mixed types show count of all', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [ + Attachment(type: AttachmentType.image), + Attachment(type: AttachmentType.video), + Attachment(type: AttachmentType.file), + ], + ); - await pumpMessagePreview(tester, message); + await pumpMessagePreview(tester, message); - expect(find.text('Some text'), findsOneWidget); - }); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), '3 files'); }); - group('Poll Tests', () { - testWidgets('renders poll with latest voter (current user)', (tester) async { - final voterPoll = Poll( + testWidgets('mixed types with caption show generic icon and caption', (tester) async { + final message = Message( + text: 'Mixed media', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [ + Attachment(type: AttachmentType.image), + Attachment(type: AttachmentType.file), + ], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Mixed media'); + }); + }); + + group('Polls', () { + testWidgets('poll shows chart icon and poll name', (tester) async { + final message = Message( + user: User(id: 'other-user-id', name: 'Poll Creator'), + poll: Poll( name: 'Favorite Color?', options: const [ PollOption(id: 'option-1', text: 'Red'), PollOption(id: 'option-2', text: 'Blue'), ], - latestVotesByOption: { - 'option-1': [ - PollVote( - user: currentUser, - optionId: 'option-1', - ), - ], - }, - ); - - final message = Message( - user: User(id: 'other-user-id', name: 'Poll Creator'), - poll: voterPoll, - ); + ), + ); - await pumpMessagePreview(tester, message); + await pumpMessagePreview(tester, message); - expect(find.text('📊 You voted: "Favorite Color?"'), findsOneWidget); - }); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Favorite Color?'); + }); - testWidgets('renders poll with latest voter (another user)', (tester) async { - final voter = User(id: 'voter-id', name: 'Voter'); - final voterPoll = Poll( - name: 'Favorite Color?', + testWidgets('poll with empty name shows chart icon only', (tester) async { + final message = Message( + user: User(id: 'other-user-id', name: 'Message Sender'), + poll: Poll( + name: ' ', options: const [ PollOption(id: 'option-1', text: 'Red'), PollOption(id: 'option-2', text: 'Blue'), ], - latestVotesByOption: { - 'option-1': [ - PollVote( - user: voter, - optionId: 'option-1', - ), - ], - }, - ); - - final message = Message( - user: User(id: 'other-user-id', name: 'Poll Creator'), - poll: voterPoll, - ); - - await pumpMessagePreview(tester, message); - - expect(find.text('📊 Voter voted: "Favorite Color?"'), findsOneWidget); - }); - - testWidgets('renders poll with creator (current user)', (tester) async { - final message = Message( - user: User(id: 'other-user-id', name: 'Message Sender'), - poll: Poll( - name: 'Favorite Color?', - options: const [ - PollOption(id: 'option-1', text: 'Red'), - PollOption(id: 'option-2', text: 'Blue'), - ], - createdBy: currentUser, - ), - ); - - await pumpMessagePreview(tester, message); - - expect(find.text('📊 You created: "Favorite Color?"'), findsOneWidget); - }); - - testWidgets('renders poll with creator (another user)', (tester) async { - final creator = User(id: 'creator-id', name: 'Alex'); - final message = Message( - user: User(id: 'other-user-id', name: 'Message Sender'), - poll: Poll( - name: 'Favorite Color?', - options: const [ - PollOption(id: 'option-1', text: 'Red'), - PollOption(id: 'option-2', text: 'Blue'), - ], - createdBy: creator, - ), - ); - - await pumpMessagePreview(tester, message); - - expect(find.text('📊 Alex created: "Favorite Color?"'), findsOneWidget); - }); - - testWidgets('renders poll with only name', (tester) async { - final message = Message( - user: User(id: 'other-user-id', name: 'Message Sender'), - poll: Poll( - name: 'Favorite Color?', - options: const [ - PollOption(id: 'option-1', text: 'Red'), - PollOption(id: 'option-2', text: 'Blue'), - ], - ), - ); - - await pumpMessagePreview(tester, message); - - expect(find.text('📊 Favorite Color?'), findsOneWidget); - }); - - testWidgets('renders poll with empty name', (tester) async { - final message = Message( - user: User(id: 'other-user-id', name: 'Message Sender'), - poll: Poll( - name: ' ', - options: const [ - PollOption(id: 'option-1', text: 'Red'), - PollOption(id: 'option-2', text: 'Blue'), - ], - ), - ); + ), + ); - await pumpMessagePreview(tester, message); + await pumpMessagePreview(tester, message); - expect(find.text('📊'), findsOneWidget); - }); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), isEmpty); }); - testWidgets('renders location attachment', (tester) async { + testWidgets('poll in group channel doesnt includes sender prefix', (tester) async { + final channel = ChannelModel( + id: 'test-channel', + type: 'messaging', + memberCount: 3, + ); + + final message = Message( + user: User(id: 'test-user-id', name: 'Test User'), + poll: Poll( + name: 'Lunch spot?', + options: const [ + PollOption(id: 'option-1', text: 'Pizza'), + PollOption(id: 'option-2', text: 'Sushi'), + ], + ), + ); + + await pumpMessagePreview(tester, message, channel: channel); + + expect(_findIcons(tester), hasLength(1)); + + final text = _extractText(tester); + expect(text, isNot(contains('You: '))); + expect(text, contains('Lunch spot?')); + }); + }); + + group('Locations', () { + testWidgets('static location shows map pin icon and location label', (tester) async { final message = Message( text: '', user: User(id: 'other-user-id', name: 'Other User'), @@ -432,10 +587,12 @@ void main() { await pumpMessagePreview(tester, message); - expect(find.text('📍 Location'), findsOneWidget); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), contains('Location')); + expect(_extractText(tester), isNot(contains('Live'))); }); - testWidgets('renders live location attachment', (tester) async { + testWidgets('live location shows map pin icon and live location label', (tester) async { final message = Message( text: '', user: User(id: 'other-user-id', name: 'Other User'), @@ -448,133 +605,205 @@ void main() { await pumpMessagePreview(tester, message); - expect(find.text('📍 Live Location'), findsOneWidget); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), contains('Live Location')); }); - testWidgets('supports different language for translation', (tester) async { + testWidgets('location with caption shows map pin icon and caption text', (tester) async { final message = Message( - text: 'Hello, world!', + text: 'Meet me here', user: User(id: 'other-user-id', name: 'Other User'), - i18n: const { - 'fr_text': 'Bonjour, monde!', - }, + sharedLocation: Location( + latitude: 37.7749, + longitude: -122.4194, + ), ); - await pumpMessagePreview(tester, message, language: 'fr'); + await pumpMessagePreview(tester, message); - expect(find.text('Bonjour, monde!'), findsOneWidget); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Meet me here'); }); + }); - group('Channel-specific behaviors', () { - testWidgets( - 'prepends "You:" for current user\'s messages in group channels', - (tester) async { - final channel = ChannelModel( - id: 'test-channel', - type: 'messaging', - memberCount: 3, - ); + group('Channel context', () { + testWidgets('group channel prepends bold "You:" for current user', (tester) async { + final channel = ChannelModel( + id: 'test-channel', + type: 'messaging', + memberCount: 3, + ); - final message = Message( - text: 'Hello everyone', - user: User(id: 'test-user-id', name: 'Test User'), // Current user - ); + final message = Message( + text: 'Hello everyone', + user: User(id: 'test-user-id', name: 'Test User'), + ); - await pumpMessagePreview(tester, message, channel: channel); + await pumpMessagePreview(tester, message, channel: channel); - expect(find.text('You: Hello everyone'), findsOneWidget); - }, + expect(find.text('You: Hello everyone'), findsOneWidget); + + final span = _getPreviewSpan(tester); + final youSpan = _findTextSpans(span).firstWhere((s) => s.text == 'You: '); + expect(youSpan.style?.fontWeight, FontWeight.bold); + }); + + testWidgets('group channel prepends bold first name for other users', (tester) async { + final channel = ChannelModel( + id: 'test-channel', + type: 'messaging', + memberCount: 3, + ); + + final message = Message( + text: 'Hello everyone', + user: User(id: 'other-user-id', name: 'Jane Doe'), + ); + + await pumpMessagePreview(tester, message, channel: channel); + + expect(find.text('Jane: Hello everyone'), findsOneWidget); + + final span = _getPreviewSpan(tester); + final nameSpan = _findTextSpans(span).firstWhere((s) => s.text == 'Jane: '); + expect(nameSpan.style?.fontWeight, FontWeight.bold); + }); + + testWidgets('group channel skips prefix when message has no user', (tester) async { + final channel = ChannelModel( + id: 'test-channel', + type: 'messaging', + memberCount: 3, + ); + + final message = Message(text: 'Hello'); + + await pumpMessagePreview(tester, message, channel: channel); + + expect(find.text('Hello'), findsOneWidget); + }); + + testWidgets('1:1 channel does not prepend author name', (tester) async { + final channel = ChannelModel( + id: 'test-channel', + type: 'messaging', + memberCount: 2, + ); + + final message = Message( + text: 'Hello there', + user: User(id: 'other-user-id', name: 'Jane Doe'), ); - testWidgets( - 'prepends author name for other messages in group channels', - (tester) async { - final channel = ChannelModel( - id: 'test-channel', - type: 'messaging', - memberCount: 3, - ); + await pumpMessagePreview(tester, message, channel: channel); - final message = Message( - text: 'Hello everyone', - user: User(id: 'other-user-id', name: 'Jane Doe'), - ); + expect(find.text('Hello there'), findsOneWidget); + }); + + testWidgets('no channel does not prepend author name', (tester) async { + final message = Message( + text: 'Hello there', + user: User(id: 'other-user-id', name: 'Jane Doe'), + ); + + await pumpMessagePreview(tester, message); + + expect(find.text('Hello there'), findsOneWidget); + }); + + testWidgets('group channel with attachment includes sender prefix', (tester) async { + final channel = ChannelModel( + id: 'test-channel', + type: 'messaging', + memberCount: 3, + ); + + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Jane Doe'), + attachments: [Attachment(type: AttachmentType.image)], + ); - await pumpMessagePreview(tester, message, channel: channel); + await pumpMessagePreview(tester, message, channel: channel); - expect(find.text('Jane Doe: Hello everyone'), findsOneWidget); + final text = _extractText(tester); + expect(text, contains('Jane: ')); + expect(text, contains('Photo')); + }); + }); + + group('Translations', () { + testWidgets('uses explicit language parameter for translation', (tester) async { + final message = Message( + text: 'Hello, world!', + user: User(id: 'other-user-id', name: 'Other User'), + i18n: const { + 'fr_text': 'Bonjour, monde!', }, ); - testWidgets( - 'does not prepend author name in 1:1 channels', - (tester) async { - final channel = ChannelModel( - id: 'test-channel', - type: 'messaging', - memberCount: 2, - ); + await pumpMessagePreview(tester, message, language: 'fr'); + + expect(find.text('Bonjour, monde!'), findsOneWidget); + }); - final message = Message( - text: 'Hello there', - user: User(id: 'other-user-id', name: 'Jane Doe'), - ); + testWidgets('falls back to user language when no explicit language', (tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final currentUser = OwnUser( + id: 'test-user-id', + name: 'Test User', + language: 'es', + ); - await pumpMessagePreview(tester, message, channel: channel); + when(() => client.state).thenReturn(clientState); + when(() => clientState.currentUser).thenReturn(currentUser); - expect(find.text('Hello there'), findsOneWidget); + final message = Message( + text: 'Hello, world!', + user: User(id: 'other-user-id', name: 'Other User'), + i18n: const { + 'es_text': 'Hola, mundo!', }, ); - testWidgets( - 'falls back to user language for translation when available', - (tester) async { - final client = MockClient(); - final clientState = MockClientState(); - final currentUser = OwnUser( - id: 'test-user-id', - name: 'Test User', - language: 'es', // Spanish language - ); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(currentUser); - - final message = Message( - text: 'Hello, world!', - user: User(id: 'other-user-id', name: 'Other User'), - i18n: const { - 'es_text': 'Hola, mundo!', - }, - ); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: StreamChat( - client: client, - streamChatThemeData: StreamChatThemeData.light(), - child: Center( - child: StreamMessagePreviewText( - message: message, - ), - ), - ), + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: StreamChat( + client: client, + streamChatThemeData: StreamChatThemeData.light(), + child: Center( + child: StreamMessagePreviewText(message: message), ), ), - ); - await tester.pump(); + ), + ), + ); + await tester.pump(); - expect(find.text('Hola, mundo!'), findsOneWidget); + expect(find.text('Hola, mundo!'), findsOneWidget); + }); + + testWidgets('falls back to original text when translation missing', (tester) async { + final message = Message( + text: 'Hello, world!', + user: User(id: 'other-user-id', name: 'Other User'), + i18n: const { + 'fr_text': 'Bonjour, monde!', }, ); + + await pumpMessagePreview(tester, message, language: 'de'); + + expect(find.text('Hello, world!'), findsOneWidget); }); }); group('Custom MessagePreviewFormatter', () { const customFormatter = _CustomMessagePreviewFormatter(); - testWidgets('can override formatCurrentUserMessage', (tester) async { + testWidgets('can remove current user prefix via formatGroupMessage', (tester) async { final channel = ChannelModel( id: 'test-channel', type: 'messaging', @@ -583,7 +812,7 @@ void main() { final message = Message( text: 'Hello everyone', - user: User(id: 'test-user-id', name: 'Test User'), // Current user + user: User(id: 'test-user-id', name: 'Test User'), ); await pumpMessagePreview( @@ -595,12 +824,11 @@ void main() { ), ); - // Custom formatter removes "You:" prefix expect(find.text('Hello everyone'), findsOneWidget); expect(find.text('You: Hello everyone'), findsNothing); }); - testWidgets('can override formatGroupMessage', (tester) async { + testWidgets('can customize group message prefix via formatGroupMessage', (tester) async { final channel = ChannelModel( id: 'test-channel', type: 'messaging', @@ -621,11 +849,10 @@ void main() { ), ); - // Custom formatter uses "says:" instead of ":" expect(find.text('John Doe says: Hello'), findsOneWidget); }); - testWidgets('can override formatPollMessage', (tester) async { + testWidgets('can customize poll formatting via formatPollMessage', (tester) async { final message = Message( user: User(id: 'other-user-id', name: 'Message Sender'), poll: Poll( @@ -645,11 +872,11 @@ void main() { ), ); - // Custom formatter uses different format expect(find.text('📊 Poll: Favorite Color?'), findsOneWidget); + expect(_findIcons(tester), isEmpty); }); - testWidgets('can override formatLocationMessage', (tester) async { + testWidgets('can customize location formatting via formatLocationMessage', (tester) async { final message = Message( user: User(id: 'other-user-id', name: 'Message Sender'), sharedLocation: Location( @@ -666,11 +893,11 @@ void main() { ), ); - // Custom formatter uses different format expect(find.text('🗺️ -> Location Shared'), findsOneWidget); + expect(_findIcons(tester), isEmpty); }); - testWidgets('can override formatMessageAttachments', (tester) async { + testWidgets('can handle custom attachment types via formatMessageAttachments', (tester) async { final message = Message( text: '', user: User(id: 'user-id'), @@ -690,11 +917,29 @@ void main() { ), ); - // Custom formatter handles custom attachment type expect(find.text('🛍️ iPhone'), findsOneWidget); }); - testWidgets('can override formatDirectMessage', (tester) async { + testWidgets('custom formatter falls through to default for known types', (tester) async { + final message = Message( + text: '', + user: User(id: 'user-id'), + attachments: [Attachment(type: AttachmentType.image)], + ); + + await pumpMessagePreview( + tester, + message, + configData: StreamChatConfigurationData( + messagePreviewFormatter: customFormatter, + ), + ); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Photo'); + }); + + testWidgets('can customize direct message via formatMessage override', (tester) async { final channel = ChannelModel( id: 'test-channel', type: 'messaging', @@ -715,72 +960,154 @@ void main() { ), ); - // Custom formatter adds emoji prefix expect(find.text('💬 Hey there'), findsOneWidget); }); }); } -// Custom formatter for testing overrides +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +/// Extracts concatenated text from all [TextSpan]s in the preview, ignoring +/// [WidgetSpan]s (icons, spacers). +String _extractText(WidgetTester tester) { + final span = _getPreviewSpan(tester); + return _spanText(span); +} + +String _spanText(InlineSpan span) { + if (span is TextSpan) { + final buffer = StringBuffer(span.text ?? ''); + for (final child in span.children ?? []) { + buffer.write(_spanText(child)); + } + return buffer.toString(); + } + return ''; +} + +/// Returns the root [TextSpan] rendered by the [StreamMessagePreviewText]. +TextSpan _getPreviewSpan(WidgetTester tester) { + final text = tester.widget( + find.descendant( + of: find.byType(StreamMessagePreviewText), + matching: find.byType(Text), + ), + ); + return text.textSpan! as TextSpan; +} + +/// Recursively collects all leaf [TextSpan]s that have non-null [text]. +List _findTextSpans(InlineSpan span) { + final result = []; + if (span is TextSpan) { + if (span.text != null) result.add(span); + for (final child in span.children ?? []) { + result.addAll(_findTextSpans(child)); + } + } + return result; +} + +/// Finds all [Icon] widgets rendered inside the [StreamMessagePreviewText]. +List _findIcons(WidgetTester tester) { + return tester + .widgetList( + find.descendant( + of: find.byType(StreamMessagePreviewText), + matching: find.byType(Icon), + ), + ) + .toList(); +} + +// --------------------------------------------------------------------------- +// Custom formatter for override tests +// --------------------------------------------------------------------------- + class _CustomMessagePreviewFormatter extends StreamMessagePreviewFormatter { const _CustomMessagePreviewFormatter(); @override - String formatCurrentUserMessage(BuildContext context, String messageText) { - // Remove "You:" prefix - return messageText; + TextSpan formatMessage( + BuildContext context, + Message message, { + bool showCaption = true, + ChannelModel? channel, + User? currentUser, + TextStyle? textStyle, + }) { + if (channel != null && channel.memberCount <= 2) { + final text = message.text ?? ''; + return TextSpan(text: '💬 $text', style: textStyle); + } + return super.formatMessage( + context, + message, + showCaption: showCaption, + channel: channel, + currentUser: currentUser, + textStyle: textStyle, + ); } @override - String formatGroupMessage( + TextSpan? formatGroupMessage( BuildContext context, User? messageAuthor, - String messageText, + User? currentUser, ) { + if (messageAuthor?.id == currentUser?.id) return null; + final authorName = messageAuthor?.name; - if (authorName == null || authorName.isEmpty) return messageText; + if (authorName == null || authorName.isEmpty) return null; - // Use "says:" instead of ":" - return '$authorName says: $messageText'; + return TextSpan(text: '$authorName says: '); } @override - String formatPollMessage( + TextSpan formatPollMessage( BuildContext context, Poll poll, User? currentUser, ) { - // Simple format with "Poll:" prefix - return poll.name.isEmpty ? '📊 Poll' : '📊 Poll: ${poll.name}'; - } - - @override - String formatLocationMessage(BuildContext context, Location location) { - // Simple format with custom emoji - return '🗺️ -> Location Shared'; + return TextSpan( + text: poll.name.trim().isEmpty ? '📊 Poll' : '📊 Poll: ${poll.name}', + ); } @override - String formatDirectMessage(BuildContext context, String messageText) { - // Add emoji prefix - return '💬 $messageText'; + TextSpan formatLocationMessage( + BuildContext context, + Message message, + Location location, { + bool showCaption = true, + }) { + return const TextSpan(text: '🗺️ -> Location Shared'); } @override - String? formatMessageAttachments( + TextSpan? formatMessageAttachments( BuildContext context, String? messageText, - Iterable attachments, - ) { + Iterable attachments, { + List mentionedUsers = const [], + bool showCaption = true, + }) { final attachment = attachments.firstOrNull; - // Handle custom product attachment type if (attachment?.type == 'product') { final title = attachment?.extraData['title'] as String?; - return '🛍️ ${title ?? "Product"}'; + return TextSpan(text: '🛍️ ${title ?? "Product"}'); } - // Fallback to default implementation - return super.formatMessageAttachments(context, messageText, attachments); + return super.formatMessageAttachments( + context, + messageText, + attachments, + mentionedUsers: mentionedUsers, + showCaption: showCaption, + ); } } diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_dark.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_dark.png index 771ea6e712365d24c446b55e6974899eac081b39..c244bcc2ea2f7b8f5f74fd27b9cbe12a1e318f7d 100644 GIT binary patch delta 1998 zcmX|>Yc!kL8pmHXJ)+vu8A^wd7_Fx0YP(8B-PydG>$*oE+4a?4K@@lBVvKrz#=~&iLk7j@K<^Vca z@6%}SK7M@6v*LpcXKZY?8?5|^&1D`cJcx@4HGEomD0@b6ta~H!azbudZ;SQnq+NV z|2$0+6B~P3Lqj7lgPGCGyjWLTQnC-+Tz@h@8* zHud+PZ$}>o8#^VEBM1_7<@?y!*h_CWdD0spgDWe{b$cM{qKHiar9D2gSr2UD7hh42 zcDRj;xshfHe!?9^Kw%SddFK0h?}=NPnU7^M8G9KBV)@|Sy(_w|9o%PEbe&v*oWv2CjraZ^&b_pXCYvqPF#&u40R%Jukk;1sZaDz!>@wsq=#o5`1@pfuz(CZ=N zQUvT;4&;2OeG2F4<)u3J^Yc54LM=>rsbpXFDznFkXIXsR!CuN!~!=~D+7%Tc1Rtb&qh4mnrHv^TRj3bb@ZXeS+&_^jJ| zcg6{MX(D0+mX)?bxJCl4dn7-{IN;YWz%y5KmVMf~yPMr8#ux+b13D1$?v7Mq0oz4~ zM_NDdl(AW%S+co!k()DAa{*8_IG9&iS?N#bU(8YfyWlUSFjy?kT7;EqoZc>NwXxaE zJ9qQ|6vZEV+R>gianofc$KCtt-i@kNEQ$7vnAKTvp$E{#FId)2I>|T7UM4)! z?Qf;fJ32ZxH^!VS$PsmTKzH%axw*TE@-;3RjrMJL>+@XrjvmOWI9$N9x~l7%hzVp% zUQ3C1S_7K#2)q}thUm1kK#pRYKDqp<{%-`pZSelcqZyKt$d9q!?NWn)jb{B~Uidwr z8b#g$2DP9w(RzJyX2y833sBz&ljf9-z_k?Zxvo>EA!twsAPOhF>~w&ju#;r*Zz;V_ z%A{Eo(}-Jkcn;6BAFGJ$YO%apS$|jYWwS_iJ9Q8xRmam#2zo#F*9lA#=J4gZ?&U3( ze)r8Tm8UnJGC@FBek6;LuyeAku*|)Jl)3KO+X(;imxbaRNE6lRxqcWIpXpR8(KxU9 zs(x1@>B;u1$W*7(96SlhRm)cDlS;Ten+NA@S7titylRuOhsec4@FJDB{3J#-`!^?I zBtv6!J2@eOt1N*vx?t5B3n9yV2E-Lh8Q?f-XFu^WTMz2z(x{UKg0>!fyW; Dfd}rv delta 2002 zcmX|>d010d8paQl76cV^SSo}?5Jl87mK_O;g`ojaB!HSCh&~ocSd6j;NJK@)WfEBg z!757x*`Y*&Y_X1*2(%MWAVP>iAcn;d*%Fcvn3M5&=05kie|+cM_kQ=hzxTUmKry)I zjdMl$c!#9^CgKGomN8ib>vi|*uU+h zSCv{ChGItV;Y?ef;LoyG_WPG#p1n>kaUn9F@+sZFelC&)98k_Kq%z4*!U5R)rUePW z8TV~D8zwpZ{yF`dNUL;B03JLnDjJxWDE~D&+7XGY@99AW9zE)xOQukgGBWC@RI9EG zS+GK(aC37r9&Iy4$@ighqW zoSj$R9rN7M=?Rs$>%0n*&hy^&^xS%kOm|wI>}aSpagMJFuAaVWP7Fe@vZdA*ASf73 zLZjPB8w@&~o_O)%#R_2!XAbLNjERioEG*#5GPSfoe*fTLK4V>uLlW2CuNuED#kLfO zg@rw%)5E4mwWV3Wt7nzbsE~>&4raNDsi{?ajy<0K(3QTZY~>5Qy+ElCf;GVFNNJ(b z?BAyT=H~7mQakh(7WncAb#)T3zBD&{pdyAalkV`txs;R>X^(M9Nh=9_Kltjd3_eH1 zZCzQOL363oJ$ZZNtCJK1jhETtkCtvAzx&all&SUxiA16;rnR-X_PfT9JwLwM^{;>Y zgvyZ;*@IM0`=Z+{#1>py!Oy0v zQt0uhFsEtn)@43M3w&>KZH*~w`dvLwPs2FxqV~6X&vhX!{hvO4s*sp7l~#2*jQymO z1(ntAY#Nf8?aqb@UloT%$p#u(s&;Oawe#Y?c9Ya`Iy>q@`dyvusH(iYJcbCew|`%{ z*Hw!%->QT2gYm77uC6g~JuonEW_-N#Yrl{q2WRJKSU%m697tMQ@cXJ}YG&4pM}(4U zL%G!Dq6ZIxiYsRm-TrrMHd)OLhhdk`T3V9kaquht;{#Mb!iG{BM_KR;=Y&!R`k73o ztdWvo3A{>R7)75y=i6FBlet_jOeA#6FW6(T8i352p&EwllL<2Q%lO!#7k)iU?!30k zetgnK7oqDzn>dODTelt%C98nST>W^H?l&+yTVv))%!qRLfxLyAd1O>sXw?z(hhm)s z3IjlniuB)q?9{0RaKf9c(LCHFa`#9WSoDUH)^!T(2}!#;{{A!Tds# zpH^4z70u7jw(^PS4jy9=p&CvnX(NN0<{71!`brBeeT|nR6twU0CoW_I*6-_UOULx$ z;^VRD>FF1QyDb2)8q>_%scEct{R#kgS2>1;acls-{|SO>LE=jQxbo74Y{gkwm$P#w zpI<`w<>m|97TwVWg5KtkQw0%n={vi_s*`}b__|YRjBx3ME+yh-_kAHM*aFqtd?cpW z1~pN}g!*2$T**`mErB9y;b1uD{@e}it3jyk`^eO?>#EgFTdWYm1*I3me6#hgLhUy% zNwZH2N0fiAF<+G-G{Jgg=MG9mg7b|_V_o8~iWi41QO5`EJq-6jySB0LA=M#*_7C?< z`lR2qHPl8l;+}2)i0cGFmekvg0|X_oHnMPyb%GxzpS{O6_@m z!o~vouvjtA-)LVv#T=P=9rsYkZYCW;Z8zcz{r4DWm6Ouq2q!jJJ8pWKbV?f5O%Ld> z@OQ(&E5jQG6*0l91TCm@<-E7vsM*IqEocy-rZ@QTTJzIgJ<@A5R(atpxI1Dh5Dw*v7$IqWK1zUM1np)G$3d?A17TqJIz=e22dBe3mr8xQ!Y zF8Pz-4;P)QU;fRvij*gxvk|^m#OD=lda8CJ&s+PDezxUy&GXDh(Ewg3-=jVahtFL7 EFExeqrT_o{ diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_dark.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_dark.png index 07e0c32c714c9e1c90837b75427fc676a309fd11..c3f7476ee652bab3beefc9ed1ac9c77cb3ee543e 100644 GIT binary patch delta 3180 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 delta 3187 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 VbDjPZYby{iAkJ>*v5vua{u2&JH6{Q6 literal 4056 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 literal 4238 zcmbtX3pA8#zkih?744GRVx}~eCR9SmEeVrqOPF$Pr<9qD+?l~BshvX6nC&`32{F0M zXk11mhNdCcVHlTWYcTHPHZI@eoVCt6-#KS}`&;Y0>s{|V^SuA(|NJk%|Lw8$MT@O6 ziZT!cZADp{*+7uQGw?fIdLwwM6zR8tm!z*L%2pa&A<{Sg0RJU?Z7fWof(9i41Z~qr znVq%`N)hs%@3;lJ)J_h~FDsq8nDV)BpyIqsS3y+Ci8eo)U#k#v&-2x}4F%z+B;`*( zPpJI;>ezb;+_4kvSv$s9~zAUZiY*}>VlZES3eWLR2L zFE7z2*dA@Jt*@V(mIh<+ah^CFGQR$ewW=zmG0$4nGHxlXYTdnX@HBn-jMvp=mt$K} zR+gKSb2vXg-x;GMDGeDM;PH4f3ynkE5&((p9WO6zXsC1G zSht?9%i8Co} z8Xci>3Bv|TW>Vn_-Xrbm3@45Jw{KI#$8&6lO&gCNJ9c+0cwqAVm%$>S26rNC)&xr5 z@W6DRXl@Sc?@#MIqgPT?l(Dk1QtgXPH4BGKHt&YRjSXhMA#>Yu?a^q_*AtCe6_hnI ze&cepqobqUfi11+Nz6x(&Ivms2ExBE#p?uA1@?fR*OiV!2$;z!;{N@KQ@2KrAM}g~(@H#ql zMGqqL_wL@JQ*rPV@b?A!yLai9i00AXQ*w)oFHcWT`?qXb(@ZGzyqeH9!NPOgyXZ(# zlxE_2e(0Ky!tME+ADgK-T)A>P2)>4xzK;=7!+?DDSd2HrDT+0al$3-CS*68FL)N!I zBw1Nmsqa*o403w%yB_Y=qc|rL}m#{tGv2><#JaoLZ zdUh{fS9?%%(k1B)`Zu+TO%T<-Ao`(-cm&2+4{>L``l)f!v0{qX|>Sgg^? z`~a(A&0(7lli6j2xq|D3e}3wppcxYhgz}80H)|MP+8w>->C39}TE~yS?Cg?uk-=3+lo5kZ*#Ft(~An1hrZWyc`V7{Hpr7@Xdg1v=m zR!z5Uot>TW6MgsGZ{0fT5&>k&j^K{8agtAxEDnhnJ zwqH87yLD=+C~UqOj&XAvuo>99#w5WKp{1+)=B}<9mAe*WLEgF73*Xq}WPv+BU;UNM zVRa1+mrmaudm(85lI-8+)KtD92euwC{tT0bK1c&(90O*8o^D&)4}kNpmx2n4Iwl;C zplxpbCF~%zN+_QM=c0%`UHzLuo`8Bg${Z#l?#=t41x9B>~05$HykFwU?XSV`ds@>eABE!sB!5 z27Nq2LY$P9g}mYsO+SDBai^<&YAPzOJ`0gkUhWtX5fPWgVCWf!%v;B; znK8)VwUbUZ$o4mV^@0sx(Xp{dlyH~v`}3vZ<3WnUV6iO|6VH}b!V?sBX;;15iy9UP zn)*J31O5O4q@L~Hz2K*V`Z)$7$*4=N1%b2uH_;L0p35FC)25agVYc zz`!ss$=%%@fqHIfcIG6mP8fjINNnZt9IQmz@-iMbZgjk*#>XS&WiE_kHA&6G!}yBX7QDo1z*5y_-GvkWdYfD6iV<}ER4x8LH z17BV7!V78W!GLn@AUA6x3fj}tGpgo$T$W|l89v{6D#&1XY|Q2sLIoAYx-RpmdJ12u zxceu$aVQ?MC(TppMwi4^*)5jM{RS>nbpPx-P~H)!)YR1B(Wi9-ld8NOb`}z zzRAzO0Juk-+z5_3fI4&L39O+fJc?w zad>7b<8q00-XAJa>iVGQQO`;TJv^q7A7p}Jv)uc@&ZIGpuSXif_Z2$0vEN=j|#7GqD2 zVf*Te2%$X=uIpK1MHyJ0|CP*kwAjow)jTxcP$%emPXtqcYx^U><⋙&5EvUZA8qV zX1B@egmLcEm@sE}H?9=)Ww~vb0cM(m`16c|YwG^Wsl^K5(<$*XYZ1?zU3o(lja1h& zVar|eguUFa-b%|gT|^C3Qc6mz6g(u|Pal|MEjw~J7eeRzA1(ff{|$j6&O9QfTMCI- z=-WBzXV}?duZoxV?uCH{F5@oO%0RKza+|1~!^62@BF;%sDsj;wIwl4Lz5w)TEnpdBGf;GEYinhR z7sSzCD`^Ut>}ycC{U?~^jtcP@0Y5Ml=CXP76PZX;e1GJw86K7@5OH0(!miS`rlyym z>@4$=s|o^p79P2=jEfj42vEvld5N5{-iE>H={}$O5!)JMI4eq|T+6(OSK!HS( z*~pz%{?qQ7iEi4OQjLkH@@F#zq;qF$u3w5YJxw|{SXX4s3|MKsNwh7uMdsM#sFK;* zqa*daPM*+8L~Cyxt>>ACdM+gmUKETprKRw=moe~eBTr{G{OYVlCL2!sV@HSFFEz+agIbf@})r`&juRk6Tr+8nP;;d0iJ$WK_W5LOQ3+ z+|)r=2hCq}bcuq(F1vhtJ2i#DjulIK{f)_LCvu5>YUBEG4qIg8>^g}Oy*WQD(mbF9_s=*q#}Fm>u%r8KR=D{9D}k~Gj&F>XvvlpK#L8iEAsoK*x8X|Bhnu}_o&Uu7@8{ZoZ~FcJQRNkvXY)o+Lr|L0 h|IwiRZO2!)x}Fv|>E--saytYr)Y*$>1*X^T{{!>01V{h? diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_light.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_light.png index ee4d6631778499a22b5c1ab4c669303c31e13d15..f1bad28687f58810d823838fcda0439cdbd3581b 100644 GIT binary patch literal 4149 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 diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_dark.png index 0f80e3136392d21bf628c85878342771cb8e7dc9..c84f986c7614d4cb8a05e2edbf94b2f2850111bf 100644 GIT binary patch delta 1601 zcmV-H2EO^m8_OGzL0ff6L_t(|obBC9ZW~t`hT)$Vk(4MoMj9PQ8TBUYSQ=*)S)g{2 zVPvD>WDPyCAsCGW5@nGRFPU_JKrkBdS0xu|Jrvo~(Rx5e<4H{qk9l4yN(r|NH);=?FY>|M=&# z=yCf8VAHx?%xk?~w>}d*28~xBqw%h*fQ&I_UD@BUtV0FRR81Cs#( z7k{LAKsK#!sSdynXdICB#!q3hyEdCmZTu4ik>&xhmjXL%y;^lW0_?2D0a-1V?Mwn4 zSgkyNA=Nk_x3`OSCV>vzEGXL3Eeti^oZ^%$_TTDcG8_I6yYe>+0|v&Bf!pU zEg-AqvaV-dlx)B2*<~%4cU_MGJFk^%Ab&L)jca=J#5SR9p-o-SX0@2#bUg@s5n2yO zRn=fPtiz+PYA_hIeQ1cR*Q=UcT6sX59wZ=t8c+6X|6p4EzTa%58=LjIZf-u*{66O9 zA)o5-C_QLE{o%KBZPx2ruh+F)F6-`YQFpiQ^MHr) zSpovUm&a#Ik+Bzlkzv~hoKC0p{Q2{mPNy}QO#GY)@VBj2tD4W}HJi=q&6_uMeSO{a zNci6G&ulamli^ZZ=t5q9^2OfdJU{K@nxF(ZHJ$?GLUcGu% zZ{NPPRm7HRB_R9z`}M;QKh%1?uJ`ZX*V5)vKtsdfu)g_!=9{Xj`st^i>gL9;C~uoq zu7Q00_1Cr8Z0g;+cj5uGV!2$_+qZA4{`Vfk_GuiD>2z8zU%sr1i;H$90{DxIi;Frr zIjQ~q{jNvhp2h)r@!~~YU0u1>0X&G+YE@TPSM~k(-*-I<_q4JCna}6#Oat%{H#awR zaB$G|7~IoWI3VNkSU`XWHJ{IGGMRKe2KO`$$Y3yV;T(8S%jL3$!`5$Gqak;q0}v4q z03recKtw(gN1#QMfeA(qsy)#^ zWpDG8J(&U8VFi=n0Ye>;t=bd)?CdOh+^6iNetv!)odam@^z^jr5onPWlaT>L1-5EW z^iPv%2u>HP_C!BBJBuFoDSN4(pPxtP0Gc~JJ?)du2uTI4+7ta#lN#sT@|mtX4W=&1UAms5cD_51yL^5jYV`s=Sv|0-oJm}^*DS)uc|g}9H`&#*YWXjot&K1Z@>Lki^ZZAi$$$gt2Pb~JOabvutuX% zO(v6i`t)hNdiAQ_y?a;d^)tHb(fEkg0`jNnbXw1!KdTNeBQ0nMO{D5iLp00000NkvXXu0mjf^hZON delta 1586 zcmV-22F>})8^#-uL0ND~L_t(|obBDsZX0K|hSAL*k(4MoMj9PQ8TCB~cA{}|+ZqNo z8dk2MM>YhbkwBtMQsPffPJlo#4&t{;F49_SELQCS$wwcH#jjUY+X8^k)bDx#0QQr! zp#ic1v!DS50e{HwOPu83U{nXAQ4NO&)$jMK*Y8)a*ZUHu<_Xzux3%4DYO~qYYPG8S zyJg+q-*r6}e6@O2wSBCEk0*z9cr>kk-$I9OY&V;l&u?onUp(eh9iF5|3CL(Xsl%gb z4F-d@4-JvcdR_C6x3yd@x*i3-8m$LpG@jJ-_{2w4?0=wb&2Db$PDG#;tp;Q?9@p{7 zH*Fmb9>wkKrk0C&*JHrmYwhC1U^uMl@w2uL2ajfYd{V=MgRaMbz1Lbmj!&NX*owVz zeC+uOO|=q`@#L@ugD>)pU}!~uFsSk5unqSu4a#5SjruCWNDQLU`8I8x)bDsx$sMqV&Xguk94A@(Z-}#3Jqjn~N4!D>O zxyAt*^xZ7T{&MXOa*YGhANcr+{pDwNAlEz~Jug7^SFhLebzTTHfA{y|19*}wACn;h z7=O(JvTc1!bpZB2z^QqG!KZq6xd^%^}6d3U~e@J$a=MEXAG=z(#sRs%yR!;mFKyQAy1&2cdJNcGy{c+_1#vVQ*YUG& z+L;KxklWdHEfsI?mjXR~Wxhh%TeZmzo?0rp;N z0a>qBbvyH-WcS_9u4}cr?|KZ_d#zjp`K!@rT+@?hwh3hiZR>V6tL0+e^&s$7XgwfR zRfFNMj!(X+!C=t#p&_!_tZQ~{ zfB^9I@!1lSu>lu$}TCM8cyLVOp_a4LUX&jL0bXq_C^iy43U9~e2z+YTlUDf&d zc^w`ec0CFYG!DqCSFh^k=Ek)S;8CpC>$S(QU@&mu9C%c#)vAWW)^A&*A$Ot!5D^dn zA_4+HL_h$D2nYa?o%&kC#l=PRxSxOi`EUR2<>h5`4xqWSv$K*xdfeyirG9yN8Jz=Y?(FQWlOG641$Jsr^v{!R2u>WQ_C&w9xQHJ2IeV#J zUS3A$0Gc~HJL`G`T4V*0!9xa-o!S%qbCVzmP8X>?(f|FgpQ6XT`p=t3KL^m<=e$RPZ*EKb$ff;^%y+RI3U;8*8&1Os_}SS*VorwkHG_t z19EwJStlna)$hBU0<^E+@7J?u&+50|e(QP^9)D>3uD@I^>&=@t_2R{gb|wP&i|@bx zzJC4n*IF)@U5~;8tt=a^uCA)r>(%M$sSD~rD~7{i{qVyNRaN!j!-uZN;S+jQwQb`- z{eHhrPfzRo{Jehu{r6ffm$h6jYrS5#ad_Yf7!HRu8jWf)nbh;=&+GN;*Y*DW``T!}TmS$707*qoM6N<$g5C@>`v3p{ diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_dark.png index e3bc386511f56402c3a3dec279ec5256267a2be2..8ac58d0272cea13d6c21bbee0bb9a0b8c5e20146 100644 GIT binary patch delta 1080 zcmV-81jqa58}1vBL1CClL_t(|obBD;PFv@m$Kjv79Bkr*kfcpZrO|9@Uqjl$aj&8m z$X)cXq#C8YgOO%yONnTjKP7-|@WOL4mG;qY`Fb(Skmos$f9sWE^WkOLeg{>xDgd~q zVc!b?;C2$Rp|b*$5CH^#8TGvYxXUJ!`!(6#uC1+c4VzIlO;dxxpzmdI$E;SXYTISC zZCe+MMa}2uwV2Ot{JMjxTHW|n;1e@4l;(t_B!?Km71R&1SQ;XSxTnfu1~hQmfUf zUcY`V9zZ9S%VoWK^{VRse#Ue`KsL;DI<0TN{kD#ekNaKzi-BS$md%Y?$eETJ!nMe+@J6>6*=Eb^reTwdV-P zh8d4g#{vS}sQG+elgVW5IRdg_nx=8%9Jo=-<+4Vj^*;tEAOJ)J1b~Qu01y!n03rec zK*YE4-eHG_htcD{|Ni^G{BMtrj-qP->pM6&==J(`lVJfw5OF5@9d@0w%~O4!$qe+z z4w3Oh8_q<(!wwG*qsLw6EcK(Kqv#sI`VI~bdcD3Kld%Cr5S)pAhh67v^HkqwG6Vgw z1Cv1kL>n{F@36ze!{~9>IZOTM=qS1du)c$XgI=$12a^E;L=R`8-(lA|+dS3xnan_c z?2{n^L>l5u^gHao{`PJ3xUc``ryIWpu)gcOR&RnGld%Cr51fg9hy8z?m)!T6%s_wa zlOX~Ve{7iLa#>B&^t}w=4r`jGmdoYZa|C3=oS&c9cs%ZV8NeMj9*^tn>}>5h0twyB)0000w>td* delta 1168 zcmV;B1aJH98|NF4L0pkZL_t(|obBDuZX4Gb#^H|~TBJ-{mhHre0u(LKOAwqy>*Thz z3>*|z?w~-60yY%KeetlnOryp25KK{&~sMt8G)vDh8@^j5* z-9|(}paatfJN5X! z6A)O#U@)ksPoMd-6}Q4*FsLU_KksawfWR6aJbYBm$h+aWJ?`Dxs_BEBPG))LLufc` zYG-FRe*C`-{`vP#*TbjhPk;VvvttAt0e?StHcvocO&5!KeR%)2?`6R6diVC{y1cyT zY~HA|N$?3fJ3XnYs@=y=omR-kS*=#}_Lt+D&$@l*pquE>p{?etlnOryp@lI^5jXaR;zmb`n7lfomeiH_3G8D zs{j0q>4Jc4nCWy{-+udT9UmX}y%gXMIzB$GgM)+G-rin&mVj)SufP7fK7Rb@UI%a^ z+P1BaA3xSN-+Z(7ECFlTFw^O@=JT8X8fM_rHJi=q{{8!F&k>LfGaiox1h`T2`Mf5R z$=Y)SWWzK~_3e|90YnetO!PbKI%k`w`aY8x=#P`I2}u_n9v()IyUtna zM@L7|HGuW)@9+0|eLIuk0Ynfu6a5am&e`UvzRzR^`eO%^aREddXQJO>hlhvJuUK;Q#0ogF;=jSyZkNaK*aEFb@<2pM#TYHXxY?zai69EBk)Ob9ularIR=LpD# zIXXJ3hYugtaOl4V=-zNRteu^m`st^i)}PhMis;N@v8W$^{IS0H;)}kQ0^C7ge)(nn z@WT(aSS;3_B_JDfqp@lg0`^84qjQw&wGBot~c7%a<=ZdFR!C6A%C*?t!zx i0hj@^-~l257Wp?dORY?D;NzD70000}c@- diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_dark.png index 35917f7482842fd95ace2197176733dcf057505f..564440b438f8f27227ca09cd42d7a3fb2b6b88c6 100644 GIT binary patch literal 6068 zcmeI0c~H~YmdC@QM6^{D0ofaDL7-V=RaTLur4}wJNWPt#AH(fLJ>dkb$s#i5rHC5C3Bl+E5{c?Zzp7TAQ zbIwh^YHR(Cu%s{q0{Q0B4;Bs(2ww&S!tV^-5AIxEtx5tvg5l!7Gj+4G<{r)nF@7POX@DsMYYu#P3OHZcV0$(yN{92r?c^f)Ebv3&ShUS3*qa&l6Va+Zy~N><@bb?ah>>5~MFte8!F&q{$X_Q1F$@O0#BS!T0 zKfK_mlC_{7HL!h{6Jvqz>$CmhUt8K5H=t*IYavv+F1@`@Mx?r_NosC=2v;r}iSxZr zdmbvfg@_n68KguWksb;$NOLIL{Gu1r;s*Y@7;zpfiismEMHD)eSzw9CV60TF%lD7l zJvKSJqmz@!sgQcaO2ql<+S(HjKgc`XxZ!p8uI~B#z6V^NnX4&DQ}se0#;#%rGu>IL z^z9W)SHeN@LNd)7LrAqPJ>JyR#5s~aIXRgmCmW3-F!ZEQhymNJkD|H7a(7wu$tZFM*zpHMkJrww z;3NB%z3mlxk=(9wNolE55uvKKytp#+~WK)#g&%69>;k85xdl(aUg1g7Ubs38q z)cDtq@7=o!$Fip;ezAsi8E+#tdi7`Q5Zl)(-n7E_=1wvO@pxP{`T^0p!}z(ZQaP<2thqzuWi7S0&4aUW49M z1L6#X!BB96kP;vrXJ_XmBoZ0TUYmMi7xR6Vjh4Q?c%_pnb)q5srQ$Cj23`?X=mgwe z?ZDH3TL)8pV`h;0g=2v!LZX4e!RqSntpC+^m?s6Pj*jaJAM2N#3PK_1|A!M(m-ucME6OFz&9t;v|`!1`{nxr^%IwnlZdXYguMObHVJa&vTij4%V`CRJyx-Q78wt%~)6hqyYHDhD zCX=~$J^aBSFry!qm>_%4=nidBd}zuqq5N%L{I~{A!^x+z<)@A@cEcB!0?zw_j&Mz$V()jj`^j2<(@oSk1k!oMJtofDZ?pd46g*9Xp9p#(SYH`D498ZfM zlFG0tcKDd<4W_QZU{KzPi8YI04glEs`FY;NvfJnx^rP$39WDHu`TT-jX^q+2{%tVn?-;22tNSn!ZtQahKGl11j500bz|f9 z#9aE@&l^xEYKg@G<`sGR^y!SOaNdCN>P|8SuF{E~8;{k+T_ERjyZlEvN{^G%(>!pP}>D|2rr1yXpcDlaeBGBrhb z5}%>bsI084izqb^gTQ|E;S09k5_Oc$+l>JW)Un|4Rj(5Swk zg5h9nksHR8m4AI`Xo%jcPZo`AP{4VHJ;$_etBh0Xo(jJ((}$7LmX?;xQ5L|BUjF_n z!IO=57sH1AbxTT0rdKdH^*~~AQplXq;fSsEIW`@I*fF0Edwk>5>T2NU&!6%6hEczZ zy9Vk=R##OWRaI5Bjw!kA;qh%NdpgzG#l$mr6bA6eN*{yb)JezMoN> z2((LAOG^aD?$Ge`_MWpiocv($olE7joSbAk)7?!xc}GEB+7+ynMS^Rn-)a=?>FELF z>NGwTJ*S)7%X58kaWOB+9BddF2D-abQ&SVlSRPi+?y0V;JAF!XKS+^)86e$uEuax@ zAFoHEY|JFavQ%$O=SPu}b8>P3oKOq~!@Q(N@bHMAjwkRkFIRedf7c42x=@fDcTDh6 z8&6nab2e(~>YX6j6@lqMmIY`7>38gGIeMc1ezBtgF5tA5` zdP+X;SGU%Csp4${VDySp8OHj<-J?Zdg}W=cy@yUtV4= zgTHnLJwd1}6mo{2-z+5_lA$0Ec?tc;Fl;U7g4wsf^?vfRy=>7!N(w7(MEGcl-Z7Bw zygk50C@F!tmjW%Z;p;a@5#3i3k;Nl2qfA&&3Zzi7+kJq&c7C~08rF?cC{{&Ve9RT)C^X+6~2n(OBU z1%FA2hP2fmirgpIS%MvsVI7qZD%IcKQ6evvT!Aqxr^YeY~62Q35RI+Odh0^J}=r$3^g z;2BA69uq#t^XP=TTS{MKHBEX`;k@W8l3(Z6R;qm&dope41(}@8>G`pGNR|503l(qn z0p!egCF6qyImobvv(%}z6v;#TtLwHk!IXBZUCr_D5BTdT6`s>N$%s+x&hmFuIbj!S zUq2Nh@$iBvsEIZk^A`hQmRRCBIz<@hnUt{a&-Bfcdp)V`48gy4dY45;0saD5shsfD z6R_5nI8->agzxOMilujmS2z6h*?WRz@MJAh1%M6Y61n@E_MkYk08tN$jfWp@Ph1pm zJCfCN`Rn?9Zs?|;=S6XZ6g1_J~(`dTri4 zaaKWih!P1(Fsaa`--|&xY4FeuPN`)R$$Mgx!BKyN=eu|BL_rxSCMMSW{IDn}B0A@K zvK1AkNiFb%iW*UocJmR77u|DVye7|Ig>O18E_T@%7*&H{mrLd5Br092%O1) z9#jf_pZOE$w+thrq7MFqi_Z%K{VfT>-Sq>J9;het1Y)iCPcTbp-dMKh1vBAK%=0f& m(O>6>e;Mch{OR!)uDuZ^p4k?34jj8fE?L@IyfMH1x4#2>v-r^f literal 6059 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 literal 6051 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 Y-Z=WRoZ&HW%MQ74-qxb*+>Lwx1Q7xNx&QzG diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png index ed7e11f4e68b1f28e889d399037384bb8fe97d97..1a809d7150f823689ff503daca4e1b365a02a087 100644 GIT binary patch delta 6587 zcmb7JXH*kiyA54M1f{A}L8Pdlf`IfY0i;TY051yC1tbvKfQY;{Kq5^kQlxjK8;VF5 zLhm6+3j|1LArJ`M;rnxc-Fw%am6>%Wb8^m8_Sw%qlPIkyt#~`Y^8>$5(#`9Qkto_H z<@3=_iXAo@?-~3i%o2JtXxCNQ78BC^&b+kq>$`=6L~>VgzZNj8xu*Ya-GxCnNyvKW zeV%KcYr_}M`_>}G;R?G^xU9_O-haMNgf>UKb_t$ncX+cN{5>OGL<$1QFqK}Vfk5)u zX=orRY;2H-vtR@H{-PohxTyPooL2$p(;>Pbs{|1nD=XvglaIfdnxJ5or4~pL1>9nq z85{k%He+?RXVN?cHl;;2r4qe&uS2tC9wmyr2}WfkhzvSP8}oK1b zE-6kC%nO}kPqKx>!M&rqwb_REI9oy z>Bpb*^oAdsaCC>1|9xJdgkD0a?&C?pQNl&MyriRAGl7dmQStsZS69s6vsS$r? zWtl^BnQI0@hg%9Z@?0Kx+>Es>A2-XuF*WldGW0{5gIIwLfLL)t2st9gh3qFE$iy*a zbhHsm^ASJ&2_MPJPA8ZXe>Qq10DBV8NgtUYV#3=AMu|+o4EKGJBeioofZ^g$N=`9n zKihQ=t(0R4Lul{p>`cwhS~OxT!04Zm z>27LV+}xxOKB1cZ3e`5ZEJ`EKPGBD^@m?T(!5-;+`SJzHe<^Pyl|I+BaHV=2HvCOL z`A1Vznarb3>L-X{yvQ313yU8$HF;z#;9#BfC|Eb;t}|=b%uHI-A%^Jg?5wG%sJIX+ z0bwKs+XL_3y`vnUfqtS`MvoPp-`2c#*x@wRrIXLt&xB)j(lv8bDtwE%w7lFXSU%;F zGjkU>+b(6Te63finYVM1=6!3m$M`Oxk%%W?8j<|x@>;BDhdl3-s6@aaGo0c!h-9p| z@iseZ=j&eck3jG4HV31|Jz$}Og}R%sIywf7jiu((tdZ*M~n5088s;lfe(w+mLyM@v!)^<2P1 ziCr5jw8~*gf`|~mT5D2L(u<8L+0xR|)z&B`qiSRF>>Ai;2qBl1**E63x3{wcdD9z3 zu=2H3*Nn@)i}L7mwseJ$B`AMcfuQ-k;^qSf)!Xx{vAa?yqhExnN(;`RIXnTOG1m6> z=H=tV)I&0J>TFNX+oDnY8`o?i;L}4Hj0^w0{EWA#LN?ygA@_GgM(!wW(<$VSH|^L8I)S+mp4(|ozNe4zQq@BF{*z}b_lo54BNB5h@`8ioh%w( zE5vxKZc`noc+0$1)KBd!`HINtN_lBs5jAATtZF{NL`6^SMr4S7Q9AU)0`fUnF z1d8=(tq+#}aHj+eT2G>6r-}=3i#}+ki{?$X?gyPHj}-`mH4HYkx3`x%GcWkaEZ_m( zEh#C1gA~L393VP6+S$kW^`wlSnxVh0V8pPkmDNB;M@OnMc6m7uM1W!Fj2nN{)d!2`SO#3;0K)rgU(mmR{;e3A@ZQ?6w%e8z@Bs zG4cT!13X;!LcuFYa}TzB^AYD=db?;YUx+!S85B&)MXTj~$mU$+^73;36Eh2NZWK&) zZ~lY7zrTC6O`7!k8ytQ-#7;l}C~^62{W}+uwynaaE#DXR@N-|D%JKd}WgNqBetg7C zQ(=#(8czn_4zGdOSqtN}?Ws5!+Ghg1!peLh8M?Y04z>4r~%U7D5H9ecH zOacXyo_?l6#$Rn~z1_mYn+9Y*CMI&qUSSD4SW50xDKjlB7<{(U#;SUAz23J;9vU6? zqYl3Dx#^fxp4p8`i?pt)t1F0(W&l@js#J|tsYL(@Z|>#oo#C$EI$CT=nhhbY_NOc6 z;o1%?pQ(wg+F~g{(oZs2sk9BbQV<~Df$+t@cyQ_$sk-o`;K0yO0-n_1jcp1m$|-cO z{=BU;UJg*#XhF&<+KhNC!HFV`**1h7)>Pe;^c#YRfVeK6^y~>Kmw6;`Ff{wQqs^gT z!OP1louaaqsHdGQ|w@z zKu*s3I-}k*kR6$snJJU#@QzlgAPHyIfA`=$sJ$tVhR}nmvGMWIHB!faSC<#Kem>O6 z@uA(?Iin2&+A1GkUz76YJ!8L|W)YpxIy^SYk9$hlEDEr`#^CSK`CQ%N@qS zs!;+ysh;)%Ki1&V=$6)+wHK&fqQ2BiWvC&tdwwXkPd`lWBVH6W+~ zLlAD3_T>eWDFMBNAaZJdlt}DpEma$qF;5TyC5B#tR#dB+FRy+Ag?ikY8PFw#TZlvK zyIwB@i-Scm-x0N9I*xx2b+b34PG`a??&^8Q82-w={W_zadx#8ykbuRUa6&Gi=urv^ z7rmuTkAMsrYEDP^44RL0vQm*b3HT_j|0o|^K=15_WMhc}N>~%;;QgrX1!8Rswig8> z&MDiMS|mPArBIvF{6Q7e_IGp6bC=3$hVixp3w%$Nfvv5rr$mQUqJ$tMVzre;3HgU*2O^$>oPRpwBS=r!CcfXd2 zKeEf_m$%&8Am`5OIyZH@NE(3EgDfdsq(N)?$o*W~J%wvLVp?pki$trSp<{sf!dv9K5@hEP@D1=GwDedM99T?r4VsnN5ofZzie(^FpB;7d7f7Z=5ZeqAdMgCF1_+>`n5ZMS1|&!LgRZx`%ka} zPCkuW`byX;T1o)&N5n$lKcUQF`pq4#9{_3L;875)$C*x~z-B`bD)cm>IZUrY#2w!* zS>In3f|z+MMm%YAx+6>@D%cxzLTdq51+3A#%V(u&sGrDEjqN zVdkIf8hqG*dkkqp@?cIh-LEwRY7V|>9R$?aUb(=81lsBV2F0I+RddwDxrU1{^_F=8?vIUD*rt4xEL*drp`rJ zv<+9^fZ-!GV0@c^(a2Ppv556|*;XdJ#inmK=p*geD&8OrP_E>N2 zVs*6Z7Xd-I6XT^1fw0rkLZpQRArNGbwTd;bC8uTaxwf}Xpw0qTX>Nf(z!h=`6vVruc?o8J+WU#V zCzK)aU9p?o^uOuUvR+D$9R14XP1ce~*KqiFAj)k_Z~Rm6jI3&N%kfBp_)S4*X_?-2 z33orpCh)Hw2WZ%rUFpqu4chsa5!_rY6XwXzv^9R8bcARM{R?hK+?z`?OJ^v5xAxyTR&G7-|ku4ty*v5OSLXJK5bwP+BO)>Es zIFYh_73&feqzMZrviZW<|Lx>Fa|Y6qdv}#4&6WE$EMu1q;^=8bhm5?a0X$cYzib>> zZ(-*U@Ko=UBphbGRP>;HMDg3X5wf8&!<0!ufPhD5W+s~JyUIyR9b3FV-c)+v?V1Oa zZZzF;MI=9_Y413rt5*_9P8cUxE;mkX8D4$+gW!zm%YmZ%&AyNhnEGjH6kL;{vS4t& zzDBz&q(sf{{$Oo~HSKyW zIjh$bv1IC-6FHf16))Ku%VnSU%%>HRq8L_g)L5^wwv1oO&kX0)O)k#bcDKjql~(w2 zKOMsXTwXKHH;4~yLY=he7kT&f28MUkEk7b99^RB6wxb-v^Wxxu-%?0fjrFG_swYHjQT!cZT z+K@mDpFKRxJkBbK1+^PDovJd?FS~2XMsL&b9ID^T%_)CF;RY_&3a__4OQa?Qdns9s zN0AzPb_^ns2|#X{dl+FNQy~9z$cO(Kvd{|Vz&vep0WLSmE4nkJXqy^d`@ON$1+i3h zyTmR@WH{fI=#P-XI5g)ltp$oetKH3^If!NELF-I_E&i^cpqbdGaXAUEzD9$si@l|W z%cSp2srMOS>TLK)ZkQ>rCA0Maf+)L^Pgx%Z;u3MG5{YlG^f54~%WwZMavsA8G@j{N zth^Z5BM${Q;3G;de^WzNEx$A}qxCpe54f9cCVrVEr1`&{FM{p&NWRUs0!zyDhK10u z%2vH==yBv-Awjd{rLV=3-p?0Zu0koQr3MapFm)dM!Bq!yj`nDue|-B3P2cPueJGaz z&P!goz=-#(We$sN`$IXRd#a(9*ERaJ9QQI~-WgV1J+fAtj`2E1tB^UP{|3nw(>oQc zp;2N`y%2jA3bj&syacUke4qY_p4K+SeZb_!FUh^Y4TFRiOH<$97I)9;WNy7Jep6Pa zas!Ir@Qhnha0vqqye4QifABRQe^v^Z$j8``DX?lv+Gl2cSNM~7f5V+V?NK}n%WKVV zDA-^09T0?{E+*eir8Q6XkX_+BTvgl|!dAoOFeP>k!Rx!>TYK18nGE7j63!?Tt&smN zxeUL91xjGGIdwKOhHGF_zLuQn;s|ODNUVD(*KrhgLY9kDTJ{0st>fKy>rPu3V4x3~8hT;V;jAAVd<>UHa6Dxw_y{$9CImwmFAd6IsN zFFz?JlwpRLrTW(jG*d1#X0{jnBsVm|0keeN8-2#g3|HRopA7{;C#I-!EpetH?#0wj z>Iuo=n3+1LicpVIcOVn7CgD2c|9}hTDw$Y_HLAAqZ7^Z|LCDd_Q$9`3Wg7ANMimQq zPw5>eo)uN&!%x!dV?6e!IJAM(exs-AuT^IG0QMNV7`l>L2BH3NP+XHwK_v1_t4!`o}L{tMN z_SBA_=+P2+RJExIukLjH?wY4bp_Dh%HVFO;wZ12_zoqQ&t!Y3{d_SEX!TU$EFvj#Q zap>BNZ2oTC*(jD@{TBSyP}J+ZUvCp$8R{ zj$$QX6O%0yMp*d+XMT-@0Gp(_V|xtci2a+JQNnKZficj)Bk{bkxH8?267w@_f#~Rc zZ;fv!?#9LVZf6kP)^JTKbi$&q0_W~LM`E1@86C-gMFQ}=H6S2IYyaH*S(~BG+WW#!7YN;br$?2cKK_Q&=DP`S zjz-LmK*!USK)LDAgz;UsjQ7MhhHhZL6d78-3!#MKsV)hMdqvLm+7QLjYd)Rn2bf3q zSwpp(`_|kK{V;Amr(HQ>e?4%$%}9q@fy)s6qP@umgGjHsdxIjl88?RPj zz{!KujJ_26>$4KApP~IZMjH!MTHRdJV$|=d) ze_#5a)?l{(Bv~OqF78Djv8durZ$LN6+%iwAzNlZ)qHF>|LYX-7Ov}hJHm;*9p7nAjfn>$60?IPp#g;ldA^7JQHTRYTHVdQ)^sz z#3AygQ)?3FDkv*m$T>xJ6sU0jy$NwfKGh7cJ`_v*MLUnOY^?4^2tm#zrT_IL>`sctI$#b+faJ1R9f5eEWpXeq3@|#zSutuXs3T>gE)7;b`5FC@@ z4G2V(o*v?;0p5ZQWErn0vVvFS|9J|Z509|F4uQ~QLABH$BU^dd{~rSga-9eS<)=K) T5r2{b0S~CQp;noO;{Ap@m2w7n!d! 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 diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_dark.png index b1aff3f7903cd6565094af495fbc9646e481711a..15f2d45217c4663e2669b93194be74e229a8038e 100644 GIT binary patch delta 6420 zcmZu#c|4Te-yTeON=S;bR%FYPT?`|kL6)-b9!bc)ueYVrW391_HKpv!Bq7UKD>9Zc zhO&&ZjcrVpF$}|ddw%cl^LhVx=Z{&>eV=oE&-J~o>rCCP54WB_)MLAnM&k9l;C1aC zE7C3&q9!_y5l!Tew`ZDvZui_Sje(KRR+g;;qWwrWJO0`u7g21s#>Ld93OWIOSNBUQ zTUWlu>3FL4)K&8`v5~JMkZbg65lRFGlkttoT57*3rUNaR@JiEKAY;;^V`%W z`)gze6;Gz(Ng-LK70N0q4b9CJ8TX*#K&1bDo2#&x->|)geN4jP6lELGB7NoA5>IC_ zp8ZN3LkN-!CaF^zOT*J0EZXm=Gp)YwY*1EKHufe!p<7rfI?$f7V0sB5kLsqeeenuws&A9H|O< zc^5sdq@q$*L|=113#4)ZoGFhyJP`OhvaoKjqk}El-Y`&$$K(GQOpJ9F zLCHFet)e?>hqaEPCUh9&idVAjI+P{TKWFm@@_I<%HLqJgGR zygg(NET5-xezQ-whIeqVMUCZZpCQ%Vrc?wGw9ubrzlebUNkKWq*pNaZCwd?0NZ#oV?e<2h^V)(Z&ANAJJ^b4xbG!~ zDE4ThGMrfz{>CP=+ndY6Kiq*#9Y%(fTD<4-_c!`w${nexryFc*T!mEnUDI^n?}od( z8L1RVz+D*>b^TMgZ3y`=eANAtKfjlYi_2R08P2u(k01N!u-Jk%!YTR(fuJL5*-^BI zhsW9;C+8aOq2fmY>E{{s{Lgc9bL)uU4NbUpFR~}%1eyIV72spvdH|K;Nnc)aFmx#x z2LjT5FADLo^TBC63=FZ10Yj;<(~R-%^BbQS{SLA_-|{ytoP}!3*j<7`_eBs$>|c72 zAs=|R+`xkK1;+Sja0f~^AG635*I#^YP*QAMrmz#m%DQ7?XLm&yZafqKx3seAJ)TO_ z%gd|Mvg+SO0Bp=mC$#-4i=@_v$mMp^V0YWsvKfKM&8S=uzkvMw{Jf$ft%kk9;bE`9 zz^xy>shc@-AT|Yl_67{5CIX{*OoDD2t>t3smx{H1kY$zQtNM;@b>APBPx}x9y5&G{ znzW@WO8J8x984eO^qNid1@)TAx9fk7>H|X2nb-qX)|dXE3RzTHoM15F5avkef`b#= z+S?cW&^XV-(@v}Z?LqDnlu+SgA(-h z(AZljz=8eLN41g7GH3}{NFqe6)=W9|K%u>!iWeic2305P(O_BXYh80 zfi7uVlO03D6&WA=;sFgnD$5{()G!Wq@wuAX+R7QSN#t$#Z*ZTbWn@+k>H8663^f2~ z9>e>yvJQaIwrQ(qDOSdYN$;DCM%tjsRpCecR7cgxZ5ddL5Pe;^TqB=~&(c_poo9pX zu|kcFS;{nFp?>c`#^>f<7#te%bamAciu%)4=U!~W$e`^58eS@&$}u~zC|NI6eor~x z@)@!e_B}?HOjmG1?$Ckg@rf*5RMiWP$A& zlcQ*AM}n{^@6*VcD!eDiMHk*<3@w4gv~^I8W_u#GZo%cQx8{!xJ_sb=(7-tQ&YZfM zbQ$y}5OFve+!G7QtR0^d7kld}@s*%xg3rteaX_&+QbZK-o*F9fXEus<-Q@5%Pr&e= z4VWX%rJXk{(Xw}N?zLw^zRfWwbj9gc!(i)XF;}tgub|VSehdpxecynZBALbBzO&+w z(|WX~PoHu*N@u6#P7}Xm=+OmWu%fb}vtUpeB)_{3+xs%VOq|RZUWn=Fpcd4cOG+5v z%n`^wwKCN-C4P45klswR0_ba_|II4HQ?-9HGD75jYh-kt5d^+=xid5L={DF}HCW!` zw%~qTf>B70(+DSLmG%^7oSn0#$5{CTn@z75Z`~AjA5v3#kmuRB$PN9?s>Lubd*@z* zicm#8MyU?3!N3qOH=2`cQLl6~;HDVsaeV#?YIJ^3|y0t)RzxIVB9+)7?6?#`XV zjMG+_C=q*lj{z{SJseO}0zCXdFG%~)PJ>Uchxp*;#IHFUnUz#F?>zx!Ms@6<8`*E> z_E>sWS7F&e%%@qBv_%xFGSvW6bEr#3Tpg$va1=8 zzm2q?y~N0vg7u0fT_QgDW&ei&zSH)>T@migL4HK1>U$jc=Hv_nbguysZP?w$KlvdWMXNm`02`8q}*+;_N;k1xeea^4D@v+e&RzS z!>{-%JYO?uL0H)R7UuhNRdb%s$7)3LDwTlT^9*WC1%XMDc(5Db+VjD8<-sq&jsWRl zDl)VXkahw5T>LEK1Vce>0Q;%{V*vBxlFbi^kXO!|s#j+-Vhb4Ti6=WfuyePsfxr39 z3diEkf7}>XFKAG9dlb#@P5MpG@I5o8dz2eiW*x^x9<_Q^dlUBO1?_OQO-qWlIp++8 zs(#9Hesu!%(2)sWXtg9#*6U<)fVnD5z8CI6Ud<*~tTNT?Bgf7yYiA^J2_nkqM!;Hl zL!u+%>gO2Zu=w-KMD7#+}%(U4-!vDN5i=q-;2PAQEN^lwZ`<6V6wPesc_OzzvAW}~EFGO{-V~|6?|J?_`k~84(!2=K_;6hf8&X=o zQ~qrIU@Y93D3s9Xb;-TnzDQuaq#6*r8#AG(8^pA$Wr2EAN@(_cbn)PYW;;xKj7r# z9+J|wI^`3#l=vCB4NO!Bia5Ghk0h&fRNt*A*pG=I7Jz~(OE>^es-wb)QPLygjr0WW z=)D(0jPW@;re!^$^(2(F07%)urSbb(BW=_<+!t)DHk=cNdG%ZXm9TBRq@A3*dW+5 z3(Z++Tqn6eTnu4|LJPEP9_-lx3c#3kJB6;LzCDvImao(&*|e4Uy=;%soCm>JnkkRyc1vXy_UH~zXj1(K zlHGW~nQV?H^8R#g2`{Cdm{!`;MSw|_es%|PpZ3IwRD?{mm$`xz`p>Yzk}ITlg}Fwp`85Dvv!&lMpR0?awcTmA7D$IQ^w{!t^`vztBWa>d|!$I2?4MyTd+Ga~;E$u`GnMbLIOsDW45F z$bbxjhI>$M-Sv#+w_xoOHvQ}0r$Bn-^TW$3{DeuKh3a)$clvCFHYKjg!bw}qiS{!>M}wfvwR zV$#MeLuJG^%W6z3P=d55pHg#>;PafE_F-N&HVM6A<*BnpM+;hGTJ&T<1ktj;ER=a? zH$GE%?vBk*DQh!9Ajk%X@Wvb{Bb*N0{qdVb`(RQ9 z4t8};Gb7f?pDI$krmJ)iV;M|$$cbUkR6Y}#0!4|r3AD~_5REPJlWcjo0E3(X5wFXP>b{*OS zM*PqHt5j2AGjJqofyWIxHlJqevQ3T%Bn=F4x zzs3kdO}8q@wG<-Yh@pq&B48sQ-d7&wt1*qskeBriFPnLAS|FMwFduA-mr6eHA5WCr z*1B&vpc3NRxgJ${?r!^f|DsHijG7|xf?WChfc_5$dv*V#|BBuh0X~JXxTw zc+G9(=#G1XW*0{~SfL-jQx4Y9`~9qa%aLkJD<#5~_cb2h4_u;Xi{ad((Im6<^sG@y zHEF8?A0@^HgZ&wo-rVi|>eBT3N^es!Q;;@1tbJhMRs3tfz;#{Xr!i)I z8>7tZXjd^l=0}XIe~mLjzMqP1fnxYxB0{^xan;sHBA1{L)a;$ zoDt=UI+`FT}0uKJ+2XK#N`g4!{tmkD5!Up60a=Uw_ z?qW+L7E87_ioAuELT%>k3+BMe$#SvJ!N8F5Ggy@OVV7v$I9!Qn5uh%cgOR0vKGnD1 z3uqo*i~u7SY*VyGVkBYhz#;F=64nXI|K*fXAHBMIKOKG2E;RSNL-A8Xi;djNs0vAR zU--4p1PQZOKh!Db%I9=o=5O1zW`wSTUgNI;o(^8EGDWW>M`j0s2EZ~W&6x2*P>^Q} z9xGN2Ytz~Yzjn_^oLjo`UPAD9){^WZ4{Q6M8Nqj_in>6F-TT=nAU&4bzr z-y>$ZDe`wN_eUx3D1ssPSN#U?0^-MI^Ph0ahWq_*I34-D`)^ofL*74*vT_Knbnr|R zz|0n~tIOEA)W-bPd98x;xk6_oT4dcMk{5FF@;Wt84T6d-I5Q3SxF25xAw*IFY-hkx zmksZIrW5{6(#iW6`?O9j1~t2uLXv%23M4jxEiqxLQS%mj=sX$IIKkJ9QHR7j*yMS7 z1>PeNLubK6?Fwp-W#pA{C;8aFw>0myVlEZ+{3M#p+FHA3f`@~A>8$>R<9}DwlzBxa ze45+>(-XlRvZ@&~_fnka=Szm@q1a-2EkVr%vE9_>_m*!6uDxY zWE5qk%An?6Y@lmqZpNEE9}gc=D`w=*q&gDZH*=4D>yy6>O8mk(1_nsZJ6D0ajWGtc rGqxsC5D3Ev;+D2~-ES-d&*l-sueZEhy_rvVnB`3q^Kw$(jFp4RHSzT4*}_rNDXjNR0Ne0L%;o)N&6s~b$(Kc{Kq=!Uu zb#~Sr9UZ+*crelmlPi>rQdCtfNJ={H;o&jgDgbBw_kz>la0xI#8>#eQ-IFIzF8I|AAPqA#xVjlYX`u@FsTKw4enXW_zd$DBkrF`Q%=6%Sk zIW}W4h?eTV@q4m0@=ki#PxBrpqlqmlYva2_`W?Jv^D`5MobE==`BekHtsBY$wWJVId&1?tG?ek^SRijE;}{#l!v?f ztKj|u+`aYnbyvj`wE&lM%L-LtdDlRAKCx-ru~=dVD|WB&IjBla?+nAdk7AtP z5gjYF>{afcg2?EpUe2$c($KWki;X_7R2#!D$jcW@(A_*dWE1CQ<>c}W^F;CI+0^1f zv7I^Hn&oJxeQ-V?h(R{1sHyo*Luy99O-v+vR^eVopo)r%UGaFUuC6X!b-BAn6{8*v z?1h4wo|J;sU&pls=O{&1wIN%~@(7sqG0@Ki*47A>8v1LC-oQ$82`T{r!kHLduKXa` zKQ=ZNmw3sgYBXqbCXvC}qSMwa5LV&=?G+%2UDx*_(pJ~HUJ3S3%b7Xi}CBaIx&8P5QPNrU>+$r`u@P}!E@AQGrn>yfdz~JCs zxI(RILDuWnuZ>bBW@oc4izuZrCuc5poE8sgX=#D67!a%>2q$`5pe1vK6FBI6+DFvQ z-rjyGP)P8)y^l}a79OH9Tz8c1qB_T+>vOmI`}_X^aU1YgX5UZ0a?N`rtfrHbQ+t{K z^vhu|B;LwIrb5`h$-+ITyMU37fg&>)fk^X7-g0~VatZvi1XqthK!js)o=Yx*g4fT+ zNE`{9s)C=Nsg_L_M_`Bfcb6uhYKV65pG$jC`&+FKzTMp`lgTtr``xVb$dt!0&v)DHoi(zHRZD=My-6p&p6c&Y)xJ>id^h%1wOMOd@`gGHV)3Ag3U z_M8WoGhiKpcw?=stdi5yH7YiHdVAg6+!p`oc)j*+3fz{xvgmGUVFZPYXhY?mG8n#B zPP{f6PB6(cHZB>8sSj|-?7Lq#PMUdY4}0PqywRuYdNpMLxyRf76*fKk`YY^2oKsmI z5TnkPOxy$mIYn*2=bIMr!_ttNNN`iTMY6hDxis*CoSZ8TxBgA`rNVv@oj9<@q<3Af zk+_x?=a>}A-I}0loQA&g!r_cSw1Ot|$5z)@@hMhUQts*)JX(q~DJv^u|85n))maO9 zUG&LdO#ql4r}%jO$D-p6c)$$2UE>+KYsAUvCMJ0GYg`N%gXE;7K0dMF*wOk_R53Av z$EGRN2E%0!>Cf1bG~>E^dh*Dw^PHWn*_X1W(jqo>es@z+QqC?*Rr@U`Q$tubY5~8S z6`R5Z1x4pmCbqNdSqqGy0VKco`!`*VHVr{0srPhuCt%{e_{9JrLD4*8>3KelKaW?& z>v9zD=BSDOY^ixPPe|?or$$ZviIi6x=?~cMQpqVQDvG0**-9ron&}j2U{I`Tnx5}P zM3ccvJDcLQFcqVb#=IZ4ufSSPJJ8xw78kisZ-q{&N4c+n>uMaeu!3K`BLcO0# zMm<`e?TKvHc7OcyqiaQS9n9C8SOz#i6#^Bk*wKQrTK(->^gI(a|*i{?%!#^UWV5J*qoBy6-z z0IcgAFH(&JCxnjI*I+6H+Hm>4Uh3@0U?SHya9FxZ-7Yh4%{S3^`~~6st=?SxzvWzN zzc!d=P5+g#zFiItHup+HAkI5^4b%?1uV5l_h8ERUpG1DR1#%PF)r5*q=yFWBowZ~2 zxZS4^YiUiwyWR*Z@3*cr_QgLFG9yrdy$Y(wDWmVt)~*enk@k3QLlTb~q5xx>MH2 zqTXQysrlB~WVz&nc)MIx@GcAjxuYx)e-VlVu)mYEAXtMUib)Y!SVx4{-i6!!x41qP zpEd8znr>4zZJIC{OW*d$Rf!Y)mV~?aIw?E3NQGBRxkVqcsW)J}Ty((KuTr8iSwdXX zlX&8CnQP^2dd;gn3bL(}npP=`3%;r~`YB-$6_@UASI^Km8)Ic$J8^suCgb(ui`h7iO-i!CT(=Et zF8_EO3e7%$ylnspv%7k$9pD@iY|fonzK2JISya_sW-1~OcA(%=6g`-&w*P0_9`pwk z64=*<{^fnBIk)c}dAp^*JOWDmHqyz%2udx@X+45acz(}0qDT-R%7ml7pF^GzZ?3MV z44Ep+!)Z@gwu^28xD+H5+Ww|3#KW`jE|`H*$`I03;TATx9<8=I zO%kOyP)x;gC%RbQwA63#VTDcdEzRo-6{Z`Glb%m{XH<~+PM|hD0+&vUYm$gK*KS3! zdyf>N1yoc3FQ}78tWGX0x$C=M(bo*yn{VotPg#ii5cTBKM}_^vU!RQy(i5_^L5QMO z3MLk%qqh#HgE5A@^AB_E#Tbm*a@;*N8b<*ghC};ed&>$=}eQz%45N>rPtGIH^s(R9lmzKqKZ?)aF-Q zn}m3uq2d{}rx24IILDRwEgpDq*Z2A0!hvV!q6_yZ9M8g9kS*1!=7nM}pF#X(QcwXhcr&kAff9DKOG}gV zA}I5;Pp(|pUK}aU)NA^ZEDXB`1er-b$hu)qzAb)y0RHhNv$H$`l5vaJH6wZ%yuKkM zu@9zfGb2h-g-`EC=*_qYKsCQiOZc=&Kvj6ENg`nbZ#&}5!Fxa3v>-XM-cu$1%5EN= z|FD%oAsrs?Ekx1TNeaj@e|(5OXqHj@m)b@goAaw)Lx#OJWe0~Vwgchi=$@U1vp z)RP-MhWbh}?<+qXFG>p+ap_AG%f!V{73^*kao|&)%v2C}p&sy+naiIyOb{zlPM{MsG&ImU7|w^Y;xticQd)l3 z2E)#(NZe>MmSMJ##jQwM@bmJ=^tAjEk-cCWN1Pt}KB4A3yz+=*y5bh%!TM@d0GOJj zB!z5XudC@`LIq=SF=2Of(YD-ADnt`V)DLHEqrN$|Z0c+3LynVn_7) zxh^?D?*g9LqvfgF`M0yMD_iraIO)W*4FT%~{7>$OqYSFb;uo(UG~Afc4MiaMXEwzt zufF2#zB6%fVh$dZ_a)_B@kf&_Z9ox#9_tN3f&rb5(M!q@l$IM{h?{P{sJg?`g#SQ9 z$=Wpf&2_FH_1a;W0}h)^0nWw`z^XR*uD>T&tHD#(AnO_>7-_aK)A2x1P`Jg+T0d@U zZJY({^u(kGc4N3pHajeU>1C2f$^N{CCFNPVNk&QECYXc4P>I4O`GFvg{~~umxP@?1 zG;lBuwbNV0xk(sw?{Tw~xT#8098FIAXsvNaj=kn^2|ys^>Q$7C$(v_cJ9s)Hvx%L6 z${Qvsr8=ha(TK@CDrm#xwwgO-p!eTTi&y`atDA0?=~7=|rbg7V>}+PRAH|6)!#}`p zg$pjR*>yv4CyaJCpsndW2_ie%>KwIkU6Wv3M9z{bDZfMqiQSKzmhGTl^6!d(c=eCY zHP`ApUoO&{HFlO7QBwQ$l1KO;G0HT*j?>?rr;0RRHg3PW0DH1e7MnS~I>&Ubqc^u2 zqQAVM8brypC`=+q(N^!gU*ykeU6hmWSh5ylnbITYa$$MFVyW?Is+PVJzK0< zVmqw%Nb%G`Cf(wqta=v!@)rtSF~w9nmH9uz!kYD0J(j_k!3NJi7JEN^wg2 zJH8ttA)6O1;P9iC*M8Zo6x&im_^5Q>9L{O1pz>EAeS$G^{ZzoN`#PH5@6V|pn?JxP zz=UY^4A=HQ%H8N&iFb*dGPih}cjA_d0+swcsuB2a*9;W;J3bj0_p`X)HDXa7RaMzc z`_C-fQLB@LkF@;9zb;}vfOxudr0JCP!yO^B48jIYItKgAr(eKE-T5e5A?b@}TN5K31Pt}O6gtd$A8Z!QvTd-{bt|Y#JiIw&nmec`qn@53 zwVMMKFb7miQ_9c2y{?&mXuxU=L!Y}Xh9<0pGWi@2H)de2i7vcUO}|C(yaKZ+P(DX> zX{D7#)cgxN5XP6#DUF4bd#S)JZWVVeksYtZY3x@d=ZFEhU&O!fq*k+R(T5h95=};L z9cyiUf-;Z0zC#yuda>^Ax;l05cRT#>PvXG8BFAUa#5&vSsa5g;#kG#lJ9pTto%d4* zWe_mgi2!s(&{VU)Tm4RaiKnwwI>0PN{ru7 z7oCL$v$t8b+^?sK)axbCL8V9L@cZgydbQ4nEV)_sZt_fjkk>d!S@diFZ`Rsy-y#&( znkO6v>h}qxZX{S{4C)CSE~%avXlLi6@+=FMG3Tdq6Y^M`zNpIs3!Q2qJ~X3qJ{ZD= zZrOY4!)N@&H-RprsykQR=H^u2je-bTE%f1Okt>vPI7*qX72Us4VW(@-ngh7;luWEV zs2%TOViYTaw99%_$S2GATL~@VvHM^keE=8qKrwc0)M{kdvtVd6r1ToPcL)~ZasEf} zW|xSgd_2eNS^;BkXn-|X4&&R$pXq33zX~HZCWaT=^8c;Vu6?WWxvkivOCeO&$Z^IW zb95150Q+^`^z8kDjcL^-<@nHa@`jIT#eoGl$WDI_K~-poZ}T`pBhqv6wt}$>(5-e( zHE$@jacH*LO_ec%TnY8M`Y2fnU3M;KlQ6JulEXeNroO-ak-W^J*Fe(hp35J=P&O)p zEN6OSG63C#b~g4OO*Y71J0{bY^kbgrLruhW6GUFK2;nX7=#(~xr2S6)U&eP%SQBk9qQF57p+_)=Da zGYF2Uv*MEFclsx?FSE5xjWh-#NtDWCL3t@?Ad$itEq`xh>!q<bMRY_tw8rXO}JW?8vzhU*bf;$mz7$*Mk~ErLGvxBU^oA{Y36;{cTScZy9%{ zF&G-io$J-bXv_PCWE{ZsC!bO4CQW3i>`rU8uWx>O{aa_y@45U>zKJe<=Ul6eMbf3P zN%?D0M6)I7)I+>c4Mv#9*VG52&MQLH?cg?IbC)q=Wgo%%uYAl)DBb{y zJVxY$U3c=B`O-!!{q)H3*ZL>0?oU1V2NoYEbQ6e-pUzQx@i(rvy!y|a85EM!hA%29 zdUa)QrDirsrt6_{*g(RjJ9zLFd%4+pihBn76ZQE&+Vog<>%ZFcW9#bBu2wyU`o61V z?Fc9&{>1L}^zWDOu)e?pgfIUXofdZv1Ixo$I#U56d}EAG!$RbsDrakfankN22zVef zQ{D)Uy74wUY70EW1%>oc7QROG@i`c_@{y`)jp%VGgzxx4z8a5^7hyzMD&C_?35WNG zK=7Xx$xO=I`d)gQBMjUAz#@B@=~UeG0P=MS!s9XVw!XK+ax27@Tr zcU;@;AmdX-5B(p%_X*nhpUHqgpuOlDTE@Hm*8hWH#L;Ok&%F7IVf387Jp_EAb?)6L J{>$#ke*q~TKT`kz diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_custom_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_custom_dark.png index 2cf5a7f5e10ef81cc412a434de0f7bbafb64468c..3eade7a90f7777a84bf2c814f864184c8510110b 100644 GIT binary patch delta 3701 zcma)9cQo4%_l_N_R;;46t2R|>(5O|ED)miliBY5WEHyqt?35O-y;`#ND*qssON}Dt!#A2;+#m=ci!DHN1O(@l=1hb6SqAd6VBnj_UQ?Hhsy0M#KIbcZ~w) zG|BC+@0S)<`P4Bda84%y)DJKVkAdy4vBZ>b-kJjp`?WZvtaW00XKHHihg8}#*ZH-J z&Rruaw3aZy4!#*>yg?17r4~UZpgF+U3=EhDbI*YPza?5CK~%JUrfv`e1tO>t&;4Uu ziD^pT$c_giEUk+#r=m+XZa(^HJ&=)gJv&D@#>ofT(>N;a?nN7&%}(Hq(k{6-NBlbm zto)_|8AlNo-bdaUfr-DXcI&GFzo!P}JdU$^Ml@k0yn#G*t43;jRndd+EUc($?~@TB zYRh_m9NUu-&-JGpBIfZF9(R2O4Sh|9CH_~f4L4)KnV_dYn3#WU9zREQNC^B&do!=j zjqS8=Wya?F{fPTP-5!bYTk&&oi>v_2bQ!@?f%1yI%zHsUe*a>;uF~lcEkZtQdiE z1VQZh!rr~_yh_2RHiH`zD2jQCB~_Gi6U$J$KkeC+_*-Vs#I9xP;Yue~<1QPhi@tqN z&@1X9JqZ1~tMuT8EgqsKF|k%k1G3DFEs21L0B%of%MitmUJ_D&R~FN3#q<}w^zb`7 zgoW`)t-t+i{A>bAb|?KMDnKP#Rw*t(QT9Gj3>{5c;Y{v+(l<~$n3%wCgp&e%=Q0>G zjXfaHB5O7TDQGb-_X!7HkZ$Q1Jdz%~Su%!{O-^`gj2N)|BVb6E%&OOklH#X)Inu2( zM!fueiwcBurKQ?5B*2`n(kO4Ds3kgWY04%DzF!m;$cyH<0cZ)H)@=uGKkF?~-3kd(@*N5cV)PREHLNmdBm7v`{H z>8eC9L#qus0&sd?eC>^Bg^G4ff5~S#BP~K}2Ubkj|6jq_;Uqd{9zC;cv;V}_3~d>C z*P9(k(WkQZUi79LBw1wYCUDEE{7K%;mUxTqOVX~;CCkx`t-4{DDIuqwyD`%nX$|sA zX*WcBnRs(g-J3LIE%Ckg5&zK4o^uyN7LRnt<`{S1djWoTFA`(RYf{9J&8F3@&LZap zng)+8R5+zAs{+ZAcs28ofaZ`Vj8#Rek>f%|Ipe2w=6>I@4E}MKCO5y*EcBc?-+wnC zHHu3>!0SP6MU*QeCcj-CuDHY6{c_~fp@mW^E6J}q$ucJ4TX78IO^f!1PpLsIk@pWL z--Ks976o$A-0!6BUt)jh(S0+b2W}Z0_|WQBEBbvRv@py(@&OoG1Nq9`-O$X=6mb&9 zdgpxIyB=L^)w)=trt<<*>i?><8H;5KDnu+p~2EQukf(LLih{7 zr+Dy|;(IFBy3vMqpN@HHeh$#f_E6g1K&mqKa{061vA8x=LW29yhA`CGX|JS$3mGpJ z{iI4}`9<|Dm4T`ej+vQ4iskL3OkN##Fhz@QRx1!{<$D^#<(QGD43U*zPT&R&K$Jh% z5M#W2Qj5b^*pS7`g+H^-ts1i8f`CVCChgB;cM2{FDg^Hwv)uv}dus82ud$BV+QMJ- zcsDr>XUg3H$Qu6s{^+LZ#e=`U+FmBORqrhOTrr0nGGCAS@Y_!7FV3h)_jG8jcz|V| z!PA#zZVBaZ36(%!Mrn5}Mh#G~e8e>W+Yo+9=eh`n+@Y0XX9q^6qdcX3pzpfm)-4k$ z%S*<$K$m+=-9R9`N-`mn3YV*({xqUw9JooYZtssLWW>S#lyNAPX(e7p{MLF}`&$8K zdh@IPKA&T3g(9Z}1O#<rkn2ar9d=EnHTA^?X<4v280xicThk zNv`DT76Sidaj|WNZQ|#mcBY(uUs(B9ZB&@%Ogyw_dOsE)E-e%|Im3$ZhJm73>6C)Y z8OgOSTA{s?%U3T~DUQpP#-7l^0=;0FqLUGG9GX#ft|bE(%XHN)vwx&2lec(@Ha3Ym zbwb&kHc1q*H4DA2b%imrqY*a@AA*X9zvwrGWbiF<)-37-kJSph zWD!>rzJ+XTZu-qlrJ4lDD6c8RIt-@cJ6dEn&deG0kbYRp7yAD}-L%BJz{&7Sm|Psg zH2`A-Ah5&L$Mcd!N+CfBI>Oy6lVggnuR58XE$@>wj4*b5Gj`J~7=S6S+IfpJ2u22~ zG&nUp{4rJ2p0^*m)Sni}XFRCTti2Z)C(!q7E^-YquWtrH%A=x>KfgnVrQEYSV_)vK zb?6X%K)+P)T3r3{U_vpP6t)0AVbsuXG}(y(#2J-eU=Ei3I4;i;A-e2yUOWyls-skL z@>q5(jJ^cbnfS8g*~Fg*1Z;HK$U>7~>AfOFmn0;lY6At3h42As&%U4D^nlSD*69)bS(35{+>@6$l=MPHtU9w)ag+d7uVIdsf?|@4bCIRg&wTh{pmn@gyacGrvfd* zx+iV9N)3PM=0qF}&?V=ey2n;|Pk?x=`dnH=?V#do^v@$}*u&#w(r@Z*)+86z?OC6m z_IPQSpKBIPq5bbnv@zY3wvg>R6HlM1NCd&riU$SVrk4k2c5dSLL(lGCS%mxXw*d?A?1){bPwQoresp1&J4(r6Msw+Gvp{EE7IYKL{;Q&Car za`dK?u^^29q@HYqE|CQX>F>_4_OGoL++;ayWEz3)k(UhJJ=oLHnv^3bA@TsLss0)r zdoDtt?;BibZ1wfFVW`) z3SW!>!RW~myE{P}kW2-el%ymkthPut(M3g7)zM9=Jv*#&o__vsz?(?~UCDvs})P8AQH({bDsnLE+b0Z!Y7CrC3dqGINUQk`Y zc19xLX)clsZ>#q9uH-$P4y5;_ffk!S5XV==`FTA>QiC%f@ha90R@*Yj?uS ztJyZ|t!U`{8y)V=;u)Usj1({c6AlVBObnO|1L=|EBTdBL5IKXP)?>5T&4}raGtYtA zRd2vyI=y+(LlnQgZMgnYgXdffj%$3O>!~0%IrIwXoT3dd7syw2XIn|2+a28AE1W6B zZ-%BsURj#B0VBQtGXJ3UI<2SL)-eSFMfo!zuF3Uh;FS=V0?ug2nNT75d1* zq&hFQ8lZM*_EItGjNCOxa;~9G20JI7zkXt59ISYBZfxT(Z18bkniXw{NZ#Ak zW^x03*k@m?mC48S3}gYnW<`IFpQMxqEp|8dV0L)k4kvU4cnVO@%C*~whdm0Hv$7jE zs6<;DNf?O!{1 zqAA~@uC{(fe3pd1J8z{}BAOiV*%+pKnZ@R3orF#lR>IA=8nG)+TSoq9p}?f>w+0@M=h6|DgmUU zFuy(SMe}dzt*a!3*L5o<0b#V>L{U`&=E`A*xq>@uct|RM;XnR3Vx@a+3o3?A4-WJF z1oa`1h+pL{Ub7$Dr)$E~{#2f}cFOAY+wO=k*Lw2?U$mN~oM2=myhiKJ#LGX?@@gmj$cvApi48dG;E4GvD1HvqSn za;^&N+jziX@$v5Tt}kIRaOF|74@@;;Ph8f=cKFq)k{-F*FUaOyE-pVeCS7(WD_1Gu zs44zOtipCvH2PCQ9&^E4+J+u&V0m!YSL5CD^Rv>o`03KWh3D#r*KZvtK_tXsnz2NH zfHSKmFC7I+>sEp$?*O$ZA0W3n4d6{5Up4k3=^YVw}ntHL~hn93G<3auL+a@h=O54mX+bQ%N#+`&m}OJ0@} zmb!If2rp=;B~hr3BxL*|PKM9&ol#s{l{t*#5eP(m5cScCHt*RTB5Et;p(s^X{ux&) zJ~-i;wQCN#kPyc1ySB5x3M^#Gr5!!B&gE7I{fQfSeK#EqajPoz#;r8!q~i#+z>>N@PvV&SP{ zvt=UJf{$uJng!Mrf!6e@b~ME^q#(j_Vf5A9cg<=O5|FeP`MzGy&_5H4@S}FUe2tB8 zK~oHBVwwcR79G|yLR^ReE6&HKqXI@;U{jcW!79V}F`EyQO%+^SdL4l{&Vpx3iBi5l zIFoH_LM5MU0TU3pM)@2_a34mH>AlZ~G}}xDx@p`ltT|vR`*x-P^!{`8GP3x6kE`4q zr*$ti$|cvW{QFl!WwBxfjC7`K6@Ai&t;LiqSgMd77z4iIAPvS=p;c@E0!fnC;0y6n~94@4lBxbTZiO zWS0*ZeSAzZ;Hh*x)5peCyZqXUnBUwmP5FLqt0SB#YM{zMKPON1r_e`v>q(ns#y^ZE z0weXKG<0IrioZX4Ptcn;YU!3|-n)#Z9w-|iif~X})$m2neUtLEYtPLjk7XvyB^-fO z2W~VxOfx6@{qA#-%hjLAtqUt9PjK|_)B8+7b)R9TrBkDYE8e$l{7Tr+4jo(RC~~@D z?C45-z4;PpE@^1d_D8C><}z*j~PMw3UO!$s$={3ILGh|1u($5 z19s7@KCbY!_bv8AV6qiTtld!w%+4+0UX8Vx!@5r9vjOi9c#b;#F*nsvF zjXXP%K^!B*@Yuwfc&~7sGSY))AZf%eN~_v99G}SyV_uo4~I_QIy&~DRpI(RqOK%RArF3<7XA;t9$+WUFuvmJXb7h zL}_{ZOw2G-T~Fl#Nr0HFjBkCoy1E)>ZXSihWIf%PDqHBb>wNh8rGO|PA*A}p+oDI; zPL`*RCiur5;}s?p^;tvAusrs5!+ZSZuw$E#xu%i1Dc`=@mXNC&yHI^!)k}hZg;!QJzdh(Y zW|okAh__>mxv;X2VdV-2mf_d-FBVkJ9kMLKSf8oWf|TY1?-<|Das2qNkfqsL;`Qs- zFc>WK#quYotBZeU_>RNAj?~Zrxf@d)s?V|`=_oSmQHrYIM=?L6SPnoNeCNXg1dY83QkM)v0zM)!Q&BAZk4 zc$6Gu{h7rC3fSBRDftnYlIO{xQMq5;vcI}b$|n^U^Ix2#PryR14<}m=HHydy0S+d@ z^_yfB=fQ!v;?mawE0>f4q6fHa)jK)y!A>fj+{sUK&v2%dD9J9y_!<5i2`7&0Gtf3` z&j1`fhXX+Svnr}q35)zSUY37^7_c*Crc1A2;dcViCnc=4A2M)XDyjEbjjd(bW3Ft~ zgg$}l;G7pNwP%&A;&n3It+nql@g}yZF=`WUqzsKgMbPP6tpPuG`xt6JFZwvbI3^dD z2N`O9%*@V0N=zbU6xcBi0j$>xrywKm@!N!xrbuxS70ue{68*w4n?@nkWA&Lbf6@U4 zmTn-nUg0l=GgU0v&j{zS4~YsA1MdzTaPrrjSQ4?pb^r(r|11= zu)OW-a`{|67QdQVZ@=sUA)Kv+EJJmlm45pnjfHA`z@0o}@}W{Vw+CCRT;XIh0c`Of><3a`R2;D%{88Ku{VWI@dNrkSC#M2+&K1&==7+6Jgm)u#&D2a zcQ*wEn*DD=p*HG;o|qXK^HypqU7pz>HouliKOiZZo)pvE5Hai9!^9&Mz{QOoE6*o4%069pvFL0V zqztYIcqBa({wO>HEJC}w`h@2{91~$4cR8~cQG=7d=DVw1;=N_PmKk9gj_P_j^S0l! zoh0;fd%p7|upL_Rw}1Fnwd4afcw=&%a(KOV{*x93inzLaZP@uamP;Ch4WOritXn~Q zAm|wNvPhbYl(3XOg0kIbgIin269@zp>bszDyq1B7$cM*_-=6t9 zA0PmodH=(hOMe~CwS*T%`HJK3wfwgX*8T;p0Ro~gXtXwVHVaCRe8I_Q9rq|RCveyo zu)8#$l0)(q7sfp9CS|ZI0ycUY&pC^0EV2gU$|pE2hv3uQ$ZXDkHXNX$;cpSGh&6Pf zn`+F>L6(+PpsJc~+u7Nr>I>)_7(gHpqSK&jq}C(+H8a#}1N@ceK-wNW=Fl_XahK*9 z701v)=;g1V#D-@_n$KgdsLavgQ0%ro#Z*7#C;Lg)SE!zh>!e^{BZ5$vUz4A3;@1^I zPGC;=Oia0h0nh$g6p?>}G;AX#GV|viz|1xfvNi`eVfN$p#j~rcNv@bor<2XE;r1%F zw$Et8j@mCV&e8qSY)V}T{@vZHdy}of`7cQy@{Lai7l~#MCW8;Q@-c`V&H1I^{%UPH z^sWkGut`Bri51Wo?z52k7LVB&NNc6_kqlvZ!No_eu&USSd~PT%+QwaTl)M9;!exS7R?8-E7b?%mqG$r zjjcV>2T3cx6d1DqwCMTjb5lxwWXvS<>HQ;%OyS}wya3Ej4_vhdU%jEaJKU#JN?4J|r4_3qHRxNZ_Bt9GW+tFC-+- ziz$PD-HU|kkk;%ZOy}7MfC1&>{oV{|>IkaeTdz>+inM?`p*YMZ;b*zjSr4o8x?jNL zCv4sV;Myn+oIDwq_^(c7jk!gV+Dqjo9No|nEg-;stH?y31Q)%xzNdF%7~yEoRpg!W z%ko$*tsmRnqh=v&=|ozu-JUWoAwNA_WG^~NpGgh%VTQq>5EiW+xDSE_b#y9UvbP#lJl7jl;>F0o+BIsegWvIrTo%CpS)Y4Rmtc+Sc4Ald=w>;uu1ha3Ytr zmb9P9~Ndjv-38WR^2Zhu_VO3vK9via9Rl+cCi&k)%bF>O3l&&^ zDP{_P9achv`I&fxI8#Yn=p{i2LNJRsimDL;6GIe;mI~q?ha_M?luQB;r6ZVLq+?`M zx$mL%Sdqi1LP`i>e+O5HrCJrE-Q9^pd)HH5dsUw;aG05#Y)j^i*eL5D5CUxzVxTPl zQ$)PNWn^X&{V^p~ z9$Lj{STt`Ho#u7@ov-dy@RLLn`S%?ix`u{A9;rkHp$08$koT=y&iZ`J$b8~1#mQX3o{fyWq536W=1n;u%)G?P@ouJ+*^i?S3Vv|_Kvy; zm$8J)xI#VZ-37YWjVUTo#EIprX|4|g;lc5_)++Z{J`tx&?T6i~l1U^|VVnztHC-OjEO5 z>k|WJlKA8EL%bQIFyd$P=?ML#HhF`aV~+JHET4q^o!(W_Sh=GV`C8-6a@yIgY5ux8I(P4!56eB1?k&L22QUU?rkDG!zw~m>QvvgK&SUM` z`g-!}YNQacr|1RlvKWARn*Y&`()oo2W~7JRAaMa77aAIRXIsrP-K!1P;stz>^`(7r z5jMU0*T;&TgS|$zttN7WVWsTpOsnEyE?4(X3y>bA^gMl`q(5l1t`+;-0J3?}c?4*C zDw}NyFla7b+)^Ufe*U8_kHrQD(!=GAV(+S4E_}7$yRiW;G>%UO7&mjUw=I;y`hcIi z_3?4@9ya?`(45besb z(#ShVG6ksN4t>$Rz?z*^jxAiqL=056>O73zMvhd=Q3U~k(5yIKJwyqFr;|vBgoxgM z;{XDlKBmDPu_`98Sig^wCh*zHLD;P3)io6;)WyQOAC3(OxF8A3qnkTP0nfYmM&;(@ zoH$oDUQuMy^6I=+U#EjKa2mP%#n?68NX{T1ihLNY7Le?N(^EW|%v@VzyfB7PrhwAZ zC%TXE6Yi?t>+=f?{7~cV*_Pyq;lgd94{b`E8)#F1q=a(RSNX=rkFKD8#?&BuPQKq#_p9i=W|fhV(Z0Mb zyVhzfCpNtr%em!1^ItKdCYXfhbw&4WynL04Qc4z}(1VnZ3xoOjN-jxRhK7ZePxstp zrr3SueEs@W;}>2LLgDjlB07*~41sH|dp$i_Q!N;t`u%Z7`FR&BYinIo)9PR`vW4OK z^WSWrjRk+(Y+Bm+N+%{JC3)3v_+lWNxbgbcNt%?UN~A!eA42GW?6uM~TBQn(nP#!q z099S&@q8j9Yk8)NOH2HTP!FT-ieO5fodr=}^)a3^JUravaeRvuGjIfnM8Y56yD2yH zFPz`Rt<$T#5i&>LBG@1fO@8|w5 zDQ5YL+Nik**`k}c1Z2Pm1X$NaeAYaLbdq||h2W?mN&wpBV1f1*nU1@EvvY%+0|7kh77c>%X zPvD&Gw{L27wY5_tu;g4|H@Lw-=Kf0*^kL^6xayCB;^MQO`0y*$-J`Y5%@yyW_i~)e zKQpK`wgxH`K&)a~rkee=r?}ixTie@F2BuflPjrP|DseVHUobefZ|}T2e%AK*;Ls4l zz;ycxCT#%T?cG1JZ_mFo0vEu9tk>m~IfMXZ^E7UWD-`Tr$8w|2?Iw7wf0$0XT^b)p zG;6fUzBhxPxzQyln~$xKBU>>0B(>LejR5I+(Sn9GO;25^ayv6s(7a{8?V%;;B!v)L zIlEElqCWTG(0=~Ka+kLwBYE?DN%ub0y3{l@G{C7gOHkF(gcFahUH|z{38^;Gc6XcH zDij}cxM9iZ_R!)u6hrc(qIypH%qJ1f!a6XL;Pb4NFF4t2o~ZQDN&%R|wNo7_S>tIv z82<9*aQAviW%gAXy@Sc*6Co4Tw4WRP_FM1RXu$Tc?i0X>u6SAW-fYdCi@VmU zTsaU-i?d|F&OYo6Y>h5^5}L(zsc`LxOEv1&2$;K`R6j5{NE@d|WxzP}jS#KJtd$jf z%?s_ZX#rVghG6!I^{#r|N{3Et9bHrhLvw;b5Mr?Jr4}FR>hu%#wz~S_MTpPh7^HE# z?Ed~MoV_ARuz7oZ$dC)KNOffCAQv&Qu>w+v5;)i^ykf|zgh2zF6y^KZ2u7qX~mc*O3=r~a%!3n3-}s> zuklD3)49R)nIKSmL7GKzT0fUSCC|2K*YkoZIV7RlVLu^ygLC!5+SmRgena~3u#5|YHjcAEccn1nr4a0nKtw)kiSWIS)VG#B0Ob{+O*bepe=g!9dUWAMP$0WY9=xBXVRM&}6 zQu}cbRDd_5S21e$S9Eae#8wDA$qc6CUmC7p~8ylNJ1lmK8t}wpyr>%`3Rgi1DtE(sFvCFy!Gy<&Y+dF)=QU@N*I_Ioz z{rC~%G3xB3nQM%A;(O<9p;9pMyq1=?kXIwa^YyD&AhF}x;FTo93(p3Lmv}+0wT>4+ z^xMzB?w^Zy`!GF?Z48WvAj$Q*+YN6?W_c(bJPBO0*|C<&g=CjtZv>h{3~1!0lre|) zmE}`!;qf&}J56s^9(}VfwazasEv@k<5Tt;=wV7CP$1=wHt&8ha$m(9q#6J6Ssefkb z)HK~FKZY#2{4W*+rh7O|bVMAHl$4|j?QbngPIX3-4<3?-=8_l|dWo}IA|fJ#aZcIU z+2dc5L)#}axN6z`QZXDAJWUQyYag0P>V}2q1?FBUKe`gyZdO&%GKrsL>*UZE2{E%R z&IzhdEYLS@_>HSr)^s$e2qzVYQ0B%526!l}Q_ce_s;VHyrm}D!-+s3 zKqVe}dzxC+)nL0G0UhN76%4??%+LFs^gvojiSUy)mN;iH9e1VAVmqsJjqqD_xIo3K_%09h( z3?L4T!JxKuD69m1DD>OBG3@YUVW+RcungDEPWJY~2ZiL6l>FM-+8?@en|I#atdATg zHHm_@x3|~ldL=>nfNuaKEbn{Yy2U3aCkM~&7l<=_nwR%>WCYDn81^=@({>)v5bc`A z1{N{+yQWjQT#v~YZ47<321d;>U~6J@)Bu*QUyicddT!#>Z=s&E8hTmmgdS$tHb7JE zrR&*$dl+)Py3HojWX-|3R)&0lR9IMubQB0ZC`B7jp;D2GndZiaw+mcK@#MSe_uq8oQ_U8pTaIJ zEyR`9CLM>f_8(z1wgq#A?nXrXWR#bEaeL2X`rgXtU)MR?r&^&VDEJnd%YezISf~je z`e&u$xKW{PuTuWAapDV{sHmtLkw~4nf(kjmMSxaX0m4un)krtPxw{*o^7^@T9UY|V zzRT|chkA_h^}2Ah8)jdQs%vQ})8f9eL?NUBHJD%VJ`>e;Pp{VJ(PNnr(5H>ro|ce# zvG{}pTe#FZxBp0bSNF~OPjkJQ>pOez+%JT0E!f@=@tu1-H8$qkf0?65Va@LC{LIL| zr#m_|WzvetvPAhidfrveJn!yO)=OeC#lQw>gwm#uu3e-*_;*eG$n@n|2Dr!{Bu?&L zXI=U8X^~wD%)xrN&adywN5b&tLkM>QI@&IaD3(FgF$HaS0Nq~?1A*j5tH z&?-ZyLahaVJid>qnfvhZAI8Uz5?NVUs0QZKBKBfipeY<$ zS4S)oWP#<9aq!pS98-IG4h49IwMv)ze@DO@UH8RPdz4bmG|91TD1@7bhjE~~(VLs+ zdrxMhq=}{Xb49`&(0~)EWoPQZMiXyVl;f(as-Wp+-l2lVg*Kg2rzM1ig+JYy3L<6O zykn##4-L!#3*`0ob{itw#y=py{Oc;aeYK47{(UlVp|KR`%Xo>zN@Q6mF+7c9pimjI zx$EUY3k|aX0K|-Eq7G}A**6_Q%@W;lFGdd+93#2INl|8q7<#)cH4r{{IZRekGV4E%6dD R{uu-ub7O0x8be&n{{YxThC=`V diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_dark.png index dfecb4229c12697748df5c200fe1f98860591cc5..051dc6a03a0de979495b89ee6a1596cf52a101fe 100644 GIT binary patch literal 1054 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 literal 944 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!jX2nVV2<*1aSW-L^Y-q2@91=y;~&qT>=aZ@*buzs1&?2hf`TKT=9{F}wbA7p zPbS#kkl%3nuj%eP_8Ynna;2s&wGU(4tD)!-VALFuZ7@5jCzyj}@y9nw(GOmF#x68@ zdwp+wx%i(0qtFx=dNwh6wIM8robsPhOjeZmB0Uj#go{IJQc{zEn+h>=D=F50Z zI;OYu%hToZuhxDE*?*m#A@<&7d8fkX2bJ&c`09QB;d#5t`0MN9US7Y&aNyw{)taEc ze`ju-v_!ss*ZSkpTkFz#)aHU8GTe|S1hnBmKln8T~CmHq$Bw$=Wd zisxKE_kHiZC-wF9ZP~PGk>B#iXZZN|wC0~Lf37vPtE~L){D+nE($dnV_$^oUTpQ+n z`t)flOUuG%Y;0^>cI;TOaG|2-rEAxwt-88u-oJ>Wot>PiMl;_@akn=0_4m(}y#Iac zh7AiQP89UKbnaZ=wd>cF?`u!>(wu&}w7>i4qFHB`84GtGopLh8#LiC7)5^jkU}sF< zo1-5-Y-myl(E0ZLeMiyGHC21%Zg$_i8R_fq?>z~q#cSy#KY#zZJ6)6(DsW8kP*L%e zmX=<1by51h;Naj{vu96M*?K!ywA&R(rmM;IYfnF|In&2$(zfl}x9;32d9!=tM#I$2 zk)A;kZCWB+SzDtf)}Bq9Db zFJA3DJWsv0=I`Ch~{ty;p{B_F||A+raRk^vHi9CWGb#MylY)`o?iy VPt+I7o(5()22WQ%mvv4FO#nq5w7dWS diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_inverted_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_inverted_dark.png index 72b4eb830e44b2cc1b2956c7acf29206b0733ace..2a41c505bbf7bba7967cb613ed0ca64941078926 100644 GIT binary patch delta 2802 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 delta 2889 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 IM6N<$f{v7TUH||9 delta 2721 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 TsHxWz1PoiWgVl2juf+cVEI>+` diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_dark.png index 39787c085e3e36ae67fa344ba414224ccd8da0bd..d56033ae9ca8dfe784045d31d57caae4eb1ca814 100644 GIT binary patch delta 2747 zcmZ9Oc{J4f8^=W#ieulS+w?h8>R>zvQ9)NX zEdR|Iglg@dx$YYo2@$7HPEQM&6xq*ZTBighTR79PjopipX0;wYiSw9M>rCHZ(Ltj_hGp7JOSCBTAQ`8 z5q&p3y~O#r@71d!B@R!fnuc(X`oCrbegL#Kco%r8%Jl^3eC3cI9{*x)Rh<4RyqP|8 zonKBY);2I{!G}2d#KpCRkOeMRL(o(?G_`>(q``POa_;wJ-^uGO{wg%3N4{CgL!Fs| zf`WFHj7mMr31T8!%93X&<@t+i{I-T+J!`(9Q6 z2Gbi>q2&vd_CbH(H+~FPuZ=%Z5A~^cn~bC#0t;kNC|Ui1M<|XHuB=K3xxQZi(_-W@ zk^=OYyPp-In~ji!=T`^Lzrm>@G7|dyQOE;~nVE{vJ<4}=DDc`?Z3Hv~k5r|A%tt>P(K*^mX$M5dG^GT{=2wUIKpc#Gl=R5}S*8{cTWSI!S1d93 zKyL#QxmCNq=HRwBctom*OjGT|^X*RJu5!3;#bW6Q406$e_jP$WVsMc;CX6bPUR_&@ zl@6$$Uwbu&hugVHrU(uW4uU|PUnmiXzwmJqHSbg5`4XVzb?%k67{1s6iimK+9w2~C zt#@Z{nHA-BrHNd{`_l3o{IuQ~LaM6;CWRgk^m-kPNM^BE_eIf?PHDYEb{R>ilBp)A zF`emD`?UF+!32!U1YFO^X!JUKs2aUJ|I*!{Fs9v0G2IdfTRXc(RqvF_;K#0&Nx&QC z%?_x9Z?k*$t?%3d0{Elc>FMfPBJ@H=DUSbfWpYyeUSD|?&UL|+73Je|{(ciVyoY!+ zH>YWj0fC*z7)O(ng##*K1BGWpTJ`CAQJX8rK9nu^Y81s-TsZ<*0f{6E)7JLIVxP@a znUhNE<(=4r*v1DyFMj+etEHv&8T;;ga!kQ`Kal!efB)3`f-~B>b~L+`=hGZ+)a6@U zgwCWMzb-ag<3fcjAgT;lXp)FG)<1VnD%~<6&V<6}6Uv>A)pv=5KF*yU9v*f$p1Zs} zWyUq$`SuMQ8yj19x!TRcBhj_eqTVfWVAc$wn#|z*;{=My@#nyBK# zvZ7VBwSe3CL`N&ZnDD!`4p5&fR|Na~pU-k7#Kk9n?QDHm;M;MCwD8m%%?@;HEv*aN zn3jag?@tbekF9+7@;bdV^sxoF;|+xBa!mWHp$@9kvBR>Y8it$Qe7)YOQ<}pKVQ`g> zn_I&}>yMs7%^L@|aGtdJmD^Vpg)5wFwl9T2O;E>O@+5PROL$NwbEbF*DwVNtqik>26C+7;JVNebd%CH_AMesBjoOE^fWJ;3n_NRQ3r!VuU%V%wtmIG ze5C!bKTt?uPh0Goq^?)nlVEo!gdDZ|>#RULD6PlON{wZ1)a4^RhgY^NlVJtDC~NR1 z$ORI0GvXH)!|EwVA>=-IjXGGN^F;ytLF5UOr_VS8{wF1xwIO6TPftLg^T%_Y@PFi? zsXYpxBPRsaYOt5MQL2$NHSdnNuHj)wAu{?7V8ilad%rI&u?MO4Y!z7wBY*X!G~vQw zS;I#d3k?}@DJgP#S^I+u1+k?2{fPxoB7}@UAf#2S7ChgdP%3gRaR4VIB(QmZJURmN zIP!hrXU08ADXA06R_-!q#!o#IFJ(bP;VOV!6K&DIHQ-7dUX6|_5n6T+PDOip^dQfi z326<}$)gVsyHYf=b+W8|{QOXH<5(h4Zx`-GcYAn#k~)|zcB?YmOe?#qx0h&1DGjMI ze(#F2(vY#q+f-PS4Q#+XE-Zw%Z5ZWV=!?s29r4j{$m_g;|=e^73PMfrN zocm(4bieSty1Le@NU^U@9=c+QbYIN=w{L$qKyxpgjgES1Y$aNk~E+GS`s#hHt&U@A42AilOp0;+D5|-pul}?VX)I>pUt^f&_nK zvr9St5##WP=x7p;$IBS|&j}=R0VHjdXcNb(WR@TL*yKq3jvavv$*Pwjv{0%0)L*4)Y delta 2816 zcma)8`9E7}9}Ut_qEd}ejImQqTT5!}#RRRbs77o>Q5iIrwnl3-mfC8seH)~WXw}lH z#!jeMGDz&Two+Be7-Fxb-aGI654`vDd_MP==X37!obNg3o=;*VM1~1r_!`>8F}!%S z#OnH)AULh`H+|AO($Yk(gJraA9=gYs@r~BlS>{1Cc54jnpHBB&?26N{?})}*qnL3? zT+vvUbqoxSB#}srb9R=s$HLarBQx16)aOVyALR06KulohpUN^^ zLBPAou2Wush%bvGn8DC<#yrWgiOtcx2ou-?z7xn8SvEuv3pN=OhSv$iR7$COo<0_G zR1-rcbdDNkNrNjJrbR~)+(Nwit-XHRuDNr;BX>hr~Zp2iH zLAybqzwT%`zG5$s$News>FYCRXWh0tl$0N}$!He|6H-%CL#_ISe6eU729p_GUM>fU z>i*o>oBngAB?*02U(5I$T(AzAjFy)hnELK10a+d@Ve!?{mtuyOtELyu2*pyPwETwF zhZi~s=~$$4RfQXH-kaTb>dRYNq~2hmLPA2HbZS1mxl><2c#Ww7KA)1BDx8#kN6Ww< zWr8q)36@T1YSR6{3Nh(e7#Vq%qf|I}$DVJ(ep^g_(-0K3vlI5Zrsk<;n76kA1Oh3W z9FLbDSwjxSmpCFi7wdpD84ox+`F#(_PZy=ILCy?|mJjKai zl?+YpqpysTy-<>w*2)_C`mQMbfdupC(GD-alybKB3rCy;N^JAC-GEtW;xz!C;}a6- z{(@iFnWi3c@$vEhGJ0Px_51gS;cfFVyOVs3KNdgWJMBFid;EL6muG{EnN*9! zzc_tI#@Z+p+_0TO5uclz>wL^UUZUp}j;KJ!0|kBlOrNrerczDXwU>4exSPw@?P=EHi^U4x_|jiafJX!dLcP7c8y|2G$|@?{ z{p|oI7`74m_msvD(+M0Zzof))BmcH|7oF75Aa-g z`X_Ubch@|qthlL*FKC|XJ_WzHy1FLIl>R2Q{-|wlb2%NXCX#g^lRx;C$V2zhEC=QS zs(bei+_B(s|Bn+pkqYTDhP*pAcw6$BqM{-~VlN9=Q?s*AMpwQl9I|@Tt!~YtS2JsI z#m)(;H?uDnSmT0%fRg|ce&)t!`jpnB-0P$1ypAn@FQl<`Qf}AH(xyuDE*~&_rTUlt zy4-sY@-+X{lRkxOZ~@lV)>gK(#|cnYRu+J;9&WJr<0yg}>*wdkoSG77Ads}FaCE(u=rXiot= zbcOJvb2Y3xP}>`a6A+B!O+xbm49?8Vv{IoU`AmTdGC9+htf8kjFg)sn!PM2&fdFA< zsa!XG=_sFLNnHx1uI+Z?DbH(bMWEfXh-9xvk0jq<2~e_a$>ODKJ5?}wG>JwtFV?*W zLDCUpd4S=yv|@vGkWKCJ@sMJ}2!ww_b7ec%+2sf1uJ&Ozd%Ow=38gj}{*PTiB6GhE7QH5H z9vnTaI=q<2VT8s4#9S{m`bjUBrPCUDu*2ZsDN=aK0fS>p-ed%{w6r{2<82@Y1qF$$ zEp+5LdQ<}*L?94A(!>K6GM1GkP!#(Gz`P6`s`dKo+~#LJSQi&l%>pp}E!N$=x+Kb% zGV|vBd(4*j-(mWoPn^lx5miOB9N_=zYT>E1***<1>fv6)u=>t-Q6nyD4NFG}~xDmSHibTyFR6YQarem~lYB`5@^W zc`GaBETzFGIT@Cubsz!ebqa&@E|HDO!`p75E??#z*oX;Xr$nY?(k(P2T(A8iOo|{Sb=dCL@AK9tR;k2RK?2&v*~V&#^sk)rvmoV1Oxc2ct|Ec^bW3$iGXPN3i`bhx|d=#M2!z*kTayTr;yp*O+?7{0Bv^Z_oe$ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_light.png index 488eca938e9bd2e22bc56b2e15eff23967cb7eee..c6d2fa54ba4dcdd8539db416997b77c7578a4396 100644 GIT binary patch delta 2709 zcmV;G3TpM|6uK3VL4RaPL_t(|obBC9ZyVPY2k`%p91bapBXTG{M45U~wk)fb?b?Zv zE>fT=k``!#E{tyamD*j=56~_Pq(G3OMVclE&_%ikn$))J#(G#1CB8W#N96E5T}Wd1 zCWpFw!RNOUY5MX|;vC*N_uL_;X_^K?2$=$gaR)#MA^oC+MSlpH8kDdIAyb1A79nJ6 zP{JaFObtp{gpjF035yUiH7H>bLZ${KEb>OER4OPG3gZrgT$5?s0T425(O4V@`v=f8 z4Q`Jc>%p~gheQTMGb}PO6h%QK5=E(0VmfXt8pG8Nv_w3KN~OYd{B}yE5~9%!Rz;~)hS6wb zGIl$frs3OfpVks@Ft`Sf$33*?V?|NU@$A|2%Uc{CK70VPnICghjVNJ}p@~GII5`P} z=S^5$U54Fm@A%kcGFAJzR4O8!&cNZA8&RZ-zi$#GFvqgd3-Thi^%H?w5)~$6W1)L;9vTrM*iw--uS zZ-2O4PDLaV0RZ@XKFrKmb&rvf<=W5Z=h=}YkajdpLnIPKCZoXVbTBP+k7H3lQO*&G zMq%c8_agAx`wICx!qBNqxqY;JB`b3_fA z#(_?>fK)04yL}c`tNsMs4u8vou3x*md$oS3N~MCewN>3?)X#iMrQy$l zR8_6bL{q601VMl#Ihl>$Hev1VALGv-{{!*l834fTa^Tm$dLO}+8_b@VOa?_cZ~JpQ zrE(d2yZcZS1rDbJH`jwBh_HW&M6%&_DkWoav2((zZ@XAoS%xUGDcrN5DCZ5$n}26# z>8_8jZyWG>-B?-*j3CNZiA1XDcCu^2N~KabJPH8-mI626a!FmsZz;Ne`o&lH-5>sh zqfi8@nun_9aTJQ+x4-`*KKt_DgTE3ocI}GB^>v6!wR^EQl}tgFWt7WhB;tvl362Fl zMPsws+;F>F9jLkOA`e~iRPQpD;%ZJs8-yAp3cW*pBK0ZNS zRgqU!936*xj^8lDIygMRU;g@e#|MA($v?5Wyht;xDa4{^+`NRI&1PY@&oYg(*|3Vn z#bRUIMRvfpi($p5SL5GCLoSzt+1$RyHc6fMs`~vbGT%pwM#Bv2T}S zxg?yPMsRp|1V97N^SF2KE_j~D^XD&WZxm4!v9+~{d_Iq-PoG~F9NWidFc`75bsMv@ zvm=kMP$=T*(`T1&n$GcEHAJ$AZ;rZk(oxh- zB$5b)P5=OzbOtut3`9{JdW_BVt{)s89YIcAtOTbs8NB`0!{G$hhlUvzZ|dFqaaHpe z$Epb-Wb_PNU78s4*Dg2Auof3Q$S4P0|3hHW3tSe{-g)P7!@s#)Qrjy3dT_1nc7q2G z?zP+}+U1{N~IF>zAC*M64r-5`!Qa8 z*EwNv9Po<|-W^3;gfYqK#F>22ro!_)=EiuiEG+o2u+W;^+kbJnq`IPZ_a*Ofa7q2g zXD}EL2#kNhN)$yHjmFv*pk8rXtri4>`g_2Jp&?-E5DwLn$#kR<0opOVc@n1gi1>$SeVR_o(o*??#^n;Q;VtJN}5@#$r? zS|QhV9B{B$I)8V?a~y{U5AGqEOu=TG>3Z3pVY6B-wTgJCGAjU2~e zX(>=^9Q7|OOjLY&dA)9Av)S79rYx5337Eg!?lW$Iz<**x<7h8!JKfzbCw}wf=j@{+ z6Gs&7wN*^VT=%_kyvc;y8@EOjnSP>ZM>rg=-JYB4o=|;0??fE5?FfPZo;RaZDgu4H z7K_D#J9lo6D7wy?cMJ52TaqM5Qvav=w3qM=x4OF0u=#nKIh~G+){e`~&#d+lj?2cz zIwH|nZGZFgG_YE&2nN?`o1dLdwr6FOu-n8)LW%O#;)F891h-#FsyWIecsz>P(i zOn;`HA5>P06*B7EE(Uu)&4Y?*Q(U9b*w+b>T#pBq4ly)fU2Go_}F7WYu;-35!ew7M=K$Vv;(^^|0G#vAuow z;)=44Ip(!G7C24o$Qz+jsq|IUCXOMF^Q1l&}aPQ{(>&ywDg&J`Q?? P00000NkvXXu0mjfqe?Cy delta 2625 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 diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_light.png index ed2f3349496df262e619cc22e0781865a5c22cf3..cc0cb28d0c59afcc2dd9bc91cd5706db5ce1ab8f 100644 GIT binary patch delta 2837 zcmV+w3+nW;6}c9WL4V^(L_t(|ob8=kZ{x-phF_ARtg|Rl2V0VD)rWe$2WQhDEzklD z&|S36MS=nedegtu%l?Ai7D$001=ETh-!UH>~641!@8TWul` ztcD2I9MEjG@a)+hMkD1qjiP95Zmxsp4v%NPoAi9Yoy9zT`W&53N0oMbT)>?>+lWLWw%H{Ohx+M~G;>=fVSoP+^?F^E zRww|Qn;S?Zc-!o9DuclQUwyR;K)nt4q+G$<_jhcwYkwBZyyAW0Vh0GehH z=VRDdUjs!^W9K8wGP>Qa?r)7o(^kQWf)e6ugnzC43T>-~MZL_P{Y-z*_00yE6Q550#`)#oc7PdDdW-eH2=P-p( zC znF?05R>#5qf$D{5n#R`FCOFRe#&CXqZukNF{eQl#mG0!Ef}^8jRZmIN47P7?A!4KN zMifQe^#2R1v$1jk!swm*ef=_WE4#b@RS7&UMSf@JuBm(mgMndt6GhQ|f>o>5G%utm z3Tm~+M1s}r3OGGI#Z{J}P!Jp!wPffyZK}1JW>~K%GHTVjt%5Z!vMi(7Y~lR;a_G;{ zh=0d8Tc;xcP^naP7a(f2y6p=Py6uIhtEMTVO@BJaaoFD8vc)CCfImvVQ{eSO%jrDv^Hxr}YLy{zPyMk$F+u`9cYV|tg%V9j@ z@gS8yLP+a1%Rfnu?Qa=D7(P(7+xTl!1%9>eQ3?vc#)R-!1P zTrR89u5B>(QyIFOQ9KxqM+GqB9Y(`kH<{qrzi?qtu{n) z09=mDI4*)v$X@3+%d!~gtRU00`R`b-H#9;w5Jempisl6C;Hab#tC#uYvwvZAc?p|q zD^m+lyZTW2zu6lUHx8mG;_I(>HRqgND=~5voSpR` zNs{T&Ou1Z9P3LHaMt>@qv^`wGDr8wkr)?DV-!d&&q9~$NI=K!#0RX?>k6bPTuh;v7 z?X?4yU>y}Jul*0jlNtcP=kra^su@$O)p2riqDk-d`=b*F^$FuIxs`TPn>C68uGn8_^JYS#}zC0HN+_C0+2)L5`63V8qFFMtVG2b>DS zFqQ?I@_ZbHLVr<{9tZ^HY8pKbu~-bHl6I;c3WX*TnWd#g(;)|Lf}$wI;xUcW9hddW zEPlTq8yo8uS?3H?f|Xy%;NuS;;*-xFX#y~5n#M;TJivN>8Kcn%KHqerTm63HM(qZN zwcqc@{rfw*OZj%<^DTg)Xx%`?X^oV_;gBQC!b%nv7Joo9jP6QdAYi_}{&;i4kzoy1 zV${ZcW3+EgK+1z({uDcRHu3ovU*gGkdtev_TN|tR&98or1pi_?$Ta;|e!m|}OG~?(`TWWo7uBu@7mZ%-K~g)GDT+dPf*v=Q%OaP{ zT4z#PRw!SfKhTE$QB^EI5F<_Q9m;`Oo!g+iL+WdMM8N%_ZO+)Pir;Q2Tzm8vG4 z<2YAzKCH)svJ+yDg2ABe zSHist&&LrA26c}}cA}WhRIt*iBocglW|vSbEX;l>e_S$|MWj**$6Z2UG(xhwyvBv+ zk`+sd8 z30u|PjG#u7US#$L;?%3^cfWazp3aKhRn)!HAy?I#=S{t;o(R?)VC7wW1cH+xf;A^t zb=xt4;ADtk5eNj=FdtWX5D0`f0wP!h0>L#9!6Fa{u7L;^fk1E#M6d`1f@>gxMIaDd n0}(6&f#4d5U=aue*YH0u7PapOMh<5k00000NkvXXu0mjfIACUH delta 2701 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 delta 3181 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 Qk%|)L#$clw18Cg80Ap4WmjD0& diff --git a/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_dark.png b/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_dark.png index 30f4eaac57f544cc0b9238d6d6657610e326e20e..19cb391e79f229fbf65abfefff687d89664c040a 100644 GIT binary patch literal 6160 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 Xf7SeR^T*&wAHbgRJY9Y&@Y{a@J{;A- literal 6150 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 EPwz|l2LJ#7 diff --git a/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_filtered_dark.png b/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_filtered_dark.png index bbd46e6ea88ff2c3710e46bbbbed4a5a3178efca..a6f0c439657fed9e448e914966101f06fce9109c 100644 GIT binary patch delta 3554 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 diff --git a/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_filtered_light.png b/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_filtered_light.png index a4b6f5d57bf5e6303d57bd18885bdd93e1181023..50c24902764cf4b9c821624967fac7d361796801 100644 GIT binary patch literal 6093 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 $7e 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 Xt=xa@@jeFupWw4$XBxh~{L_B`z)@*~ diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_dark.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_dark.png index da6b13b24c01dae8865071a529bda070f2877767..fcca635d0b7ac886079649c09cba4ea446fec97d 100644 GIT binary patch delta 1402 zcma)+do&XY0LHgvSrkJJd9CT>kyp_?w#j36#E@cI57!sYqBRz|o~G6Bx)Z=v_#jCAyH!)XpNMt(n3z-FvcYVM{sIB?+t2Hz4c=E^Y)! zYgCtac-fW=HK>AP<%}qhY|y{?Kaoh)nyS0yJv^Y#X%Bk>eS419mIejFLE;h!TD%zA zcnCiT#QWHWHUfF+yegF{l_1(iR&#Cm`91R5)y}(3{gBXw8^**L&gG zjY>%vCXN!lirx{#$0J53WR1F&w)^DLg^5Z2rc^C)1*nz2^9SP3=NW7mS?#@`jn>Iy ze2gM69m)0UBLTUokj<4d%5<5UQKPP|E+`#K%spk)8>g3(+jj~re6ZL^@b~9^{)~7p zUSAQGgO8HW>*h%12HNR&A1&1Nzn!g5D4nBFifi_q1^B_v*I}Q`Y;6^IW)6Lu6s|up}>{&&i&7UWI9yR_>~xxEW@c{wAVbykJ!P)m zXV0#-Mmx0fYCEPIn6g^ku<<Z*jdU|@?YBgEI;URS--DQ5VzD1a+IQGWuM!=Ybjg28c zVtLZ7HW~%Pwnx>^zU6RChm80J+LJA_o#CUn-?PB(5VRBH{&b#G6Xjym)_mgBuw&nE z%LFI_48z)6mXJkpn9H27aoD&GMn~%sNWPeedrS`C6c%J5Ui;x06;H!oej`9rok?5G z{H`t>+m|u8v$JxC;Jrj*`!;Ir`xRe`t)BI!=8CN__tvot|2Ns2LH0+NlInJ~Ju1B2 zjbbEn9Wlq`z!WQiV8EJ}-tPJ^RqIuGHC&cfYSO(n-gI}rk--U>mGLIMx$4?NYA#E) z3NAaZ(;zcW18}$^+xTs>C1bB;M<^QbTG?3_5@7CfAmFQwcNDt`v@(Z@5X@eIpwD^1ueGjI7A$+!=(`h4BGw}$D; z%62Iwjv*iQoOCHwj=qvs7DB@!oKGs79WI#x`#3&h5uA++CEI$P`w>yI{n0Z+IkEk zIu4plh1x=6QI<6dXvnmqOn%gr4faA;xrSeBTs-Cze%|}=kI~S9q)0;bM4-0l2!R=N x{hh}y3!NJK%QNRB@Cgs1J?-c}LrM7`xUHB|+KfVRiYfs6y6}T5w$g!^{5R{xpg#Zr delta 1615 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 z zyBl!jvh3E|qk9b8cq*tR{1Z<8+L}DC3T3 znuh1kpZj$+`|+8_^}b^QIYz2*%`b0kY(Q02h$4S&MB`vt7HYK`3Wc*fRa7VxP_0%& zyCQ#*BngV5pi=2|-<}M7_GvgiC3B3pR)Qoa;aF|Bu8S8hUidpC5uIMOT1BZ;f~IMG zBYpC>msG>s1x z5{=Q?+8Q#M%vg^^3WWmF=`_~Y*F)RIVljWD(`h_=_ROD_nz=EyI4(0`{?<3_A@CTk zV`B4v{tm9Q|AgxN{5)>nym>BTITL+Ia=9F`*(@rRO61NK-Me=H05PWG%Fq7>f^_u2Q?!pqAR`bZ1zP!+etms?9kp5wi9}+eaS>fM)oOng z>+9<%7K@mgnmW<-Mo|cMof8 zYZHx&h(=c`mGJoSV{C2p#yrn`{3|4<7bhCm`-ay*PAFBlj?DZmzrMA#g~xx7A7f!* z0kX{VrbHK+q9|BcSU8kCwE5di9uY8sM39;N1=7Wz`}M7@Ej)Pe05dZ)?CON*f=Z=Q zC=?2K@ZdpT@146nt1f+5jvfYKQA+X3tE5am!Zqk(RdByl=RwL?7jL|cy6zogj_C%J9q9t6h+kQ zb?CZ2(HMz7M5?MHo6W+uZG8Xz_kF$W3ZjhsAN?8B>_-!g@qNbxG6r^M4?92oGaR$k zH~s3>tGIUU8nW3e8jS`TjYi*anM5>R@pv5RbQ+mV2Gwd6&z?Oy^du*`q@*yt@E3?B z&aW%^%ZdqPOgzUzqw;&SHvbo%*Z*9IqA0j}^(vDv4HSP4y#Vx;{p|(j34+i!zdLQP z?UAt`GoNUfQ3iwRwi(tes_9L+@A{jC+)2T-ObKTKTBuj+-|s+qMyl4eyimxb+_ud)RVE{f>q$ z)7>|MM*SNNZf9_xFiq3n-cd0HznBc}Z^3<8*L8n?pN_}la2yAwX+jVL7={7cw&A+2 zzfRBd(CKv0Znxn$4r;a9v8Rd?xlf>#??6&gN4BApV*)uNwoylW=Q%n%FW^}1Nryx9 z!I9)75>pFEPA~G|z%#-Gat`#`DmpvQVYaspT_+);@sX4il;jK&(~D5Ex3|tY^8}rrV}Ev^B|xt`~}m*@W6ORBB4nGnAeKL`X8!kS|) zfj}JDz}%CU8yL$e-VwkHe8(7T&kH;;yq*N$o8!(UGh# z(;cID)(x;Zq`*g>6bt`9w5Y+)tloDmD|IVJ)~bkz%eHHo#!DP0wz2k_Ibrzjv00RKt}l6J%V4>{L?v;dBl+r*=w?yW3?xWn3uuL?lj5~ z2n1{M!f+4~ zlH+cCk{tT(y?Krz`cwV=apy13!a44)e{j9o$)T}0xrUQvX zf2^Zbjz+k14ss2G5>&#hvqM}FE~KsGftN5W)(Cq%rx(wCZg>1`XVcab!vVtZ z7{+J!IBGOv-38by5-To4PRfj2PqGs6Qraw5IL}V89l2U2JMr=#uqN(`52Su{y50Xu zX4Q)d)w*%Gwv`E7!bCG4qnHB%@urmRkW`Fwl?WtWN}Whnk7!oUtgf)VLlREX*5Ua3 zY7++s$H!czb|6qP4knkC((PMN>fWPmg?9xlgrefmosZ=kAw9t`>_~4^XTu4kJP1xn zfK$xoAoO|P#z-iR&P&$;y{i_mLq|z#`nND13#EJH;$PHwbf{;PXhasxyU+2jc4}Mo z)(N`O0PY7~0MlG0$z+aBbr?kxJ(w6k4{|+GG{1*%r40u*0@B^8mjWo%b#1!MucU0wMdj}O(GD(AOC}%dR4bl@iL*z zrnnMFk`voSmWD5PQ|ws$OBoz&fp-#MLSubPyXcf7YQV|zPH8A1{;riHz(sxq<@ zfBpJ3EZ>qvk$T!@>cMO)m6}e;;8RN2Syj_kZDUgI+_~d;?b^pLUnC}0cI-V)k!B}W zkPVHE8=rKhpWCYZVoE=4lVz<7Jm?087<-cwCTx!8i0NI5t5YrMv^VaM*q@1nv%-|R zbw-xKx2cwB6^Zo2{hxIAPX7R_a}AK3NBM2rI#l6rCbKBz@VFz$x{EaExv$jfbX(Tw z=%{bX#ZceAx(rXvR7>T=82v0jQ(*MeTr_BvjuDKLd2sQVd)%JaObD5$r8W8U*FhL> zPoo+s&JuRU)|oBTKNk%$RLp6=>K>akI_l8y3WhqSrKR;QYFl+=QCkU{5J10K?=(iI zn^au^95Y*qug?>@7b6D{NrgmR%&0$zYRN>j{^Gnn-I^3YU;mm&tjHVOZ*i4~;rFxj z_LIJVkOQSWcnZi1=~VC5cTByBu7i8T#mRm07Gty79(W@$FbD9vSN8mQ7GAIa`ZpEB znMRsvUTbCUYDF**uroe32m1QlA9yr{2TiwE+E`sS`g`Dt3e89nD@Xm}o{kPq)ja;4 zogM4Y@U=)eU{X_GpOlou^*y91XDJjVtMm~2h|)sNJcwwMThJ)T>};*+9-6`n(aVpe z1n31$w?3$^uOAAN+n+Qj^0mYtUR5h|D&Ej&rRoO6bUv~;z3@IWVr}Z#tqaMxnaSn| z6>)x*JPR`?5@cdxqGNMY6NyC5=n5|n=xM5 z|7o2`T0NqKob>&1v?EZM?3oa`)k9T zswC>hUmMIkuA{h$^sq_`KllheHt85Dg zQMC`aFJeMbC(gy2=YHO;v0TT4WV^sn@62-m)$ZQ9i5U2RZLDB zl7y{+3jmoRsbkz#RaKlHz)gv(C^i>tYG?au>-}vTZhf}4jB{Ux2+bnr70&!IOIMiy0#X@Q3_hSYz{7IZZ*aHA zFA_*qwbkPNgM*B|hiC2{9qNG$GhV&hWVR*4WDVBEWoJ(IN@B3_R}NpgPmPTkR$Vbs z%z1Ll1w*u|ck1fy=1%KU^!E0)4sEj&H|-x7AUVQs1Ox=U{+?s5RMXfve+B~TeY~a~ znUU%PJ5$5_7H?pIgG+SM-0=@SeX`OOZjMndc$=Sp@@B#BupoJHS|G(o>LU@xTRwWr zBa1AT-WBxY-{a_*UqeTNYKv+rE=MfOX_{9DIE3?xk39WocQ@{Qo(0a!i|dVhThVX1 z@_||>mrJ%iP$X^SS)4}4?Mu?>EgFM&iVykw#$Ueb-Y%Kd%NDaZ9ta}`(Gj%6MmcIW>RKdmS~nG_puKCh~l;L^{-H) z@`|*OCPAw9#=`I^AYI_EdCwx>jv!2EWsD8E-ai)Wi))VN+-I6v0-+&IGOJ&kj@gOZ zy`hyXvFtsRSdyZ758(dWnS?WM+NK z$?eF9sq2#-9`dz?H=hJ^C;t3$60O|a;~z0qvpEym2v1W~!fwu=hVrSTabk1V4-cYW zS6APNjXmh~Xi0!6IP9PDtUgQ-;yQDm&$B~@oXTQ}h3-ttIG07=s`4$EhS-!*7uoH= zVJqtM3RR$Xg;NM!Hx6x|C*uqq92VXSJ2>F3Nr;QU;F^=pxmyShv7s--8K_TFt@{8O43Kqqu1?_yE6*gaix4O78w zFK|ntSr48EA6-mj1FBe3w{|03EI%TY6MLN39lwN48k90NbY*(teG5du9Lc!$hJbg~ zOSh>^G;p(T=>fW#iB46>%}c?GyC>gn%9>mJ97BbMSi*e%LH@kC*8028kg3#L2_opD zatkpxyhZ!QPa}oVrz~OfhHt7$$ZV*0G`lwzs#qZNyHgoZ&}asqbC&ZELJ4K^gb083 zA2)cSM@z1{iVLd<>gRUpNtkz9mk}uPlmCF`Szn6=S~Bj^X69t#VrS&K3m)|gBROoU zK+rH0ff<6p8tQ+7L?CBt`8h!x9kZK>i;1iRn6Zcl4+5JE`&Z;N*Cn*jwNDDAbNdSA zi!tksu>)WY?(^5~6{4F(#|VUhW71HlsGX7}41*Ep6XO*XHSGW!V=y`D@go0=<-Vh1 Y5(VwoCkxW!fxaAsHMPc68(&ZO51Y}Oh5!Hn diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_dark.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_dark.png index c9b7c2dc00291f08714ef3fbaab28a2288b4593d..0e37db5c46a91bc8044c87e303d8ee6853a72276 100644 GIT binary patch literal 4709 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 PCj#ke8ox8OyP*50#b?V|Rmt%zDBG&NEs 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 diff --git a/packages/stream_chat_flutter/test/src/scroll_view/draft_scroll_view/goldens/ci/stream_draft_list_tile_light.png b/packages/stream_chat_flutter/test/src/scroll_view/draft_scroll_view/goldens/ci/stream_draft_list_tile_light.png index 37d559d977a5afd551c817b90ef575f32908cf00..af3a1fd7f76043dbea3ef913250d7eec4cb6387b 100644 GIT binary patch literal 1236 zcmeAS@N?(olHy`uVBq!ia0y~yV2S{;D>&GIq+V#xVIaj)?Bp530R%N1DIGvQV{wqX z6T`Z5GB1I0b)GJcAr*7p-nGw;4wpFaaPdD+kJpNQuG|MBzKLrz@;IspoVcXwz2Jhs zAk)_i8{gensG9J}_tx5MG5=!;E83NWCS7`TrGmq6+PAXI1tO;}O^v%-eeUY#HM`b- zX__YQ-gJ5Os#RV<$Nl{KLgVkAy>rXHf4g+=W8vGM?6YUTJpIt%Zv7{f+C95=O^|*5 z_20R3=lpJaGccS^UV7hu{+&HDukrC;&R@R#<`_0I(iK-hI8m^VaWkCodkI#h zGgSu64O; zzZ05ZcT4Vc0oiY*+77n=`4(A_kuR2*DSIa{GR(ai26p;oCFR$@)<(ZvygFR{90$-Z zpBE{;{`9YImz|7Xj}9;_bXq%3th%>vt`^A94JNa?z=BiNlt89+{X&FORqLU*`9pzpQ!kbY}Tl62PcqgQ~BrrP2J?mZbU2KymgTujV zY|SP0<>s}ezsr8TxwZAyXJ9ZqkogP_9f{;?pwM9upP>r!-vO3VaOn6f^aFXLLDN?n z6le?DGQr-xVge4@13Y@*P%v0r2=YC{vx2ykoo_6a^{Uw#ivE6YdePY^>YLE3zJXyu z&U>Inu!DZa^}3y97h`a^{&TW5gM$Ik{Q^F~AY>5+T7M8|x5Ea0HQNmv_gy`s#^7*q z-hPR#d!5YYfK<$`XkukB=zY6>e`a;-v6}yPw~Oz4$HL8^FasFB9BM!pG=UtO0CXvN zLVH)OTGcIgBjSEmo;1UPi1YD1x8f9~g_*e-8mj)daG*OBi`xf<(1v;jr>_x*H*J=a Q0TxRPp00i_>zopr0Q&37jsO4v delta 934 zcmcb@d5Uv_V||IIi(^Q|oVR!VvqQoq4m>peyLiUAq$D=(Nwa>ic0F)%V?DT4df`&; zgZTMj%hqxN=Pw0UG}!U3II4-N%+N^P3(V%gqxEE9dw-kq^7KELy`&FzPK zzqj7}V{xXyDl{~dlOe(H-_6_J^){80%(id4bnT$y+n>K*zkb<%uwLPA^)Hj!t$S=H z%P#-=^VqRtZkN3o7`hkVe!txN`ugQl@813L{bONOX;D!2zoQ=uf4x6DyCg4NJ$7%k z`P|(FiD|q2yb>50im$)@e)+m?pH3qygNc&SrgeAr%{^DX+xYfYJ&=f+)~0=T?)hbx zze^F0Wa4JX2ysdITVFIOG^L(XhhafsZZ}9{%0l0Xsv8&@a<-^$Vi9AQFxAXu+5skp zO~F+uKvfFfs_tNJXcm~8t?CBmhDU+83pi9=LENrDFECd_6|A>way6LiVF}jTH8mT| z&9DUP?V28459W&&b%7kP#Z3?FfUuK_AO~Eq@&)UPT6yw>Py~ZRwy7xC@mo)v*j4k` zE$Y=*w{sjotG_N%dj02H*)AK2o*s}fIxQV1R^8hxtqn3{gTXVft_Y3K9bm~hkzhl` zF14R{_5N-B(r|b8gi;>v$MbadVBlUXYF8LiQNiXKRt}SdCR9i zmA_tpFi-)T{9#(v-Y;j*&Q6?ob>*s6vu4D&7S-1pyaUpJ?& z_M6UI8QH7z_V3>}Z{O}R--M?w35*V(|JD99`dk0)#l?_`rbZi<@h z>+U^xd4YlDS}AYVH%EGvjg+4;s&S+AO{ zp{TaTam#^I9S4{iHq`=yYpNhHIGpmD@8y~Eb0-I~c=N0Y4h_BfMTmhR o_YuqFi_EH%FEFbiVR5k=)%v2r%xCY;$z=cnPgg&ebxsLQ07h7$4FCWD diff --git a/packages/stream_chat_localizations/example/lib/add_new_lang.dart b/packages/stream_chat_localizations/example/lib/add_new_lang.dart index 2df5e2e888..f627260961 100644 --- a/packages/stream_chat_localizations/example/lib/add_new_lang.dart +++ b/packages/stream_chat_localizations/example/lib/add_new_lang.dart @@ -688,6 +688,24 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { if (isLive) return '📍 Live Location'; return '📍 Location'; } + + @override + String get fileAttachmentText => 'File'; + + @override + String filesAttachmentCountText(int count) { + return count == 1 ? 'File' : '$count files'; + } + + @override + String photosAttachmentCountText(int count) { + return count == 1 ? 'Photo' : '$count photos'; + } + + @override + String videosAttachmentCountText(int count) { + return count == 1 ? 'Video' : '$count videos'; + } } void main() async { diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart index 7b405bc40d..683fd22838 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart @@ -648,6 +648,18 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { @override String get videoAttachmentText => 'Vídeo'; + @override + String get fileAttachmentText => 'Fitxer'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'Fitxer' : '$count fitxers'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'Foto' : '$count fotos'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'Vídeo' : '$count vídeos'; + @override String get pollYouVotedText => 'Has votat'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart index b5672ae747..9738984398 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart @@ -646,6 +646,18 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { @override String get videoAttachmentText => 'Video'; + @override + String get fileAttachmentText => 'Datei'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'Datei' : '$count Dateien'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'Foto' : '$count Fotos'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'Video' : '$count Videos'; + @override String get pollYouVotedText => 'Du hast abgestimmt'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart index 9e3e4b75ee..724b2031ca 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart @@ -646,6 +646,18 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { @override String get videoAttachmentText => 'Video'; + @override + String get fileAttachmentText => 'File'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'File' : '$count files'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'Photo' : '$count photos'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'Video' : '$count videos'; + @override String get pollYouVotedText => 'You voted'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart index dec8f5bb2c..e2e243db56 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart @@ -650,6 +650,18 @@ No es posible añadir más de $limit archivos adjuntos @override String get videoAttachmentText => 'Video'; + @override + String get fileAttachmentText => 'Archivo'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'Archivo' : '$count archivos'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'Foto' : '$count fotos'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'Vídeo' : '$count vídeos'; + @override String get pollYouVotedText => 'Has votado'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart index 5de6c28ee1..9b4c0653a8 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart @@ -652,6 +652,18 @@ Limite de pièces jointes dépassée : il n'est pas possible d'ajouter plus de $ @override String get videoAttachmentText => 'Vidéo'; + @override + String get fileAttachmentText => 'Fichier'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'Fichier' : '$count fichiers'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'Photo' : '$count photos'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'Vidéo' : '$count vidéos'; + @override String get pollYouVotedText => 'Vous avez voté'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart index e04feb04d8..4ec51bcdab 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart @@ -650,6 +650,18 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { @override String get videoAttachmentText => 'वीडियो'; + @override + String get fileAttachmentText => 'फ़ाइल'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'फ़ाइल' : '$count फ़ाइलें'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'फ़ोटो' : '$count फ़ोटो'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'वीडियो' : '$count वीडियो'; + @override String get pollYouVotedText => 'आपने वोट दिया'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart index 7a96d1c99e..c76b60500c 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart @@ -655,6 +655,18 @@ Attenzione: il limite massimo di $limit file è stato superato. @override String get videoAttachmentText => 'Video'; + @override + String get fileAttachmentText => 'File'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'File' : '$count file'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'Foto' : '$count foto'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'Video' : '$count video'; + @override String get pollYouVotedText => 'Hai votato'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart index a8aa033076..2d48316deb 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart @@ -632,6 +632,18 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { @override String get videoAttachmentText => '動画'; + @override + String get fileAttachmentText => 'ファイル'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'ファイル' : '$count件のファイル'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? '写真' : '$count枚の写真'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? '動画' : '$count本の動画'; + @override String get pollYouVotedText => '投票しました'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart index 9a33f803b5..d325b3a88f 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart @@ -635,6 +635,18 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { @override String get videoAttachmentText => '비디오'; + @override + String get fileAttachmentText => '파일'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? '파일' : '파일 $count개'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? '사진' : '사진 $count장'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? '동영상' : '동영상 $count개'; + @override String get pollYouVotedText => '투표했습니다'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart index 321f7c42ee..b6e22e4a32 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart @@ -634,6 +634,18 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { @override String get videoAttachmentText => 'Video'; + @override + String get fileAttachmentText => 'Fil'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'Fil' : '$count filer'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'Bilde' : '$count bilder'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'Video' : '$count videoer'; + @override String get pollYouVotedText => 'Du stemte'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart index a899386862..27a3e98ea9 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart @@ -649,6 +649,18 @@ Não é possível adicionar mais de $limit arquivos de uma vez @override String get videoAttachmentText => 'Vídeo'; + @override + String get fileAttachmentText => 'Arquivo'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'Arquivo' : '$count arquivos'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'Foto' : '$count fotos'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'Vídeo' : '$count vídeos'; + @override String get pollYouVotedText => 'Você votou'; diff --git a/packages/stream_chat_localizations/test/translations_test.dart b/packages/stream_chat_localizations/test/translations_test.dart index 0c56092af8..8dde5986ad 100644 --- a/packages/stream_chat_localizations/test/translations_test.dart +++ b/packages/stream_chat_localizations/test/translations_test.dart @@ -301,6 +301,10 @@ void main() { expect(localizations.audioAttachmentText, isNotNull); expect(localizations.imageAttachmentText, isNotNull); expect(localizations.videoAttachmentText, isNotNull); + expect(localizations.fileAttachmentText, isNotNull); + expect(localizations.filesAttachmentCountText(3), isNotNull); + expect(localizations.photosAttachmentCountText(3), isNotNull); + expect(localizations.videosAttachmentCountText(3), isNotNull); expect(localizations.pollYouVotedText, isNotNull); expect(localizations.pollSomeoneVotedText('TestUser'), isNotNull); expect(localizations.pollYouCreatedText, isNotNull); From 2df946e22c788d7c43350a7571e804eedb189099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Bra=C5=BCewicz?= Date: Wed, 18 Mar 2026 14:23:45 +0100 Subject: [PATCH 27/33] fix(ui): updated unread indicator to use core badge widget (#2546) Co-authored-by: Brazol <5622717+Brazol@users.noreply.github.com> --- .../lib/src/indicators/unread_indicator.dart | 18 +++++++----------- .../ci/channel_header_bottom_widget.png | Bin 1787 -> 1908 bytes 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/indicators/unread_indicator.dart b/packages/stream_chat_flutter/lib/src/indicators/unread_indicator.dart index 0ed9d5dbc0..2ae46f73cc 100644 --- a/packages/stream_chat_flutter/lib/src/indicators/unread_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/indicators/unread_indicator.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template streamUnreadIndicator} /// Shows different unread counts of the user. @@ -31,7 +32,6 @@ class StreamUnreadIndicator extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); final client = StreamChat.of(context).client; final stream = switch (_unreadType) { @@ -65,16 +65,12 @@ class StreamUnreadIndicator extends StatelessWidget { builder: (context, unreadCount) { if (unreadCount == 0) return const Empty(); - return Badge( - textColor: Colors.white, - textStyle: theme.textTheme.footnoteBold, - backgroundColor: theme.channelPreviewTheme.unreadCounterColor, - label: Text( - switch (unreadCount) { - > 99 => '99+', - _ => '$unreadCount', - }, - ), + return StreamBadgeNotification( + size: StreamBadgeNotificationSize.xs, + label: switch (unreadCount) { + > 99 => '99+', + _ => '$unreadCount', + }, ); }, ), diff --git a/packages/stream_chat_flutter/test/src/channel/goldens/ci/channel_header_bottom_widget.png b/packages/stream_chat_flutter/test/src/channel/goldens/ci/channel_header_bottom_widget.png index 7f62afc9b330695d6e799c74c1314aa7f6f05dc1..0bd5c7fbd8040135a76aaaf80b898fa7977d37bd 100644 GIT binary patch delta 1865 zcmZ`(X*Ao39u2j&lGfUxq-|>J2x2daDbY?*Ypd3wLQ1S{l@R;~9bzk?RGy+nJ8exQ zA+?5xoe`Ds5Ndn2YAmq}Nr#jEq^DdhjP`Q;$N@ zijJt{Wwm@f>84{xvY*=+vK^BWh)5$6?ON;{q6h9iy>GVgAopDU$}aAy!?8I6!Ll>k zX<=nAat}CL$fQev*f~j}pzEkG9v^9&r~-4b`X{&|7bO7GZ7?ZuXr5tzeiRffvHHAh zZXeWva*}$Kft-L9R}%+R)4G4ZQmOWPmy={nZn$sF#OtZivN_?=sN83vC~G+B{ci!Q zDY-i4T}FjWc9gdl2e>R*S2&=2RhEXlMNM$=O%uB=QBh-)v9l@1wG$b)m(19y729fp zkeAsQLwBt&>av4YU@hT-p3E)qkb)Ag@?O=d@|YI{FQJbA{9iBzBhDdou`s! z$a9%GLlptL3$z#M9|TZ{d1maEN!)=bdt>@&ZPuy&5R6^ee{@f^nok7rNHu}Rp0QrOQ$7fTe+*%j{+AWN4@$2n8a$4NRObw;A+d{wbGZNi;v@A4Eh*FUG3#E?UnO}itHUYnHJPtBT?vbrfU$)?9sTn zlJ?jANv2bJ_uNNQk-8&j@f_gr1I{;oXZVUTDQbES9x(IvETjrSkarBeCT&2E?XXuJ zTX|4mP|IwL+t=05&`>nrmY6o)No92JFVy*Vc=GTo#+Ai};^Po{<}pew#AAHY!MDrM zB2qDx_Hf|w&eA=`(cA1zTFc}-X8%;S=_q~UHO8^jqV7udWGFFgSTaBDUlh z`;L9v&$=A@3ttZ)IYQ;Vny>_o`{W4jpo)F5NoQ}WsSds;5ZM;qtdP$Bf{$LCTG;0P zThTNMPLe{RZ^Gp|3waN9=ltSI-Of7vZRs;K)fA(*B8A*-!Va}tY%7o;Ga8jk4lMHf3pN@ zOlNr|c~t7w;zlZD9|zIbsFsN;x^w`-v(i;@_&Aujk8IA1nc7eJkcOO{P-*2hzlLe+ZxpLko$0ROrq7N}nv z;hoCAsE#Ow+GjVO{2R0$=SKO6bsY24DsHXK>zHkR+KDSZ8C2>drkVm2qKr$SOJzDI zcKh0l#No%un8{W?I7&CIn40D$=hWAh&Qs0Vh>t^wWwwUW>z`ZcW1M1U7#?f2p`I7MI?y| sC!+s><^SnFv8w2>NpZS}B!PS=`uUDmJS{Ub5p;65t?iLyOW&mb0#&J+E&u=k delta 1743 zcmV;=1~B>b4*Ly|Kz|0MNklQw{1j(-Vd1M4>i77+n=gX&~x<8&}ZrCNoqm!PW27GXi)!*w0lvl+Ub8hY(Y zs=>v;BHDAI1jd$+aEsAQ=I-Pi)wZhkCua(R)qK z$EcKxc(PkrYo?V=VBNe0y#6cT=o8?B_mW-v^=|=R`Xca)*OottD6t5@QQyXF?xN9b zZRMWlqkj?1s*Wdnm6c~&+4Nz(`~EVlR(12%attC`Nq?|_?RZ-YD*$M;TA0l}oD3E# zkFn9fDijJ2eZE<1K>p#6>kt0spO(QzM2SphKF&t7jV}ZLpL(N-J0lAdJ6P%ZtpX zmX$<%s6Viw7fZ3e4SeqTmsYyRhwuOPv40mrK-UeNj%<9cQ%N-T)&uLyrE+?M9oDdXotz?a*TVE17aa$OOC{>@{ zv42oC&9$3C(@-cBP%c%`KeG~DxAnmK$9$IFVAr2cZ!Dq<&axNKbtCIC?kO4uCNn3| zHQNs?J6WPW7W=S>VsX6?eh?*oH*k|QO@rr4xLz3hnym-c@6P)0q7;^er4sP{@1#)@ z(Ssgoa5WT3w zo_n$XKa)=PU-xh0!*!O1*PjMn_(7T_5nX7$A3_v%Z-e225b*u@OTtVRv|$wC8#n%r zH?Ca6t6zH_N+IUA7D50&{vq(QpDuqVqFDSuqO^$}_12^afq8-s(#)Q~Ia1>1pMTuK zU&d$n+v{&Ex0ZR__A;M;3iuwkmmyk35QO)33f(0_Q4j<>KEt}-2eT>g+ACR?L3E+T zUBv9V-J2s4QR-Axft2xQGTS1<5Nd*hD;QWrdq7hJ36F|df}K9#Z#&vmmd<9{Kp=b}=M-yg=nBH9B@(}3$Hdv9+WT*pD98ovXd zfkm_j^r8Yy6$k=9>r(Csd>=(kKrhCB=$?67!>&tXA0nb%P_G)WT^FjVWnBsYz;zuo z%w+F3a)w2;CwiAm*fR@RA4Q702d&QMGd$5rwgWK(i)c@%0_b&Wm|4>p|9`z%mNiAM zQ%n4Y-A2zPEW~WD-G#&_+6OWM{`t`WL^br^5vX69@IicF!g}KOf^#!$7xHTIqVuu!#0h zw_QTDT*P=h+}b_IqakV)eShV!0t1U^e_g#?LAhjLaz5Vpz0S|asFX`MxV+vme_Rq0 z?Xd{J$zXvyBMZ$|C-n#MQc8@519UqkdhOEM2aGYWhzJuqz~S)_x^AFq)?$Cd|0q!u z!Lp`sT?bz{Xro?9r$?QEMMMCAAOuc_Htr0kC|9b`bpxuFtSdh7eSf&FgSkD!YD(IYL~5M#^1Z0;cpLIj~i5H5EvRTTw_Dxe7k)k+Z!(?Chz?7r~~ zEF#Jcwlxq@ZZNQjC`dOLSVWW?3@jqb4F(nwK^+6#O Date: Wed, 18 Mar 2026 19:11:54 +0530 Subject: [PATCH 28/33] refactor(ui)!: stream message widget and reactions (#2547) * refactor(ui)!: redesign StreamMessageWidget with composable sub-components Replace the monolithic message widget implementation with a composable architecture using dedicated sub-components (StreamMessageHeader, StreamMessageFooter, StreamMessageLeading, StreamMessageContent, StreamMessageText, StreamMessageReactions, StreamMessageDeleted, StreamMessageSendingStatus). Introduce StreamMessageWidgetProps to encapsulate all configurable properties and a StreamMessageWidget.fromProps named constructor. Add nullable attachmentBuilders support on StreamMessageWidget, StreamMessageContent, and ParseAttachments, with a global fallback in StreamChatConfigurationData. Add configurable reactionType and reactionPosition to StreamChatConfigurationData (nullable, widget resolves defaults). Reaction overlap is now platform-aware (disabled on desktop/web). Remove legacy widgets: MessageWidgetContent, MessageCard, BottomRow, TextBubble, DeletedMessage, MessageText, Username, ThreadParticipants, ThreadPainter, UserAvatarTransform, SendingIndicatorBuilder, QuotedMessage, PinnedMessage, ReactionIndicator, ReactionBubbleOverlay, ReactionPickerBubbleOverlay, and MessageReactionsModal. BREAKING CHANGE: Removed several public message widget classes and the MessageReactionsModal. Consumers using these directly must migrate to the new composable sub-components or use StreamComponentFactory. * refactor(ui)!: rename `StreamReactionPicker` to `StreamMessageReactionPicker` and update related components * chore: merge fixes * chore: Update Goldens --------- Co-authored-by: xsahil03x <25670178+xsahil03x@users.noreply.github.com> --- melos.yaml | 2 +- migrations/redesign/README.md | 1 + migrations/redesign/message_widget.md | 499 +++++++ migrations/redesign/reaction_picker.md | 48 +- .../stream_chat_flutter/example/lib/main.dart | 133 +- .../example/lib/tutorial_part_5.dart | 6 +- .../builder/attachment_widget_builder.dart | 26 +- .../builder/mixed_attachment_builder.dart | 4 +- .../stream_chat_component_builders.dart | 2 + .../lib/src/indicators/sending_indicator.dart | 2 +- .../message_input/quoted_message_widget.dart | 4 +- .../message_list_view/message_list_view.dart | 389 ++--- .../lib/src/message_list_view/mlv_utils.dart | 36 + .../message_modal/message_actions_modal.dart | 57 +- .../lib/src/message_modal/message_modal.dart | 18 +- .../message_reactions_modal.dart | 105 -- .../lib/src/message_widget/bottom_row.dart | 295 ---- .../components/stream_message_content.dart | 213 +++ .../components/stream_message_deleted.dart | 37 + .../components/stream_message_footer.dart | 68 + .../components/stream_message_header.dart | 112 ++ .../components/stream_message_leading.dart | 33 + .../components/stream_message_reactions.dart | 88 ++ .../stream_message_sending_status.dart | 68 + .../components/stream_message_text.dart | 69 + .../src/message_widget/deleted_message.dart | 59 - .../lib/src/message_widget/message_card.dart | 270 ---- .../lib/src/message_widget/message_text.dart | 70 - .../src/message_widget/message_widget.dart | 1316 +++++++---------- .../message_widget_content.dart | 446 ------ .../message_widget_content_components.dart | 6 - .../src/message_widget/parse_attachments.dart | 18 +- .../src/message_widget/pinned_message.dart | 52 - .../src/message_widget/quoted_message.dart | 47 - .../sending_indicator_builder.dart | 80 - .../lib/src/message_widget/text_bubble.dart | 73 - .../src/message_widget/thread_painter.dart | 53 - .../message_widget/thread_participants.dart | 28 - .../message_widget/user_avatar_transform.dart | 58 - .../lib/src/message_widget/username.dart | 31 - .../indicator/reaction_indicator.dart | 123 -- .../reaction_indicator_bubble_overlay.dart | 60 - .../src/reactions/picker/reaction_picker.dart | 197 +-- .../reaction_picker_bubble_overlay.dart | 60 - .../reactions/reaction_bubble_overlay.dart | 89 -- .../lib/src/stream_chat.dart | 93 +- .../lib/src/stream_chat_configuration.dart | 35 + .../lib/src/utils/date_formatter.dart | 6 +- .../lib/src/utils/typedefs.dart | 28 +- .../lib/stream_chat_flutter.dart | 16 +- packages/stream_chat_flutter/pubspec.yaml | 2 +- .../goldens/ci/sending_indicator_0.png | Bin 200 -> 198 bytes .../goldens/ci/sending_indicator_1.png | Bin 200 -> 198 bytes .../goldens/ci/sending_indicator_2.png | Bin 200 -> 198 bytes .../goldens/ci/sending_indicator_3.png | Bin 200 -> 198 bytes .../message_list_view/bottom_row_test.dart | 76 - ...ons_modal_reversed_with_reactions_dark.png | Bin 6068 -> 6068 bytes ...ns_modal_reversed_with_reactions_light.png | Bin 8695 -> 8695 bytes ...sage_actions_modal_with_reactions_dark.png | Bin 6060 -> 6060 bytes ...age_actions_modal_with_reactions_light.png | Bin 8773 -> 8773 bytes .../stream_message_reactions_modal_dark.png | Bin 8707 -> 0 bytes .../stream_message_reactions_modal_light.png | Bin 10915 -> 0 bytes ..._message_reactions_modal_reversed_dark.png | Bin 8756 -> 0 bytes ...message_reactions_modal_reversed_light.png | Bin 10801 -> 0 bytes .../message_actions_modal_test.dart | 15 +- .../message_reactions_modal_test.dart | 327 ---- .../message_widget/deleted_message_test.dart | 214 --- .../goldens/ci/deleted_message_custom.png | Bin 945 -> 0 bytes .../goldens/ci/deleted_message_dark.png | Bin 699 -> 0 bytes .../goldens/ci/deleted_message_light.png | Bin 688 -> 0 bytes .../goldens/ci/message_text.png | Bin 872 -> 0 bytes .../src/message_widget/message_text_test.dart | 253 ---- .../src/message_widget/username_test.dart | 23 - .../ci/stream_poll_options_dialog_dark.png | Bin 9936 -> 11341 bytes .../ci/stream_poll_options_dialog_light.png | Bin 9630 -> 10673 bytes .../ci/stream_poll_interactor_closed_dark.png | Bin 5719 -> 6925 bytes .../stream_poll_interactor_closed_light.png | Bin 5568 -> 5361 bytes .../ci/stream_poll_interactor_dark.png | Bin 7757 -> 8893 bytes .../ci/stream_poll_interactor_light.png | Bin 7522 -> 7290 bytes .../ci/reaction_indicator_icon_list_dark.png | Bin 1644 -> 0 bytes .../ci/reaction_indicator_icon_list_light.png | Bin 1827 -> 0 bytes ...tion_indicator_icon_list_selected_dark.png | Bin 1783 -> 0 bytes ...ion_indicator_icon_list_selected_light.png | Bin 1899 -> 0 bytes .../ci/stream_reaction_indicator_dark.png | Bin 1449 -> 0 bytes ...tream_reaction_indicator_fallback_dark.png | Bin 1385 -> 0 bytes ...ream_reaction_indicator_fallback_light.png | Bin 1744 -> 0 bytes .../ci/stream_reaction_indicator_light.png | Bin 1938 -> 0 bytes .../ci/stream_reaction_indicator_own_dark.png | Bin 1385 -> 0 bytes .../stream_reaction_indicator_own_light.png | Bin 1744 -> 0 bytes .../indicator/reaction_indicator_test.dart | 494 ------- .../picker/reaction_picker_test.dart | 32 +- .../reaction_bubble_overlay_test.dart | 219 --- .../ios/Runner.xcodeproj/project.pbxproj | 4 +- sample_app/lib/app.dart | 119 +- sample_app/lib/pages/channel_page.dart | 239 +-- sample_app/lib/pages/thread_page.dart | 37 +- .../lib/widgets/custom_message_actions.dart | 196 +++ .../widgets/location/location_attachment.dart | 12 +- 98 files changed, 2558 insertions(+), 5333 deletions(-) create mode 100644 migrations/redesign/message_widget.md delete mode 100644 packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart delete mode 100644 packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart create mode 100644 packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_content.dart create mode 100644 packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_deleted.dart create mode 100644 packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_footer.dart create mode 100644 packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_header.dart create mode 100644 packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_leading.dart create mode 100644 packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_reactions.dart create mode 100644 packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_sending_status.dart create mode 100644 packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_text.dart delete mode 100644 packages/stream_chat_flutter/lib/src/message_widget/deleted_message.dart delete mode 100644 packages/stream_chat_flutter/lib/src/message_widget/message_card.dart delete mode 100644 packages/stream_chat_flutter/lib/src/message_widget/message_text.dart delete mode 100644 packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart delete mode 100644 packages/stream_chat_flutter/lib/src/message_widget/message_widget_content_components.dart delete mode 100644 packages/stream_chat_flutter/lib/src/message_widget/pinned_message.dart delete mode 100644 packages/stream_chat_flutter/lib/src/message_widget/quoted_message.dart delete mode 100644 packages/stream_chat_flutter/lib/src/message_widget/sending_indicator_builder.dart delete mode 100644 packages/stream_chat_flutter/lib/src/message_widget/text_bubble.dart delete mode 100644 packages/stream_chat_flutter/lib/src/message_widget/thread_painter.dart delete mode 100644 packages/stream_chat_flutter/lib/src/message_widget/thread_participants.dart delete mode 100644 packages/stream_chat_flutter/lib/src/message_widget/user_avatar_transform.dart delete mode 100644 packages/stream_chat_flutter/lib/src/message_widget/username.dart delete mode 100644 packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator.dart delete mode 100644 packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_bubble_overlay.dart delete mode 100644 packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_bubble_overlay.dart delete mode 100644 packages/stream_chat_flutter/lib/src/reactions/reaction_bubble_overlay.dart delete mode 100644 packages/stream_chat_flutter/test/src/message_list_view/bottom_row_test.dart delete mode 100644 packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png delete mode 100644 packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png delete mode 100644 packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_dark.png delete mode 100644 packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_light.png delete mode 100644 packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart delete mode 100644 packages/stream_chat_flutter/test/src/message_widget/deleted_message_test.dart delete mode 100644 packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_custom.png delete mode 100644 packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_dark.png delete mode 100644 packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_light.png delete mode 100644 packages/stream_chat_flutter/test/src/message_widget/goldens/ci/message_text.png delete mode 100644 packages/stream_chat_flutter/test/src/message_widget/message_text_test.dart delete mode 100644 packages/stream_chat_flutter/test/src/message_widget/username_test.dart delete mode 100644 packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_dark.png delete mode 100644 packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_light.png delete mode 100644 packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_selected_dark.png delete mode 100644 packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_selected_light.png delete mode 100644 packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_dark.png delete mode 100644 packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_fallback_dark.png delete mode 100644 packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_fallback_light.png delete mode 100644 packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_light.png delete mode 100644 packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_own_dark.png delete mode 100644 packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_own_light.png delete mode 100644 packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_test.dart delete mode 100644 packages/stream_chat_flutter/test/src/reactions/reaction_bubble_overlay_test.dart create mode 100644 sample_app/lib/widgets/custom_message_actions.dart diff --git a/melos.yaml b/melos.yaml index 5fb9b8f50f..fde910433b 100644 --- a/melos.yaml +++ b/melos.yaml @@ -95,7 +95,7 @@ command: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 57785868e62299901361affbddd06f71253e872f + ref: c7a31449e8632ea43f8c769be95a30ef9393a792 path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 diff --git a/migrations/redesign/README.md b/migrations/redesign/README.md index 5964be74fa..d444374bcb 100644 --- a/migrations/redesign/README.md +++ b/migrations/redesign/README.md @@ -125,6 +125,7 @@ class MyCustomButton extends StatelessWidget { | Message Actions | [message_actions.md](message_actions.md) | | Reaction Picker / Reactions | [reaction_picker.md](reaction_picker.md) | | Image CDN & Thumbnails | [image_cdn.md](image_cdn.md) | +| Message Widget & Message List | [message_widget.md](message_widget.md) | ## Need Help? diff --git a/migrations/redesign/message_widget.md b/migrations/redesign/message_widget.md new file mode 100644 index 0000000000..f85c65e47a --- /dev/null +++ b/migrations/redesign/message_widget.md @@ -0,0 +1,499 @@ +# Message Widget & Message List Migration Guide + +This guide covers migrating the message widget and message list view from the old design (`feat/design-refresh`) to the new redesigned API. + +--- + +## Table of Contents + +- [Quick Reference](#quick-reference) +- [Architecture Changes](#architecture-changes) +- [StreamMessageWidget](#streammessagewidget) + - [Removed Parameters](#removed-parameters) + - [New Parameters](#new-parameters) + - [Changed Signatures](#changed-signatures) +- [StreamMessageListView](#streammessagelistview) + - [Builder Signature Changes](#builder-signature-changes) + - [New List-Level Callbacks](#new-list-level-callbacks) + - [Removed: MessageDetails](#removed-messagedetails) +- [Custom Actions Migration](#custom-actions-migration) +- [Theme Migration](#theme-migration) +- [Swipeable Message Example](#swipeable-message-example) +- [Deleted Classes & Files](#deleted-classes--files) +- [Typedef Changes](#typedef-changes) +- [Migration Checklist](#migration-checklist) + +--- + +## Quick Reference + +| Old | New | +|-----|-----| +| `StreamMessageWidget` (50+ params) | `StreamMessageWidget` (thin shell) + `StreamMessageWidgetProps` | +| `MessageWidgetContent` | `DefaultStreamMessage` + `StreamMessageContent` | +| `BottomRow` | `StreamMessageFooter` | +| `StreamMessageText` (message_text.dart) | `StreamMessageText` (components/stream_message_text.dart) | +| `StreamDeletedMessage` | `StreamMessageDeleted` | +| `MessageCard` | `core.StreamMessageBubble` | +| `TextBubble` | `core.StreamMessageBubble` | +| `PinnedMessage` | `streamMessageHeader()` function | +| `QuotedMessage` | Inline in `StreamMessageContent` | +| `Username` | Inline in `StreamMessageFooter` | +| `SendingIndicatorBuilder` | `StreamMessageSendingStatus` | +| `ThreadReplyPainter` | `core.StreamMessageReplies` | +| `ThreadParticipants` | Inline in `core.StreamMessageReplies` | +| `UserAvatarTransform` | `StreamMessageLeading` | +| `DisplayWidget` enum | `StreamVisibility` (from theme) | +| `MessageBuilder` typedef | `StreamMessageWidgetBuilder` typedef | +| `ParentMessageBuilder` typedef | `StreamMessageWidgetBuilder` typedef | +| `OnQuotedMessageTap = void Function(String?)` | `void Function(Message quotedMessage)` | +| `StreamMessageWidget.customActions` | `StreamMessageWidgetProps.actionsBuilder` | +| `StreamMessageWidget.onCustomActionTap` | Use `onTap` per `StreamContextMenuAction` | +| `CustomMessageAction` | Removed — use `StreamContextMenuAction` with `onTap` | +| `StreamMessageWidget.copyWith()` | `StreamMessageWidgetProps.copyWith()` | + +--- + +## Architecture Changes + +The old design used a single monolithic `StreamMessageWidget` with 50+ parameters controlling every aspect of rendering. The new design splits responsibilities: + +- **`StreamMessageWidget`** — thin shell that resolves the `StreamComponentFactory` and delegates to the factory builder or `DefaultStreamMessage`. +- **`StreamMessageWidgetProps`** — plain data class holding all configuration. Supports `copyWith()`. +- **`DefaultStreamMessage`** — the default rendering implementation. Composes the sub-components below. +- **`StreamMessageContent`** — bubble, attachments, text, reactions, thread replies. +- **`StreamMessageFooter`** — username, timestamp, sending status, edited indicator. +- **`streamMessageHeader()`** — pinned, saved-for-later, show-in-channel annotations. +- **`StreamMessageLeading`** — author avatar. +- **`StreamMessageReactions`** — clustered reaction chips around the bubble. +- **`StreamMessageText`** — markdown-rendered message text. +- **`StreamMessageDeleted`** — deleted message placeholder. +- **`StreamMessageSendingStatus`** — delivery status icon. + +### Component Factory Pattern + +The new design adds a **component factory** layer for app-wide customization. The `messageBuilder` / `parentMessageBuilder` callbacks on `StreamMessageListView` are still supported for per-list customization. + +**App-wide customization via component factory:** +```dart +StreamChat( + client: client, + componentBuilders: StreamComponentBuilders( + extensions: streamChatComponentBuilders( + messageWidget: (context, props) { + return DefaultStreamMessage( + props: props.copyWith( + actionsBuilder: (context, defaultActions) { + return [...defaultActions, myCustomAction]; + }, + ), + ); + }, + ), + ), + child: ..., +) +``` + +**Per-list customization via `messageBuilder` (still supported):** +```dart +StreamMessageListView( + messageBuilder: (context, message, defaultProps) { + return StreamMessageWidget.fromProps(props: defaultProps); + }, +) +``` + +Both can be combined — the component factory applies first, then the per-list `messageBuilder` can further customize or wrap the result. + +--- + +## StreamMessageWidget + +### Removed Parameters + +These parameters have been removed entirely. See the **Migration Path** column for how to achieve the same result. + +#### Visibility Booleans + +| Old Parameter | Migration Path | +|---|---| +| `showReactions` | Controlled via `StreamMessageItemThemeData` visibility | +| `showDeleteMessage` | Controlled via channel permissions (`canDeleteOwnMessage`, `canDeleteAnyMessage`) | +| `showEditMessage` | Controlled via channel permissions (`canUpdateOwnMessage`, `canUpdateAnyMessage`) | +| `showReplyMessage` | Controlled via channel permissions (`canSendReply`) | +| `showThreadReplyMessage` | Controlled via channel permissions (`canSendReply`) | +| `showMarkUnreadMessage` | Shown automatically when applicable | +| `showResendMessage` | Shown automatically for failed messages | +| `showCopyMessage` | Shown automatically when message has text | +| `showFlagButton` | Controlled via channel permissions (`canFlagMessage`) | +| `showPinButton` | Controlled via channel permissions (`canPinMessage`) | +| `showPinHighlight` | Controlled via `StreamMessageItemThemeData` background color | +| `showReactionPicker` | Removed | +| `showUsername` | Controlled via `StreamMessageItemThemeData.footerVisibility` | +| `showTimestamp` | Controlled via `StreamMessageItemThemeData.footerVisibility` | +| `showEditedLabel` | Controlled via `StreamMessageItemThemeData.footerVisibility` | +| `showSendingIndicator` | Controlled via `StreamMessageItemThemeData.footerVisibility` | +| `showThreadReplyIndicator` | Shown automatically when `replyCount > 0` | +| `showInChannelIndicator` | Shown automatically via `streamMessageHeader()` | +| `showUserAvatar` (`DisplayWidget`) | Controlled via `StreamMessageItemThemeData.leadingVisibility` | + +#### Builder Callbacks + +| Old Parameter | Migration Path | +|---|---| +| `userAvatarBuilder` | Use component factory to replace `DefaultStreamMessage` | +| `textBuilder` | Use component factory to replace `StreamMessageContent` | +| `quotedMessageBuilder` | Use component factory to replace `StreamMessageContent` | +| `deletedMessageBuilder` | Use component factory to replace `StreamMessageContent` | +| `editMessageInputBuilder` | Removed; use `onEditMessageTap` callback instead | +| `bottomRowBuilderWithDefaultWidget` | Use component factory; `StreamMessageFooter` is the new equivalent | +| `reactionPickerBuilder` | Configured globally via `StreamChatConfigurationData.reactionIconResolver` | +| `reactionIndicatorBuilder` | Replaced by `StreamMessageReactions` component | + +#### Shape & Style + +| Old Parameter | Migration Path | +|---|---| +| `shape` | Controlled via `StreamMessageBubble` theming in `stream_core_flutter` | +| `borderSide` | Controlled via `StreamMessageBubble` theming | +| `borderRadiusGeometry` | Controlled via `StreamMessageBubble` theming | +| `attachmentShape` | Controlled via attachment builder theming | +| `textPadding` | Controlled via `StreamMessageBubble` content padding theming | +| `attachmentPadding` | Configured internally by `ParseAttachments` | +| `messageTheme` | Resolved from context via `StreamMessageItemTheme.of(context)` | + +#### Other Removed Parameters + +| Old Parameter | Migration Path | +|---|---| +| `reverse` | Determined by `StreamMessagePlacement` context (set by list view) | +| `translateUserAvatar` | Removed; avatar positioning is theme-driven | +| `onConfirmDeleteTap` | Handled internally by `StreamMessageActionsBuilder` | +| `onShowMessage` | Removed | +| `onReactionsHover` | Removed | +| `customActions` | Use `actionsBuilder` on `StreamMessageWidgetProps` | +| `onCustomActionTap` | Use `actionsBuilder` on `StreamMessageWidgetProps` | +| `onAttachmentTap` | Handle in custom attachment builders | +| `imageAttachmentThumbnailSize` | Configured in attachment builders | +| `imageAttachmentThumbnailResizeType` | Configured in attachment builders | +| `imageAttachmentThumbnailCropType` | Configured in attachment builders | +| `attachmentActionsModalBuilder` | Configured in attachment builders | +| `attachmentBuilders` | Moved to `StreamChatConfigurationData.attachmentBuilders` (still overridable per-message via `StreamMessageWidgetProps.attachmentBuilders`) | +| `copyWith()` on `StreamMessageWidget` | Use `StreamMessageWidgetProps.copyWith()` instead | + +### New Parameters + +| New Parameter | Description | +|---|---| +| `padding` | Outer padding around the message item (overrides theme) | +| `spacing` | Horizontal spacing between avatar and content (overrides theme) | +| `backgroundColor` | Background color for the message row (overrides theme) | +| `widthFactor` | Max content width as fraction of parent (default: `0.8`) | +| `onMessageLinkTap` | `void Function(Message, String)` — receives message and URL | +| `onUserMentionTap` | `void Function(User)` — receives the mentioned user | +| `onQuotedMessageTap` | `void Function(Message)` — receives the quoted message object | +| `onReactionsTap` | `void Function(Message)` — overrides default reaction detail sheet | +| `reactionSorting` | `Comparator` for reaction display order | +| `actionsBuilder` | `MessageActionsBuilder` for customizing the actions list | +| `onMessageActions` | Override the default long-press modal entirely | +| `onBouncedErrorMessageActions` | Override the bounced-error modal entirely | +| `onEditMessageTap` | Called when edit action is selected | + +### Changed Signatures + +| Callback | Old Signature | New Signature | +|---|---|---| +| Link tap | `void Function(String url)` | `void Function(Message message, String url)` | +| Mention tap | `void Function(User user)` | `void Function(User user)` (renamed: `onMentionTap` → `onUserMentionTap`) | +| Quoted message tap | `void Function(String? quotedMessageId)` | `void Function(Message quotedMessage)` | +| Thread tap | `void Function(Message message)` | `void Function(Message message)` (unchanged signature, renamed: `onThreadTap`) | +| Reply tap | `void Function(Message message)` | `void Function(Message message)` (new: `onReplyTap`) | + +--- + +## StreamMessageListView + +### Builder Signature Changes + +Both `messageBuilder` and `parentMessageBuilder` now use the same typedef: + +**Before:** +```dart +typedef MessageBuilder = Widget Function( + BuildContext context, + MessageDetails details, + List messages, + StreamMessageWidget defaultMessageWidget, +); + +typedef ParentMessageBuilder = Widget Function( + BuildContext context, + Message? parentMessage, + StreamMessageWidget defaultMessageWidget, +); +``` + +**After:** +```dart +typedef StreamMessageWidgetBuilder = Widget Function( + BuildContext context, + Message message, + StreamMessageWidgetProps defaultProps, +); +``` + +The old builders received a pre-built `StreamMessageWidget` that you could `copyWith`. The new builders receive `StreamMessageWidgetProps` — raw configuration data. Use `StreamMessageWidget.fromProps(props:)` to build the default widget through the component factory. + +**Before:** +```dart +StreamMessageListView( + messageBuilder: (context, details, messages, defaultWidget) { + return defaultWidget.copyWith(showReactions: false); + }, +) +``` + +**After:** +```dart +StreamMessageListView( + messageBuilder: (context, message, defaultProps) { + // Build default widget (goes through component factory) + return StreamMessageWidget.fromProps(props: defaultProps); + + // Or customize props before building + return StreamMessageWidget.fromProps( + props: defaultProps.copyWith( + actionsBuilder: (context, actions) => [...actions, myAction], + ), + ); + + // Or replace entirely + return MyCustomMessageWidget(message: message); + }, +) +``` + +> **Important:** The `messageBuilder` callback now receives a `BuildContext` that has `StreamMessagePlacement` in its ancestor chain. You can call `StreamMessagePlacement.alignmentDirectionalOf(context)` to determine message alignment. + +### New List-Level Callbacks + +These callbacks were previously only configurable per-message on `StreamMessageWidget`. They are now available at the list level and forwarded to all messages: + +| New Parameter | Type | +|---|---| +| `onEditMessageTap` | `void Function(Message)?` | +| `onReplyTap` | `void Function(Message)?` | +| `onUserAvatarTap` | `void Function(User)?` | +| `onReactionsTap` | `void Function(Message)?` | +| `onQuotedMessageTap` | `void Function(Message)?` | +| `onMessageLinkTap` | `void Function(Message, String)?` | +| `onUserMentionTap` | `void Function(User)?` | + +### Changed: `showUnreadCountOnScrollToBottom` Default + +```dart +// Old +showUnreadCountOnScrollToBottom: false + +// New +showUnreadCountOnScrollToBottom: true +``` + +### Removed: MessageDetails + +The old `messageBuilder` received `MessageDetails` which contained `userId`, `message`, `messages`, and `index`. The new builder receives just `Message` and `StreamMessageWidgetProps`. The user ID is accessible via `StreamChat.of(context).currentUser?.id`. Message alignment is provided by `StreamMessagePlacement.of(context)`. + +--- + +## Custom Actions Migration + +**Before (using `customActions` + `onCustomActionTap`):** +```dart +StreamMessageWidget( + message: message, + messageTheme: theme, + customActions: [ + StreamMessageAction( + leading: Icon(Icons.info), + title: Text('Info'), + onTap: (message) => showInfo(message), + ), + ], + onCustomActionTap: (action) { + // handle CustomMessageAction + }, +) +``` + +**After (using `actionsBuilder` via component factory):** +```dart +StreamChat( + client: client, + componentBuilders: StreamComponentBuilders( + extensions: streamChatComponentBuilders( + messageWidget: (context, props) { + return DefaultStreamMessage( + props: props.copyWith( + actionsBuilder: (context, defaultActions) { + return StreamContextMenuAction.partitioned( + items: [ + ...defaultActions, + StreamContextMenuAction( + leading: Icon(context.streamIcons.informationCircle), + label: Text('Info'), + onTap: () => showInfo(props.message), + ), + ], + ); + }, + ), + ); + }, + ), + ), + child: ..., +) +``` + +**After (removing a default action):** +```dart +actionsBuilder: (context, defaultActions) { + return StreamContextMenuAction.partitioned( + items: defaultActions.where( + (a) => a.props.value is! DeleteMessage, + ).toList(), + ); +}, +``` + +> **Important:** +> - `customActions` and `onCustomActionTap` are removed +> - `CustomMessageAction` class is removed — use `StreamContextMenuAction` with `onTap` +> - `actionsBuilder` receives defaults already filtered by channel permissions +> - Return `List` — you can mix `StreamContextMenuAction` and `StreamContextMenuSeparator` + +--- + +## Theme Migration + +**Before (explicit `messageTheme` parameter):** +```dart +StreamMessageWidget( + message: message, + messageTheme: isMyMessage + ? streamTheme.ownMessageTheme + : streamTheme.otherMessageTheme, +) +``` + +**After (theme resolved automatically from context):** +```dart +StreamMessageWidget(message: message) +``` + +`StreamMessageItemTheme` is provided by `StreamChatTheme` and resolved based on `StreamMessagePlacement` (alignment, stack position, etc.). + +### StreamMessageItemThemeData + +The old per-property visibility booleans are replaced by a structured visibility system: + +```dart +StreamMessageItemThemeData( + leadingVisibility: StreamMessageStyleVisibility( + incoming: StreamVisibility.visible, + outgoing: StreamVisibility.gone, + ), + headerVisibility: StreamMessageStyleVisibility(...), + footerVisibility: StreamMessageStyleVisibility(...), + + incoming: StreamMessageItemStyle( + padding: EdgeInsets.all(4), + backgroundColor: Colors.white, + ), + outgoing: StreamMessageItemStyle( + padding: EdgeInsets.all(4), + backgroundColor: Colors.blue.shade50, + ), +) +``` + +--- + +## Swipeable Message Example + +```dart +StreamMessageListView( + messageBuilder: (context, message, defaultProps) { + final defaultWidget = StreamMessageWidget.fromProps(props: defaultProps); + + if (message.isDeleted || message.state.isFailed) return defaultWidget; + + final alignment = StreamMessagePlacement.alignmentDirectionalOf(context); + final isEnd = alignment == AlignmentDirectional.centerEnd; + + return Swipeable( + key: ValueKey(message.id), + direction: isEnd ? SwipeDirection.endToStart : SwipeDirection.startToEnd, + swipeThreshold: 0.2, + onSwiped: (_) => onReply(message), + child: defaultWidget, + ); + }, +) +``` + +--- + +## Deleted Classes & Files + +| Old File | Old Class | Replacement | +|---|---|---| +| `message_widget_content.dart` | `MessageWidgetContent` | `DefaultStreamMessage` + `StreamMessageContent` | +| `message_widget_content_components.dart` | Various internal helpers | Merged into `components/` sub-widgets | +| `bottom_row.dart` | `BottomRow` | `StreamMessageFooter` | +| `message_text.dart` | `StreamMessageText` | `components/stream_message_text.dart` | +| `deleted_message.dart` | `StreamDeletedMessage` | `StreamMessageDeleted` | +| `message_card.dart` | `MessageCard` | `core.StreamMessageBubble` | +| `text_bubble.dart` | `TextBubble` | `core.StreamMessageBubble` | +| `pinned_message.dart` | `PinnedMessage` | `streamMessageHeader()` function | +| `quoted_message.dart` | `QuotedMessage` | Inline in `StreamMessageContent` | +| `thread_painter.dart` | `ThreadReplyPainter` | `core.StreamMessageReplies` | +| `thread_participants.dart` | `ThreadParticipants` | Inline in `core.StreamMessageReplies` | +| `user_avatar_transform.dart` | `UserAvatarTransform` | `StreamMessageLeading` | +| `username.dart` | `Username` | Inline in `StreamMessageFooter` | +| `sending_indicator_builder.dart` | `SendingIndicatorBuilder` | `StreamMessageSendingStatus` | + +--- + +## Typedef Changes + +| Old Typedef | New Typedef | +|---|---| +| `MessageBuilder = Widget Function(BuildContext, MessageDetails, List, StreamMessageWidget)` | `StreamMessageWidgetBuilder = Widget Function(BuildContext, Message, StreamMessageWidgetProps)` | +| `ParentMessageBuilder = Widget Function(BuildContext, Message?, StreamMessageWidget)` | `StreamMessageWidgetBuilder` (same as above) | +| `OnQuotedMessageTap = void Function(String?)` | Removed — use `void Function(Message)` directly | +| — | `MessageActionsBuilder = List Function(BuildContext, List>)` (new) | + +> **Note:** `MessageBuilder` and `ParentMessageBuilder` are removed from `typedefs.dart`. The new `StreamMessageWidgetBuilder` is defined in `message_list_view.dart` and exported via the barrel file. + +--- + +## Migration Checklist + +- [ ] Replace `StreamMessageWidget(message:, messageTheme:, ...)` with `StreamMessageWidget(message:)` — theme is now resolved from context +- [ ] Remove all `show*` boolean parameters — visibility is now controlled via `StreamMessageItemThemeData` and channel permissions +- [ ] Remove `customActions` and `onCustomActionTap` — use `actionsBuilder` via component factory or `StreamMessageWidgetProps.copyWith()` +- [ ] Remove all per-widget builder callbacks (`userAvatarBuilder`, `textBuilder`, `quotedMessageBuilder`, `deletedMessageBuilder`, `bottomRowBuilderWithDefaultWidget`, `reactionPickerBuilder`, `reactionIndicatorBuilder`) — use component factory instead +- [ ] Remove `shape`, `borderSide`, `borderRadiusGeometry`, `attachmentShape`, `textPadding`, `attachmentPadding` — controlled via `StreamMessageBubble` theming +- [ ] Remove `reverse` — determined by `StreamMessagePlacement` context +- [ ] Remove `translateUserAvatar` — avatar positioning is theme-driven +- [ ] Update `messageBuilder` / `parentMessageBuilder` callbacks to new `StreamMessageWidgetBuilder` signature +- [ ] Replace `MessageDetails` usage — use `StreamMessagePlacement.of(context)` for alignment, `StreamChat.of(context).currentUser` for user ID +- [ ] Update `onLinkTap` to `onMessageLinkTap` with new signature `void Function(Message, String)` +- [ ] Update `onMentionTap` to `onUserMentionTap` +- [ ] Update `onQuotedMessageTap` from `void Function(String?)` to `void Function(Message)` +- [ ] Replace `StreamDeletedMessage` with `StreamMessageDeleted` +- [ ] Replace `StreamMessageAction` with `StreamContextMenuAction` (see [message_actions.md](message_actions.md)) +- [ ] Replace `StreamSvgIcon(icon: StreamSvgIcons.*)` with `Icon(context.streamIcons.*)` +- [ ] Remove `StreamMessageWidget.copyWith()` usage — use `StreamMessageWidgetProps.copyWith()` instead diff --git a/migrations/redesign/reaction_picker.md b/migrations/redesign/reaction_picker.md index c7ad88bd04..fe6c9c7764 100644 --- a/migrations/redesign/reaction_picker.md +++ b/migrations/redesign/reaction_picker.md @@ -10,7 +10,7 @@ This guide covers the migration for the redesigned reaction picker and reaction - [StreamChatConfigurationData](#streamchatconfigurationdata) - [Removed Icon-List APIs](#removed-icon-list-apis) - [ReactionIconResolver and DefaultReactionIconResolver](#reactioniconresolver-and-defaultreactioniconresolver) -- [StreamReactionPicker](#streamreactionpicker) +- [StreamMessageReactionPicker](#streammessagereactionpicker-formerly-streamreactionpicker) - [StreamReactionIndicator](#streamreactionindicator) - [New Components](#new-components) - [Migration Checklist](#migration-checklist) @@ -27,7 +27,8 @@ This guide covers the migration for the redesigned reaction picker and reaction | `DefaultReactionIconResolver` | **New** — ready-to-use default; extend to customize `defaultReactions`, `emojiCode`, or rendering hooks | | `ReactionPickerIconList` / `ReactionIndicatorIconList` | **Removed** — list rendering now lives inside picker/indicator widgets | | `ReactionPickerIcon` / `ReactionIndicatorIcon` | **Removed** — use resolver-based reaction mapping instead | -| `StreamReactionPicker` | **Changed** — reaction set from `config.reactionIconResolver.defaultReactions` only | +| `StreamReactionPicker` | **Renamed** to `StreamMessageReactionPicker` — reaction set from `config.reactionIconResolver.defaultReactions` only | +| `StreamReactionPickerTheme` / `StreamReactionPickerThemeData` | **New** (from `stream_core_flutter`) — theme-based visual customisation for the picker | | `StreamReactionIndicator` | **Changed** — uses `config.reactionIconResolver.resolve(context, type)` only | | `ReactionDetailSheet` | **New** — `ReactionDetailSheet.show()` for reaction details bottom sheet | @@ -204,27 +205,36 @@ class MyReactionIconResolver extends DefaultReactionIconResolver { --- -## StreamReactionPicker +## StreamMessageReactionPicker (formerly StreamReactionPicker) ### Breaking Changes: +- **Renamed** from `StreamReactionPicker` to `StreamMessageReactionPicker` +- `StreamReactionPicker` now refers to the domain-agnostic core component from `stream_core_flutter` - Picker icons are no longer configured with per-widget icon models - Quick-pick entries now come from `config.reactionIconResolver.defaultReactions` +- Visual properties (`backgroundColor`, `padding`, `shape`) removed from the widget — use `StreamReactionPickerTheme` instead +- The core picker now uses a `StreamComponentFactory` pattern with `StreamReactionPickerProps` for full customization ### Migration **Before:** ```dart -StreamChat( - client: client, - streamChatConfigData: StreamChatConfigurationData( - reactionIcons: [ /* old icon list */ ], - ), - child: MyApp(), +StreamReactionPicker( + message: message, ) ``` **After:** +```dart +StreamMessageReactionPicker( + message: message, + onReactionPicked: onReactionPicked, +) +``` + +Configure reactions globally via `reactionIconResolver`: + ```dart StreamChat( client: client, @@ -235,17 +245,23 @@ StreamChat( ) ``` -Then keep picker usage unchanged: +Customize visual appearance via theme: ```dart -StreamReactionPicker( - message: message, - onReactionPicked: onReactionPicked, +StreamReactionPickerTheme( + data: StreamReactionPickerThemeData( + backgroundColor: Colors.white, + elevation: 4, + spacing: 2, + shape: RoundedSuperellipseBorder( + borderRadius: BorderRadius.all(Radius.circular(24)), + ), + side: BorderSide(color: Colors.grey), + ), + child: // ... ) ``` -Set `reactionIconResolver` on `StreamChatConfigurationData` to customize. - --- ## StreamReactionIndicator @@ -315,7 +331,9 @@ Exported for `StreamChatConfigurationData`. See [ReactionIconResolver and Defaul ## Migration Checklist +- [ ] Rename `StreamReactionPicker` → `StreamMessageReactionPicker` in your code - [ ] Remove `reactionIcons` from `StreamChatConfigurationData` +- [ ] Remove `backgroundColor`, `padding`, `shape` props from picker usage — use `StreamReactionPickerTheme` instead - [ ] Custom quick-pick: extend `DefaultReactionIconResolver`, override `defaultReactions` with types from `streamSupportedEmojis` (so `emojiCode` returns emoji); set `reactionIconResolver` - [ ] Custom types not in `streamSupportedEmojis`: also override `emojiCode` to return Unicode emoji for each; optionally `supportedReactions` - [ ] Custom rendering (e.g. Twemoji): extend `DefaultReactionIconResolver`, override `resolve(context, type)` and branch by type, set `reactionIconResolver` diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index 7284f89f0f..5d814dfa7b 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -254,83 +254,74 @@ class _ChannelPageState extends State { Expanded( child: StreamMessageListView( threadBuilder: (_, parent) => ThreadPage(parent: parent!), - messageBuilder: - ( - context, - messageDetails, - messages, - defaultWidget, - ) { - // The threshold after which the message is considered - // swiped. - const threshold = 0.2; - - final isMyMessage = messageDetails.isMyMessage; - - // The direction in which the message can be swiped. - final swipeDirection = isMyMessage - ? SwipeDirection - .endToStart // - : SwipeDirection.startToEnd; - - return Swipeable( - key: ValueKey(messageDetails.message.id), - direction: swipeDirection, - swipeThreshold: threshold, - onSwiped: (details) => reply(messageDetails.message), - backgroundBuilder: (context, details) { - // The alignment of the swipe action. - final alignment = isMyMessage - ? Alignment - .centerRight // - : Alignment.centerLeft; - - // The progress of the swipe action. - final progress = math.min(details.progress, threshold) / threshold; - - // The offset for the reply icon. - var offset = Offset.lerp( - const Offset(-24, 0), - const Offset(12, 0), - progress, - )!; - - // If the message is mine, we need to flip the offset. - if (isMyMessage) { - offset = Offset(-offset.dx, -offset.dy); - } - - final _streamTheme = StreamChatTheme.of(context); - - return Align( - alignment: alignment, - child: Transform.translate( - offset: offset, - child: Opacity( - opacity: progress, - child: SizedBox.square( - dimension: 30, - child: CustomPaint( - painter: AnimatedCircleBorderPainter( - progress: progress, - color: _streamTheme.colorTheme.borders, - ), - child: Center( - child: Icon( - context.streamIcons.arrowShareLeft, - size: lerpDouble(0, 18, progress), - color: _streamTheme.colorTheme.accentPrimary, - ), - ), + messageBuilder: (context, message, defaultProps) { + // The threshold after which the message is considered + // swiped. + const threshold = 0.2; + + final currentUser = StreamChat.of(context).currentUser; + final isMyMessage = message.user?.id == currentUser?.id; + + // The direction in which the message can be swiped. + final swipeDirection = isMyMessage ? SwipeDirection.endToStart : SwipeDirection.startToEnd; + + return Swipeable( + key: ValueKey(message.id), + direction: swipeDirection, + swipeThreshold: threshold, + onSwiped: (details) => reply(message), + backgroundBuilder: (context, details) { + // The alignment of the swipe action. + final alignment = isMyMessage ? Alignment.centerRight : Alignment.centerLeft; + + // The progress of the swipe action. + final progress = math.min(details.progress, threshold) / threshold; + + // The offset for the reply icon. + var offset = Offset.lerp( + const Offset(-24, 0), + const Offset(12, 0), + progress, + )!; + + // If the message is mine, we need to flip the offset. + if (isMyMessage) { + offset = Offset(-offset.dx, -offset.dy); + } + + final _streamTheme = StreamChatTheme.of(context); + + return Align( + alignment: alignment, + child: Transform.translate( + offset: offset, + child: Opacity( + opacity: progress, + child: SizedBox.square( + dimension: 30, + child: CustomPaint( + painter: AnimatedCircleBorderPainter( + progress: progress, + color: _streamTheme.colorTheme.borders, + ), + child: Center( + child: Icon( + context.streamIcons.arrowShareLeft, + size: lerpDouble(0, 18, progress), + color: _streamTheme.colorTheme.accentPrimary, ), ), ), ), - ); - }, - child: defaultWidget.copyWith(onReplyTap: reply), + ), + ), ); }, + child: DefaultStreamMessage( + props: defaultProps.copyWith(onReplyTap: reply), + ), + ); + }, ), ), StreamMessageInput( diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart index 029dfdac61..d667748af8 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart @@ -128,11 +128,9 @@ class ChannelPage extends StatelessWidget { Widget _messageBuilder( BuildContext context, - MessageDetails details, - List messages, - StreamMessageWidget _, + Message message, + StreamMessageWidgetProps defaultProps, ) { - final message = details.message; final isCurrentUser = StreamChat.of(context).currentUser!.id == message.user!.id; final textAlign = isCurrentUser ? TextAlign.right : TextAlign.left; final color = isCurrentUser ? Colors.blueGrey : Colors.blue; diff --git a/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart index 3bada795e5..63e08c83d4 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart @@ -68,67 +68,69 @@ abstract class StreamAttachmentWidgetBuilder { static List defaultBuilders({ required Message message, ShapeBorder? shape, - EdgeInsetsGeometry padding = const EdgeInsets.all(4), + EdgeInsetsGeometry? padding, StreamAttachmentWidgetTapCallback? onAttachmentTap, List? customAttachmentBuilders, }) { + final effectivePadding = padding ?? const EdgeInsets.symmetric(horizontal: 8); + return [ ...?customAttachmentBuilders, // Handles poll attachments. PollAttachmentBuilder( shape: shape, - padding: padding, + padding: effectivePadding, ), // Handles a mix of image, gif, video, url, file and voice recording // attachments. MixedAttachmentBuilder( - padding: padding, + padding: effectivePadding, onAttachmentTap: onAttachmentTap, ), // Handles a mix of image, gif, and video attachments. GalleryAttachmentBuilder( shape: shape, - padding: padding, - runSpacing: padding.vertical / 2, - spacing: padding.horizontal / 2, + padding: effectivePadding, + runSpacing: effectivePadding.vertical / 2, + spacing: effectivePadding.horizontal / 2, onAttachmentTap: onAttachmentTap, ), // Handles file attachments. FileAttachmentBuilder( shape: shape, - padding: padding, + padding: effectivePadding, onAttachmentTap: onAttachmentTap, ), // Handles giphy attachments. GiphyAttachmentBuilder( shape: shape, - padding: padding, + padding: effectivePadding, onAttachmentTap: onAttachmentTap, ), // Handles image attachments. ImageAttachmentBuilder( shape: shape, - padding: padding, + padding: effectivePadding, onAttachmentTap: onAttachmentTap, ), // Handles video attachments. VideoAttachmentBuilder( shape: shape, - padding: padding, + padding: effectivePadding, onAttachmentTap: onAttachmentTap, ), // Handles voice recording attachments. VoiceRecordingAttachmentPlaylistBuilder( shape: shape, - padding: padding, + padding: effectivePadding, onAttachmentTap: onAttachmentTap, ), @@ -136,7 +138,7 @@ abstract class StreamAttachmentWidgetBuilder { if (message.quotedMessage == null) UrlAttachmentBuilder( shape: shape, - padding: padding, + padding: effectivePadding, onAttachmentTap: onAttachmentTap, ), diff --git a/packages/stream_chat_flutter/lib/src/attachment/builder/mixed_attachment_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/builder/mixed_attachment_builder.dart index 5e272ad1eb..9b0fe31c14 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/builder/mixed_attachment_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/builder/mixed_attachment_builder.dart @@ -97,10 +97,12 @@ class MixedAttachmentBuilder extends StreamAttachmentWidgetBuilder { final shouldBuildGallery = [...?images, ...?videos, ...?giphys].length > 1; + final spacing = context.streamSpacing; + return Padding( padding: padding, child: Column( - spacing: padding.vertical / 2, + spacing: spacing.xs, mainAxisSize: MainAxisSize.min, children: [ if (urls != null) diff --git a/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart b/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart index 199ceb9fa2..a692917a5f 100644 --- a/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart +++ b/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart @@ -11,6 +11,7 @@ Iterable> streamChatComponentBuilders({ StreamComponentBuilder? messageComposerInputLeading, StreamComponentBuilder? messageComposerInputHeader, StreamComponentBuilder? messageComposerInputTrailing, + StreamComponentBuilder? messageWidget, }) { final builders = [ if (channelListItem != null) StreamComponentBuilderExtension(builder: channelListItem), @@ -21,6 +22,7 @@ Iterable> streamChatComponentBuilders({ if (messageComposerInputLeading != null) StreamComponentBuilderExtension(builder: messageComposerInputLeading), if (messageComposerInputHeader != null) StreamComponentBuilderExtension(builder: messageComposerInputHeader), if (messageComposerInputTrailing != null) StreamComponentBuilderExtension(builder: messageComposerInputTrailing), + if (messageWidget != null) StreamComponentBuilderExtension(builder: messageWidget), ]; return builders; diff --git a/packages/stream_chat_flutter/lib/src/indicators/sending_indicator.dart b/packages/stream_chat_flutter/lib/src/indicators/sending_indicator.dart index b283bed6e1..b4fabfd62b 100644 --- a/packages/stream_chat_flutter/lib/src/indicators/sending_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/indicators/sending_indicator.dart @@ -12,7 +12,7 @@ class StreamSendingIndicator extends StatelessWidget { required this.message, this.isMessageRead = false, this.isMessageDelivered = false, - this.size = 12, + this.size, }); /// The message whose sending status is to be shown. diff --git a/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart b/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart index fafa0cffed..bed4365e5f 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart @@ -173,8 +173,8 @@ class _QuotedMessage extends StatelessWidget { Flexible( child: textBuilder?.call(context, msg) ?? - StreamMessageText( - message: msg, + StreamMarkdownMessage( + data: msg.replaceMentions().text ?? '', messageTheme: isOnlyEmoji && _containsText ? messageTheme.copyWith( messageTextStyle: messageTheme.messageTextStyle?.copyWith( diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart index c76785084f..51cc86cf6e 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart @@ -1,4 +1,3 @@ -// ignore_for_file: lines_longer_than_80_chars import 'dart:async'; import 'dart:math'; @@ -37,6 +36,21 @@ enum SpacingType { defaultSpacing, } +/// Signature for a function that builds a message widget from its +/// [StreamMessageWidgetProps]. +/// +/// Receives the [BuildContext], the [Message] data, and the pre-configured +/// [StreamMessageWidgetProps] with all list-level callbacks already wired in. +/// +/// Use [DefaultStreamMessage] to build the default UI, optionally modifying +/// the props via [StreamMessageWidgetProps.copyWith] first. +typedef StreamMessageWidgetBuilder = + Widget Function( + BuildContext context, + Message message, + StreamMessageWidgetProps defaultProps, + ); + /// {@template streamMessageListView} /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_listview.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_listview_paint.png) @@ -96,6 +110,12 @@ class StreamMessageListView extends StatefulWidget { this.threadBuilder, this.onThreadTap, this.onEditMessageTap, + this.onReplyTap, + this.onUserAvatarTap, + this.onReactionsTap, + this.onQuotedMessageTap, + this.onMessageLinkTap, + this.onUserMentionTap, this.dateDividerBuilder, this.floatingDateDividerBuilder, // we need to use ClampingScrollPhysics to avoid the list view to bounce @@ -140,8 +160,22 @@ class StreamMessageListView extends StatefulWidget { /// dismiss the keyboard automatically. final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior; - /// {@macro messageBuilder} - final MessageBuilder? messageBuilder; + /// Optional builder for per-instance message customization. + /// + /// When set, this builder is called for each regular message with + /// pre-configured [StreamMessageWidgetProps] that have all list-level + /// callbacks already wired in. Use [StreamMessageWidgetProps.copyWith] + /// to modify properties, and [DefaultStreamMessage] to build the default + /// widget. + /// + /// For app-wide customization, use [StreamComponentFactory] instead. + final StreamMessageWidgetBuilder? messageBuilder; + + /// Optional builder for the parent message at the top of a thread. + /// + /// Works the same as [messageBuilder] but is called for the parent + /// message only. + final StreamMessageWidgetBuilder? parentMessageBuilder; /// Whether the view scrolls in the reading direction. /// @@ -170,9 +204,6 @@ class StreamMessageListView extends StatefulWidget { /// {@macro moderatedMessageBuilder} final ModeratedMessageBuilder? moderatedMessageBuilder; - /// {@macro parentMessageBuilder} - final ParentMessageBuilder? parentMessageBuilder; - /// {@macro threadBuilder} final ThreadBuilder? threadBuilder; @@ -187,6 +218,41 @@ class StreamMessageListView extends StatefulWidget { /// If provided, the inline edit flow is used instead of the edit bottom sheet. final void Function(Message)? onEditMessageTap; + /// Called when the reply action is triggered on a message. + /// + /// Forwarded to each [StreamMessageWidget] in the list. + final void Function(Message)? onReplyTap; + + /// Called when a user avatar is tapped. + /// + /// Forwarded to each [StreamMessageWidget] in the list. + final void Function(User)? onUserAvatarTap; + + /// Called when the message reactions are tapped. + /// + /// Forwarded to each [StreamMessageWidget] in the list. + final void Function(Message)? onReactionsTap; + + /// Called when a quoted message is tapped. + /// + /// When provided, this callback is forwarded to each + /// [StreamMessageWidget] in the list. + /// + /// When null (the default), tapping a quoted message scrolls to it in + /// the list, loading it if necessary. + final void Function(Message quotedMessage)? onQuotedMessageTap; + + /// Called when a link is tapped in message text. + /// + /// Receives the [Message] containing the link and the tapped URL. + /// Forwarded to each [StreamMessageWidget] in the list. + final void Function(Message message, String url)? onMessageLinkTap; + + /// Called when a user mention is tapped in message text. + /// + /// Forwarded to each [StreamMessageWidget] in the list. + final void Function(User user)? onUserMentionTap; + /// If true will show a scroll to bottom button when /// the scroll offset is not zero final bool showScrollToBottom; @@ -992,80 +1058,38 @@ class _StreamMessageListViewState extends State { Widget buildParentMessage( Message message, ) { - final isMyMessage = message.user!.id == StreamChat.of(context).currentUser!.id; - final isOnlyEmoji = message.text?.isOnlyEmoji ?? false; - - final hasFileAttachment = message.attachments.any((it) => it.type == AttachmentType.file); - - final hasUrlAttachment = message.attachments.any((it) => it.type == AttachmentType.urlPreview); - - final attachmentBorderRadius = hasUrlAttachment - ? 8.0 - : hasFileAttachment - ? 12.0 - : 14.0; - - final borderSide = isOnlyEmoji ? BorderSide.none : null; - - final defaultMessageWidget = StreamMessageWidget( + final parentMessageProps = StreamMessageWidgetProps( message: message, - reverse: isMyMessage, - showUsername: !isMyMessage, - showReactions: !message.isDeleted && !message.state.isDeletingFailed, - showReactionPicker: !message.isDeleted && !message.state.isDeletingFailed, - showReplyMessage: false, - showResendMessage: false, - showThreadReplyMessage: false, - showCopyMessage: false, - showDeleteMessage: false, - showEditMessage: false, - showMarkUnreadMessage: false, - showSendingIndicator: false, - attachmentPadding: EdgeInsets.all( - hasUrlAttachment - ? 8 - : hasFileAttachment - ? 4 - : 2, - ), - attachmentShape: RoundedRectangleBorder( - side: BorderSide( - color: _streamTheme.colorTheme.borders, - strokeAlign: BorderSide.strokeAlignOutside, - ), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(attachmentBorderRadius), - bottomLeft: isMyMessage ? Radius.circular(attachmentBorderRadius) : Radius.zero, - topRight: Radius.circular(attachmentBorderRadius), - bottomRight: isMyMessage ? Radius.zero : Radius.circular(attachmentBorderRadius), - ), - ), - borderRadiusGeometry: BorderRadius.only( - topLeft: const Radius.circular(16), - bottomLeft: isMyMessage ? const Radius.circular(16) : Radius.zero, - topRight: const Radius.circular(16), - bottomRight: isMyMessage ? Radius.zero : const Radius.circular(16), - ), - textPadding: EdgeInsets.symmetric( - vertical: context.streamSpacing.xs, - horizontal: isOnlyEmoji ? 0 : context.streamSpacing.sm, - ), - borderSide: borderSide, - showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show, - messageTheme: isMyMessage ? _streamTheme.ownMessageTheme : _streamTheme.otherMessageTheme, + onThreadTap: _onThreadTap, onMessageTap: widget.onMessageTap, onMessageLongPress: widget.onMessageLongPress, + onEditMessageTap: widget.onEditMessageTap, + onReplyTap: widget.onReplyTap, + onUserAvatarTap: widget.onUserAvatarTap, + onReactionsTap: widget.onReactionsTap, + onQuotedMessageTap: widget.onQuotedMessageTap, + onMessageLinkTap: widget.onMessageLinkTap, + onUserMentionTap: widget.onUserMentionTap, ); - if (widget.parentMessageBuilder != null) { - return widget.parentMessageBuilder!.call( - context, - widget.parentMessage, - defaultMessageWidget, - ); - } + final userId = StreamChat.of(context).currentUser!.id; + final isMyMessage = message.user?.id == userId; + + final isInThread = widget.parentMessage != null; - return defaultMessageWidget; + return StreamMessagePlacement( + data: StreamMessagePlacementData( + stackPosition: .single, + alignment: isMyMessage ? .end : .start, + listKind: isInThread ? .thread : .channel, + ), + child: Builder( + builder: (context) => switch (widget.parentMessageBuilder) { + final builder? => builder.call(context, message, parentMessageProps), + _ => StreamMessageWidget.fromProps(props: parentMessageProps), + }, + ), + ); } Widget _buildScrollToBottom() { @@ -1178,186 +1202,75 @@ class _StreamMessageListViewState extends State { return buildModeratedMessage(message); } + final messageWidgetProps = StreamMessageWidgetProps( + message: message, + onThreadTap: _onThreadTap, + onMessageTap: widget.onMessageTap, + onMessageLongPress: widget.onMessageLongPress, + onEditMessageTap: widget.onEditMessageTap, + onReplyTap: widget.onReplyTap, + onUserAvatarTap: widget.onUserAvatarTap, + onReactionsTap: widget.onReactionsTap, + onMessageLinkTap: widget.onMessageLinkTap, + onUserMentionTap: widget.onUserMentionTap, + onQuotedMessageTap: switch (widget.onQuotedMessageTap) { + final onTap? => onTap, + _ => (quotedMessage) async { + final quotedMessageId = quotedMessage.id; + if (messages.map((e) => e.id).contains(quotedMessageId)) { + final index = messages.indexWhere((m) => m.id == quotedMessageId); + _scrollController?.scrollTo( + index: index + 2, // +2 to account for loader and footer + duration: const Duration(seconds: 1), + curve: Curves.easeInOut, + alignment: 0.1, + ); + } else { + await streamChannel!.loadChannelAtMessage(quotedMessageId).then((_) async { + initialIndex = 21; // 19 + 2 | 19 is the index of the message + initialAlignment = 0.1; + }); + } + }, + }, + ); + final userId = StreamChat.of(context).currentUser!.id; final isMyMessage = message.user?.id == userId; final nextMessage = index - 1 >= 0 ? messages[index - 1] : null; - final isNextUserSame = nextMessage != null && message.user!.id == nextMessage.user!.id; - - var hasTimeDiff = false; - if (nextMessage != null) { - final createdAt = Jiffy.parseFromDateTime(message.createdAt.toLocal()); - final nextCreatedAt = Jiffy.parseFromDateTime( - nextMessage.createdAt.toLocal(), - ); - - hasTimeDiff = !createdAt.isSame(nextCreatedAt, unit: Unit.minute); - } - - final hasVoiceRecordingAttachment = message.attachments.any((it) => it.type == AttachmentType.voiceRecording); - - final hasFileAttachment = message.attachments.any((it) => it.type == AttachmentType.file); - - final hasUrlAttachment = message.attachments.any((it) => it.type == AttachmentType.urlPreview); - - final isThreadMessage = message.parentId != null && message.showInChannel == true; - - final hasReplies = message.replyCount! > 0; - - final attachmentBorderRadius = hasUrlAttachment - ? 8.0 - : hasFileAttachment - ? 12.0 - : 14.0; - - final showTimeStamp = - (!isThreadMessage || _isThreadConversation) && !hasReplies && (hasTimeDiff || !isNextUserSame); - - final showUsername = - !isMyMessage && (!isThreadMessage || _isThreadConversation) && !hasReplies && (hasTimeDiff || !isNextUserSame); - - final showMarkUnread = - streamChannel?.channel.config?.readEvents == true && - !isMyMessage && - (!isThreadMessage || _isThreadConversation); - - final showUserAvatar = isMyMessage - ? DisplayWidget.gone - : (hasTimeDiff || !isNextUserSame) - ? DisplayWidget.show - : DisplayWidget.hide; - - final showSendingIndicator = isMyMessage && (index == 0 || hasTimeDiff || !isNextUserSame); + final prevMessage = index + 1 < messages.length ? messages[index + 1] : null; - final showInChannelIndicator = !_isThreadConversation && isThreadMessage; - final showThreadReplyIndicator = !_isThreadConversation && hasReplies; - final isOnlyEmoji = message.text?.isOnlyEmoji ?? false; + final stackPosition = computeStackPosition(message: message, previous: prevMessage, next: nextMessage); - final borderSide = isOnlyEmoji ? BorderSide.none : null; - final defaultBorderRadius = context.streamRadius.xxl; + final isInThread = widget.parentMessage != null; - Widget messageWidget = StreamMessageWidget( - message: message, - reverse: isMyMessage, - showReactions: !message.isDeleted && !message.state.isDeletingFailed, - showReactionPicker: !message.isDeleted && !message.state.isDeletingFailed, - showInChannelIndicator: showInChannelIndicator, - showThreadReplyIndicator: showThreadReplyIndicator, - showUsername: showUsername, - showTimestamp: showTimeStamp, - showSendingIndicator: showSendingIndicator, - showUserAvatar: showUserAvatar, - showMarkUnreadMessage: showMarkUnread, - onQuotedMessageTap: (quotedMessageId) async { - if (messages.map((e) => e.id).contains(quotedMessageId)) { - final index = messages.indexWhere((m) => m.id == quotedMessageId); - _scrollController?.scrollTo( - index: index + 2, // +2 to account for loader and footer - duration: const Duration(seconds: 1), - curve: Curves.easeInOut, - alignment: 0.1, - ); - } else { - await streamChannel!.loadChannelAtMessage(quotedMessageId).then((_) async { - initialIndex = 21; // 19 + 2 | 19 is the index of the message - initialAlignment = 0.1; - }); - } - }, - showEditMessage: isMyMessage, - showDeleteMessage: isMyMessage, - showThreadReplyMessage: !isThreadMessage && streamChannel?.channel.canSendReply == true, - showFlagButton: !isMyMessage, - borderSide: borderSide, - onThreadTap: _onThreadTap, - onEditMessageTap: widget.onEditMessageTap, - attachmentShape: RoundedRectangleBorder( - side: BorderSide( - color: _streamTheme.colorTheme.borders, - strokeAlign: BorderSide.strokeAlignOutside, - ), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(attachmentBorderRadius), - bottomLeft: isMyMessage - ? Radius.circular(attachmentBorderRadius) - : Radius.circular( - (hasTimeDiff || !isNextUserSame) && - !(hasReplies || isThreadMessage || hasFileAttachment || hasVoiceRecordingAttachment) - ? 0 - : attachmentBorderRadius, - ), - topRight: Radius.circular(attachmentBorderRadius), - bottomRight: isMyMessage - ? Radius.circular( - (hasTimeDiff || !isNextUserSame) && - !(hasReplies || isThreadMessage || hasFileAttachment || hasVoiceRecordingAttachment) - ? 0 - : attachmentBorderRadius, - ) - : Radius.circular(attachmentBorderRadius), - ), - ), - attachmentPadding: EdgeInsets.all( - hasUrlAttachment - ? 8 - : hasFileAttachment || hasVoiceRecordingAttachment - ? 4 - : 2, + Widget child = StreamMessagePlacement( + data: StreamMessagePlacementData( + stackPosition: stackPosition, + alignment: isMyMessage ? .end : .start, + listKind: isInThread ? .thread : .channel, + // channelKind: , ), - borderRadiusGeometry: BorderRadius.only( - topLeft: defaultBorderRadius, - bottomLeft: isMyMessage || !((hasTimeDiff || !isNextUserSame) && !(hasReplies || isThreadMessage)) - ? defaultBorderRadius - : Radius.zero, - topRight: defaultBorderRadius, - bottomRight: isMyMessage && (hasTimeDiff || !isNextUserSame) && !(hasReplies || isThreadMessage) - ? Radius.zero - : defaultBorderRadius, + child: Builder( + builder: (context) => switch (widget.messageBuilder) { + final builder? => builder.call(context, message, messageWidgetProps), + _ => StreamMessageWidget.fromProps(props: messageWidgetProps), + }, ), - textPadding: EdgeInsets.symmetric( - vertical: context.streamSpacing.xs, - horizontal: isOnlyEmoji ? 0 : context.streamSpacing.sm, - ), - messageTheme: isMyMessage ? _streamTheme.ownMessageTheme : _streamTheme.otherMessageTheme, - onMessageTap: widget.onMessageTap, - onMessageLongPress: widget.onMessageLongPress, ); - if (widget.messageBuilder != null) { - messageWidget = widget.messageBuilder!( - context, - MessageDetails( - userId, - message, - messages, - index, - ), - messages, - messageWidget as StreamMessageWidget, - ); - } - - var child = messageWidget; + // Highlight the initial message with an animated background color flash. if (!initialMessageHighlightComplete && widget.highlightInitialMessage && isInitialMessage(message.id, streamChannel)) { - final colorTheme = _streamTheme.colorTheme; - final highlightColor = widget.messageHighlightColor ?? colorTheme.highlight; + final colorScheme = context.streamColorScheme; + final highlightColor = widget.messageHighlightColor ?? colorScheme.backgroundHighlight; child = TweenAnimationBuilder( - tween: ColorTween( - begin: highlightColor, - // ignore: deprecated_member_use - end: colorTheme.barsBg.withOpacity(0), - ), + tween: ColorTween(begin: highlightColor, end: highlightColor.withValues(alpha: 0)), duration: const Duration(seconds: 3), onEnd: () => initialMessageHighlightComplete = true, - builder: (_, color, child) => ColoredBox( - color: color!, - child: child, - ), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: child, - ), + builder: (_, color, child) => ColoredBox(color: color!, child: child), + child: Padding(padding: const EdgeInsets.symmetric(vertical: 4), child: child), ); } diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/mlv_utils.dart b/packages/stream_chat_flutter/lib/src/message_list_view/mlv_utils.dart index 228c22318f..a213e6de58 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/mlv_utils.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/mlv_utils.dart @@ -100,3 +100,39 @@ bool isElementAtIndexVisible( bool isInitialMessage(String id, StreamChannelState? channelState) { return channelState!.initialMessageId == id; } + +/// Computes the [StreamMessageStackPosition] for [message] based on its +/// [previous] and [next] neighbors in the message list. +/// +/// A new group starts when: +/// - The neighbor is null (first/last message) +/// - The sender changes +/// - The timestamps fall in different calendar minutes +/// - The neighbor is a system, ephemeral, or error message +StreamMessageStackPosition computeStackPosition({ + required Message message, + Message? previous, + Message? next, +}) { + final isFirst = _isGroupBoundary(message, previous); + final isLast = _isGroupBoundary(message, next); + + return switch ((isFirst, isLast)) { + (true, true) => StreamMessageStackPosition.single, + (true, false) => StreamMessageStackPosition.top, + (false, false) => StreamMessageStackPosition.middle, + (false, true) => StreamMessageStackPosition.bottom, + }; +} + +bool _isGroupBoundary(Message message, Message? neighbor) { + if (neighbor == null) return true; + if (message.user?.id != neighbor.user?.id) return true; + if (neighbor.isSystem || neighbor.isEphemeral || neighbor.isError) return true; + + final createdAt = Jiffy.parseFromDateTime(message.createdAt.toLocal()); + final neighborCreatedAt = Jiffy.parseFromDateTime(neighbor.createdAt.toLocal()); + if (!createdAt.isSame(neighborCreatedAt, unit: Unit.minute)) return true; + + return false; +} diff --git a/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart index 5677a5f696..5c8f3a5596 100644 --- a/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart @@ -1,6 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/reactions/picker/reaction_picker_bubble_overlay.dart'; - import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@template streamMessageActionsModal} @@ -19,9 +17,8 @@ class StreamMessageActionsModal extends StatelessWidget { required this.message, required this.messageActions, required this.messageWidget, - this.reverse = false, + this.alignment, this.showReactionPicker = false, - this.reactionPickerBuilder = StreamReactionPicker.builder, }); /// The message object that actions will be performed on. @@ -38,18 +35,15 @@ class StreamMessageActionsModal extends StatelessWidget { /// The widget representing the message being acted upon. /// - /// This is typically displayed at the top of the modal as a reference for the - /// user. + /// This is typically displayed in the content section of the modal as a + /// reference for the user. final Widget messageWidget; - /// Whether the message should be displayed in reverse direction. - /// - /// This affects how the modal and reactions are displayed and aligned. - /// Set to `true` for right-aligned messages (typically the current user's). - /// Set to `false` for left-aligned messages (typically other users'). + /// Alignment of the modal content. /// - /// Defaults to `false`. - final bool reverse; + /// When null (the default), falls back to + /// [StreamMessagePlacement.alignmentDirectionalOf]. + final AlignmentGeometry? alignment; /// Controls whether to show the reaction picker at the top of the modal. /// @@ -59,43 +53,28 @@ class StreamMessageActionsModal extends StatelessWidget { /// Defaults to `false`. final bool showReactionPicker; - /// {@macro reactionPickerBuilder} - final ReactionPickerBuilder reactionPickerBuilder; - @override Widget build(BuildContext context) { final spacing = context.streamSpacing; - - final alignment = switch (reverse) { - true => AlignmentDirectional.centerEnd, - false => AlignmentDirectional.centerStart, - }; + final effectiveAlignment = alignment ?? StreamMessagePlacement.alignmentDirectionalOf(context); void onReactionPicked(Reaction reaction) { final action = SelectReaction(message: message, reaction: reaction); - return Navigator.pop(context, action); // Pop the modal with the selected reaction action + return Navigator.pop(context, action); } return StreamMessageDialog( spacing: spacing.xs, - alignment: alignment, - headerBuilder: (context) { - final safeArea = MediaQuery.paddingOf(context); - - return Padding( - padding: EdgeInsets.only(top: safeArea.top), - child: ReactionPickerBubbleOverlay( - message: message, - reverse: reverse, - visible: showReactionPicker, - anchorOffset: Offset(0, -spacing.xs), - onReactionPicked: onReactionPicked, - reactionPickerBuilder: reactionPickerBuilder, - child: IgnorePointer(child: messageWidget), - ), - ); + alignment: effectiveAlignment, + headerBuilder: switch (showReactionPicker) { + true => (context) => StreamMessageReactionPicker( + message: message, + onReactionPicked: onReactionPicked, + ), + false => null, }, - contentBuilder: (context) => StreamContextMenu(children: messageActions), + contentBuilder: (context) => IgnorePointer(child: messageWidget), + footerBuilder: (context) => StreamContextMenu(children: messageActions), ); } } diff --git a/packages/stream_chat_flutter/lib/src/message_modal/message_modal.dart b/packages/stream_chat_flutter/lib/src/message_modal/message_modal.dart index ec80700364..8e28dbd815 100644 --- a/packages/stream_chat_flutter/lib/src/message_modal/message_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_modal/message_modal.dart @@ -8,20 +8,22 @@ import 'package:stream_chat_flutter/src/utils/extensions.dart'; /// message-related dialog content. It handles layout, animation, and keyboard /// adjustments automatically. /// -/// The dialog can contain a header (optional) and content section (required), -/// and will adjust its position when the keyboard appears. +/// The dialog is laid out as a [Column] with three optional sections: +/// header, content, and footer. It adjusts its position when the keyboard +/// appears. /// {@endtemplate} class StreamMessageDialog extends StatelessWidget { /// Creates a Stream message dialog. /// /// The [contentBuilder] parameter is required to build the main content - /// of the dialog. The [headerBuilder] is optional and can be used to add - /// a header above the main content. + /// of the dialog. The [headerBuilder] and [footerBuilder] are optional and + /// can be used to add sections above and below the main content. const StreamMessageDialog({ super.key, this.spacing = 8.0, this.headerBuilder, required this.contentBuilder, + this.footerBuilder, this.useSafeArea = true, this.insetAnimationDuration = const Duration(milliseconds: 100), this.insetAnimationCurve = Curves.decelerate, @@ -29,7 +31,7 @@ class StreamMessageDialog extends StatelessWidget { this.alignment = Alignment.center, }); - /// Vertical spacing between header and content sections. + /// Vertical spacing between sections. final double spacing; /// Optional builder for the header section of the dialog. @@ -38,6 +40,9 @@ class StreamMessageDialog extends StatelessWidget { /// Required builder for the main content of the dialog. final WidgetBuilder contentBuilder; + /// Optional builder for the footer section of the dialog. + final WidgetBuilder? footerBuilder; + /// Whether to use a [SafeArea] to avoid system UI intrusions. /// /// Defaults to `true`. @@ -83,7 +88,8 @@ class StreamMessageDialog extends StatelessWidget { crossAxisAlignment: alignment.toColumnCrossAxisAlignment(), children: [ if (headerBuilder case final builder?) builder(context), - Flexible(child: contentBuilder(context)), + contentBuilder(context), + if (footerBuilder case final builder?) Flexible(child: builder(context)), ], ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart b/packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart deleted file mode 100644 index 41f4ff50c0..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; -import 'package:stream_chat_flutter/src/reactions/picker/reaction_picker_bubble_overlay.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template streamMessageReactionsModal} -/// A modal that displays message reactions and allows users to add reactions. -/// -/// This modal contains: -/// 1. A reaction picker (optional) that appears at the top -/// 2. The original message widget -/// 3. A display of all current reactions with user avatars -/// -/// The modal uses [StreamMessageDialog] as its base layout and customizes -/// both the header and content sections to display reaction-specific -/// information. -/// {@endtemplate} -class StreamMessageReactionsModal extends StatelessWidget { - /// {@macro streamMessageReactionsModal} - const StreamMessageReactionsModal({ - super.key, - required this.message, - required this.messageWidget, - this.reverse = false, - this.showReactionPicker = true, - this.reactionPickerBuilder = StreamReactionPicker.builder, - this.onUserAvatarTap, - }); - - /// The message for which to display and manage reactions. - final Message message; - - /// The original message widget that will be displayed in the modal. - final Widget messageWidget; - - /// Whether the message should be displayed in reverse direction. - /// - /// This affects how the modal and reactions are displayed and aligned. - /// Set to `true` for right-aligned messages (typically the current user's). - /// Set to `false` for left-aligned messages (typically other users'). - /// - /// Defaults to `false`. - final bool reverse; - - /// Controls whether to show the reaction picker at the top of the modal. - /// - /// When `true`, users can add reactions directly from the modal. - /// When `false`, the reaction picker is hidden. - final bool showReactionPicker; - - /// {@macro reactionPickerBuilder} - final ReactionPickerBuilder reactionPickerBuilder; - - /// Callback triggered when a user avatar is tapped in the reactions list. - /// - /// Provides the [User] object associated with the tapped avatar. - final void Function(User)? onUserAvatarTap; - - @override - Widget build(BuildContext context) { - final alignment = switch (reverse) { - true => AlignmentDirectional.centerEnd, - false => AlignmentDirectional.centerStart, - }; - - void onReactionPicked(Reaction reaction) { - final action = SelectReaction(message: message, reaction: reaction); - return Navigator.pop(context, action); // Pop the modal with the selected reaction action - } - - return StreamMessageDialog( - spacing: 4, - alignment: alignment, - headerBuilder: (context) { - final safeArea = MediaQuery.paddingOf(context); - - return Padding( - padding: EdgeInsets.only(top: safeArea.top), - child: ReactionPickerBubbleOverlay( - message: message, - reverse: reverse, - visible: showReactionPicker, - anchorOffset: const Offset(0, -8), - onReactionPicked: onReactionPicked, - reactionPickerBuilder: reactionPickerBuilder, - child: IgnorePointer(child: messageWidget), - ), - ); - }, - contentBuilder: (context) { - final reactions = message.latestReactions; - final hasReactions = reactions != null && reactions.isNotEmpty; - if (!hasReactions) return const Empty(); - - return FractionallySizedBox( - widthFactor: 0.78, - child: StreamUserReactions( - message: message, - onUserAvatarTap: onUserAvatarTap, - ), - ); - }, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart b/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart deleted file mode 100644 index 16f92ce7b1..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart +++ /dev/null @@ -1,295 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/message_widget/sending_indicator_builder.dart'; -import 'package:stream_chat_flutter/src/message_widget/thread_painter.dart'; -import 'package:stream_chat_flutter/src/message_widget/thread_participants.dart'; -import 'package:stream_chat_flutter/src/message_widget/username.dart'; -import 'package:stream_chat_flutter/src/misc/timestamp.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template bottomRow} -/// The bottom row of a [StreamMessageWidget]. -/// -/// Used in [MessageWidgetContent]. Should not be used elsewhere. -/// {@endtemplate} -class BottomRow extends StatelessWidget { - /// {@macro bottomRow} - const BottomRow({ - super.key, - required this.isDeleted, - required this.message, - required this.showThreadReplyIndicator, - required this.showInChannel, - required this.showTimeStamp, - required this.showUsername, - required this.showEditedLabel, - required this.reverse, - required this.showSendingIndicator, - required this.hasUrlAttachments, - required this.isGiphy, - required this.isOnlyEmoji, - required this.messageTheme, - required this.streamChatTheme, - required this.hasNonUrlAttachments, - required this.streamChat, - this.deletedBottomRowBuilder, - this.onThreadTap, - this.usernameBuilder, - this.sendingIndicatorBuilder, - }); - - /// {@macro messageIsDeleted} - final bool isDeleted; - - /// {@macro deletedBottomRowBuilder} - final Widget Function(BuildContext, Message)? deletedBottomRowBuilder; - - /// {@macro message} - final Message message; - - /// {@macro showThreadReplyIndicator} - final bool showThreadReplyIndicator; - - /// {@macro showInChannelIndicator} - final bool showInChannel; - - /// {@macro showTimestamp} - final bool showTimeStamp; - - /// {@macro showUsername} - final bool showUsername; - - /// {@macro showEdited} - final bool showEditedLabel; - - /// {@macro reverse} - final bool reverse; - - /// {@macro showSendingIndicator} - final bool showSendingIndicator; - - /// {@macro hasUrlAttachments} - final bool hasUrlAttachments; - - /// {@macro isGiphy} - final bool isGiphy; - - /// {@macro isOnlyEmoji} - final bool isOnlyEmoji; - - /// {@macro hasNonUrlAttachments} - final bool hasNonUrlAttachments; - - /// {@macro messageTheme} - final StreamMessageThemeData messageTheme; - - /// {@macro onThreadTap} - final void Function(Message)? onThreadTap; - - /// {@macro streamChatThemeData} - final StreamChatThemeData streamChatTheme; - - /// {@macro streamChat} - final StreamChatState streamChat; - - /// {@macro usernameBuilder} - final Widget Function(BuildContext, Message)? usernameBuilder; - - /// {@macro sendingIndicatorBuilder} - final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; - - /// {@template copyWith} - /// Creates a copy of [BottomRow] with specified attributes - /// overridden. - /// {@endtemplate} - BottomRow copyWith({ - Key? key, - bool? isDeleted, - Message? message, - bool? showThreadReplyIndicator, - bool? showInChannel, - bool? showTimeStamp, - bool? showUsername, - bool? showEditedLabel, - bool? reverse, - bool? showSendingIndicator, - bool? hasUrlAttachments, - bool? isGiphy, - bool? isOnlyEmoji, - StreamMessageThemeData? messageTheme, - StreamChatThemeData? streamChatTheme, - bool? hasNonUrlAttachments, - StreamChatState? streamChat, - Widget Function(BuildContext, Message)? deletedBottomRowBuilder, - void Function(Message)? onThreadTap, - Widget Function(BuildContext, Message)? usernameBuilder, - Widget Function(BuildContext, Message)? sendingIndicatorBuilder, - }) => BottomRow( - key: key ?? this.key, - isDeleted: isDeleted ?? this.isDeleted, - message: message ?? this.message, - showThreadReplyIndicator: showThreadReplyIndicator ?? this.showThreadReplyIndicator, - showInChannel: showInChannel ?? this.showInChannel, - showTimeStamp: showTimeStamp ?? this.showTimeStamp, - showUsername: showUsername ?? this.showUsername, - showEditedLabel: showEditedLabel ?? this.showEditedLabel, - reverse: reverse ?? this.reverse, - showSendingIndicator: showSendingIndicator ?? this.showSendingIndicator, - hasUrlAttachments: hasUrlAttachments ?? this.hasUrlAttachments, - isGiphy: isGiphy ?? this.isGiphy, - isOnlyEmoji: isOnlyEmoji ?? this.isOnlyEmoji, - messageTheme: messageTheme ?? this.messageTheme, - streamChatTheme: streamChatTheme ?? this.streamChatTheme, - hasNonUrlAttachments: hasNonUrlAttachments ?? this.hasNonUrlAttachments, - streamChat: streamChat ?? this.streamChat, - deletedBottomRowBuilder: deletedBottomRowBuilder ?? this.deletedBottomRowBuilder, - onThreadTap: onThreadTap ?? this.onThreadTap, - usernameBuilder: usernameBuilder ?? this.usernameBuilder, - sendingIndicatorBuilder: sendingIndicatorBuilder ?? this.sendingIndicatorBuilder, - ); - - @override - Widget build(BuildContext context) { - if (isDeleted) { - final deletedBottomRowBuilder = this.deletedBottomRowBuilder; - if (deletedBottomRowBuilder != null) { - return deletedBottomRowBuilder(context, message); - } - } - final textTheme = context.streamTextTheme; - final textStyle = textTheme.metadataDefault.copyWith( - color: context.streamColorScheme.textTertiary, - ); - final threadParticipants = message.threadParticipants?.take(2); - final showThreadParticipants = threadParticipants?.isNotEmpty == true; - final replyCount = message.replyCount; - final isEdited = message.messageTextUpdatedAt != null; - - var msg = context.translations.threadReplyLabel; - if (showThreadReplyIndicator && replyCount! > 0) { - msg = context.translations.threadReplyCountText(replyCount); - } - - Future _onThreadTap() async { - try { - var message = this.message; - if (showInChannel) { - final channel = StreamChannel.of(context); - message = await channel.getMessage(message.parentId!); - } - return onThreadTap?.call(message); - } catch (e, stk) { - debugPrint('Error while fetching message: $e, $stk'); - } - } - - const usernameKey = Key('username'); - - final children = [ - if (showSendingIndicator) - switch (sendingIndicatorBuilder) { - final builder? => builder(context, message), - _ => SendingIndicatorBuilder( - messageTheme: messageTheme, - message: message, - hasNonUrlAttachments: hasNonUrlAttachments, - streamChat: streamChat, - streamChatTheme: streamChatTheme, - ), - }, - if (showUsername) - switch (usernameBuilder) { - final builder? => builder(context, message), - _ => Username( - key: usernameKey, - message: message, - textStyle: textStyle, - ), - }, - if (showEditedLabel && isEdited) - Text( - context.translations.editedMessageLabel, - style: textStyle, - ), - if (showTimeStamp) - StreamTimestamp( - date: message.createdAt.toLocal(), - style: textStyle, - formatter: (context, date) { - if (messageTheme.createdAtFormatter case final formatter?) { - return formatter.call(context, date); - } - - return Jiffy.parseFromDateTime(date).jm; - }, - ), - ]; - - final showThreadTail = (showThreadReplyIndicator || showInChannel) && !isOnlyEmoji; - - final threadIndicatorWidgets = [ - if (showThreadTail) - // Added builder to use the nearest context to get the right - // textScaleFactor value. - Builder( - builder: (context) { - return Padding( - padding: EdgeInsets.only( - bottom: context.textScaleFactor * ((messageTheme.repliesStyle?.fontSize ?? 1) / 2), - ), - child: CustomPaint( - size: const Size(16, 40) * context.textScaleFactor, - painter: ThreadReplyPainter( - context: context, - color: messageTheme.messageBorderColor, - reverse: reverse, - ), - ), - ); - }, - ), - if (showInChannel || showThreadReplyIndicator) ...[ - if (showThreadParticipants) - ThreadParticipants( - threadParticipants: threadParticipants!, - ), - MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: _onThreadTap, - child: Text(msg, style: messageTheme.repliesStyle), - ), - ), - ], - ]; - - if (reverse) { - children.addAll(threadIndicatorWidgets.reversed); - } else { - children.insertAll(0, threadIndicatorWidgets); - } - - return Text.rich( - TextSpan( - children: [ - ...children.insertBetween(const SizedBox(width: 8)).map((child) { - final mediaQueryData = MediaQuery.of(context); - return WidgetSpan( - child: MediaQuery( - // Hardcoding the textScaleFactor to 1 to avoid the multiple - // resizing of the text. This is needed because the - // textScaleFactor is already applied to the textSpan. - // - // issue: https://github.com/GetStream/stream-chat-flutter/issues/1250 - // ignore: deprecated_member_use - data: mediaQueryData.copyWith(textScaleFactor: 1), - child: child, - ), - ); - }), - ], - ), - maxLines: 1, - textAlign: reverse ? TextAlign.right : TextAlign.left, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_content.dart b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_content.dart new file mode 100644 index 0000000000..e1ee7f07a1 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_content.dart @@ -0,0 +1,213 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:stream_chat_flutter/src/attachment/builder/attachment_widget_builder.dart'; +import 'package:stream_chat_flutter/src/channel/stream_message_preview_text.dart'; +import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar.dart'; +import 'package:stream_chat_flutter/src/message_widget/components/stream_message_deleted.dart'; +import 'package:stream_chat_flutter/src/message_widget/components/stream_message_reactions.dart'; +import 'package:stream_chat_flutter/src/message_widget/components/stream_message_text.dart'; +import 'package:stream_chat_flutter/src/message_widget/parse_attachments.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; + +/// Composes the main message content including the bubble, attachments, text, +/// reactions, and thread reply indicator. +/// +/// For deleted messages a [StreamMessageDeleted] placeholder is shown. +/// Otherwise the content displays attachments, message text, reactions, and +/// a thread reply indicator (when [Message.replyCount] is greater than zero). +/// +/// When the message consists of three or fewer emoji-only characters, the +/// bubble background is hidden so the emoji appear at a larger visual size. +/// +/// See also: +/// +/// * [StreamMessageReactions], which renders reactions around the bubble. +/// * [StreamMessageText], which renders the markdown message text. +/// * [DefaultStreamMessage], which hosts this widget. +class StreamMessageContent extends StatefulWidget { + /// Creates a message content widget for the given [message]. + const StreamMessageContent({ + super.key, + required this.message, + this.header, + this.footer, + this.attachmentBuilders, + this.onLinkTap, + this.onMentionTap, + this.onReactionsTap, + this.onRepliesTap, + this.onQuotedMessageTap, + this.reactionSorting, + }); + + /// The message to display. + final Message message; + + /// Optional header widget displayed above the message content column. + /// + /// Typically a [streamMessageHeader] result containing pinned, reminder, + /// or show-in-channel annotations. + final Widget? header; + + /// Optional footer widget displayed below the message content column. + /// + /// Typically a [StreamMessageFooter] containing the author name, timestamp, + /// and sending status. + final Widget? footer; + + /// Custom attachment builders for rendering message attachments. + /// + /// When non-null, these builders are passed to [ParseAttachments] and + /// take priority over the default builders. + final List? attachmentBuilders; + + /// Called when a link is tapped in the rendered message text. + /// + /// If null, tapping a link has no effect. + final MarkdownTapLinkCallback? onLinkTap; + + /// Called when a `@mention` is tapped in the rendered message text. + /// + /// If null, tapping a mention has no effect. + final core.MarkdownTapMentionCallback? onMentionTap; + + /// Called when the reactions area is tapped. + /// + /// If null, tapping reactions has no effect. + final VoidCallback? onReactionsTap; + + /// Called when the thread reply indicator is tapped. + /// + /// If null, tapping the reply indicator has no effect. + final VoidCallback? onRepliesTap; + + /// Called when the quoted message is tapped. + /// + /// If null, tapping the quoted message has no effect. + final void Function(Message quotedMessage)? onQuotedMessageTap; + + /// Controls how reaction groups are sorted when displayed. + /// + /// Passed through to [StreamMessageReactions.sorting]. + final Comparator? reactionSorting; + + @override + State createState() => _StreamMessageContentState(); +} + +class _StreamMessageContentState extends State { + // Tracks the rendered width of the attachments to constrain the bubble. + double? widthLimit; + late final attachmentsKey = GlobalKey(debugLabel: 'ParseAttachments'); + + // Measures the attachment width after layout and constrains the bubble. + void _updateWidthLimit() { + final attachmentContext = attachmentsKey.currentContext; + final renderBox = attachmentContext?.findRenderObject() as RenderBox?; + final attachmentsWidth = renderBox?.size.width; + + if (attachmentsWidth == null || attachmentsWidth == 0) return; + if (mounted) setState(() => widthLimit = attachmentsWidth); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + WidgetsBinding.instance.addPostFrameCallback((_) => _updateWidthLimit()); + } + + @override + Widget build(BuildContext context) { + final spacing = context.streamSpacing; + final crossAxisAlignment = core.StreamMessagePlacement.crossAxisAlignmentOf(context); + + if (widget.message.isDeleted) return const StreamMessageDeleted(); + + return core.StreamMessageContent( + header: widget.header, + footer: widget.footer, + child: core.StreamColumn( + mainAxisSize: .min, + crossAxisAlignment: crossAxisAlignment, + children: [ + StreamMessageReactions( + message: widget.message, + sorting: widget.reactionSorting, + onPressed: widget.onReactionsTap, + child: Builder( + builder: (context) { + final bubbleContent = ConstrainedBox( + constraints: const BoxConstraints().copyWith(maxWidth: widthLimit), + child: Column( + spacing: spacing.xxs, + mainAxisSize: .min, + crossAxisAlignment: .start, + children: [ + if (widget.message.quotedMessage case final quotedMessage?) + // TODO: Refactor this with attachments + GestureDetector( + onTap: !quotedMessage.isDeleted && widget.onQuotedMessageTap != null + ? () => widget.onQuotedMessageTap!(quotedMessage) + : null, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: core.StreamMessageTheme( + data: core.StreamMessageThemeData( + incoming: core.StreamMessageStyle( + backgroundColor: context.streamColorScheme.backgroundSurfaceStrong, + ), + outgoing: core.StreamMessageStyle( + backgroundColor: context.streamColorScheme.brand.shade150, + ), + ), + child: core.MessageComposerReplyAttachment( + title: Text(quotedMessage.user?.name ?? ''), + subtitle: StreamMessagePreviewText(message: quotedMessage), + style: switch (core.StreamMessagePlacement.messageAlignmentOf(context)) { + core.StreamMessageAlignment.start => .incoming, + core.StreamMessageAlignment.end => .outgoing, + }, + ), + ), + ), + ), + ParseAttachments( + key: attachmentsKey, + message: widget.message, + attachmentBuilders: widget.attachmentBuilders, + attachmentPadding: .symmetric(horizontal: spacing.xs), + ), + if (widget.message.text case final text? when text.isNotEmpty) + StreamMessageText( + message: widget.message, + onLinkTap: widget.onLinkTap, + onMentionTap: widget.onMentionTap, + ), + ], + ), + ); + + final emojiCount = core.StreamMessageText.emojiOnlyCount(widget.message.text); + final hideBubble = emojiCount != null && emojiCount <= 3; + + if (hideBubble) return bubbleContent; + return core.StreamMessageBubble(child: bubbleContent); + }, + ), + ), + if (widget.message.replyCount case final replyCount? when replyCount > 0) + core.StreamMessageReplies( + maxAvatars: 3, + showConnector: true, + onTap: widget.onRepliesTap, + label: Text('$replyCount replies'), + avatars: widget.message.threadParticipants?.map( + (user) => StreamUserAvatar(user: user, showOnlineIndicator: false), + ), + ), + ], + ), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_deleted.dart b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_deleted.dart new file mode 100644 index 0000000000..e24f8d187b --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_deleted.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/utils/extensions.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; + +/// Displays a "Message deleted" indicator inside a message bubble. +/// +/// Shown in place of the normal message content when [Message.isDeleted] +/// is true. +/// +/// See also: +/// +/// * [StreamMessageScaffold], which shows this widget for deleted messages. +class StreamMessageDeleted extends StatelessWidget { + /// Creates a deleted message widget. + const StreamMessageDeleted({super.key}); + + @override + Widget build(BuildContext context) { + final icons = context.streamIcons; + final spacing = context.streamSpacing; + + return core.StreamMessageBubble( + padding: .symmetric( + horizontal: spacing.sm, + vertical: spacing.xs, + ), + child: Row( + spacing: spacing.xxs, + mainAxisSize: .min, + children: [ + Icon(icons.circleBanSign, size: 16), + core.StreamMessageText(padding: .zero, context.translations.messageDeletedLabel), + ], + ), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_footer.dart b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_footer.dart new file mode 100644 index 0000000000..9d8396754d --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_footer.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:jiffy/jiffy.dart'; +import 'package:stream_chat_flutter/src/message_widget/components/stream_message_sending_status.dart'; +import 'package:stream_chat_flutter/src/misc/timestamp.dart'; +import 'package:stream_chat_flutter/src/stream_chat.dart'; +import 'package:stream_chat_flutter/src/utils/extensions.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; + +/// Displays the message footer containing the author name, sending status, +/// creation timestamp, and an edited indicator. +/// +/// The footer can show up to four metadata pieces depending on the message: +/// +/// * **Username** — for messages from other users. +/// * **Sending status** — for the current user's own messages. +/// * **Timestamp** — always shown, formatted as a short time string. +/// * **Edited label** — when the message text has been updated. +/// +/// See also: +/// +/// * [StreamMessageSendingStatus], which renders the sent/delivered/read +/// indicator. +/// * [DefaultStreamMessage], which controls footer visibility. +class StreamMessageFooter extends StatelessWidget { + /// Creates a message footer for the given [message]. + const StreamMessageFooter({super.key, required this.message}); + + /// The message whose footer to display. + final Message message; + + @override + Widget build(BuildContext context) { + final currentUser = StreamChat.of(context).currentUser; + + Widget? usernameWidget; + if (message.user case final user? when user.id != currentUser?.id) { + usernameWidget = Text(user.name, maxLines: 1, overflow: .ellipsis); + } + + Widget? statusWidget; + if (message.user case final user? when user.id == currentUser?.id) { + statusWidget = StreamMessageSendingStatus(message: message); + } + + final Widget timestampWidget; + if (message.createdAt case final createdAt) { + timestampWidget = StreamTimestamp( + date: createdAt.toLocal(), + formatter: (context, date) { + return Jiffy.parseFromDateTime(date).jm; + }, + ); + } + + Widget? editedWidget; + if (message.messageTextUpdatedAt != null) { + editedWidget = Text(context.translations.editedMessageLabel); + } + + return core.StreamMessageMetadata( + username: usernameWidget, + status: statusWidget, + timestamp: timestampWidget, + edited: editedWidget, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_header.dart b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_header.dart new file mode 100644 index 0000000000..8355ea9b6c --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_header.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; +import 'package:jiffy/jiffy.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// Builds the message header containing contextual annotations for the given +/// [message]. +/// +/// Annotations are shown in the following order when applicable: +/// +/// 1. **Saved for later** — when a reminder exists without a scheduled time. +/// 2. **Pinned** — when [Message.pinned] is true, showing who pinned it. +/// 3. **Show in channel / Replied to thread** — when [Message.showInChannel] +/// is true. The label adapts based on whether the message list is a +/// channel or thread view, and includes a tappable "View" link that +/// invokes [onViewChannelTap]. +/// 4. **Reminder** — when a reminder exists with a scheduled time. +/// +/// Returns `null` when no annotations apply, allowing the caller to skip +/// rendering the header entirely. +/// +/// See also: +/// +/// * [DefaultStreamMessage], which controls header visibility. +Widget? streamMessageHeader({ + required BuildContext context, + required Message message, + VoidCallback? onViewChannelTap, +}) { + final icons = context.streamIcons; + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; + final crossAxisAlignment = StreamMessagePlacement.crossAxisAlignmentOf(context); + + Widget? savedForLaterAnnotation; + if (message.reminder case final reminder? when reminder.remindAt == null) { + savedForLaterAnnotation = StreamMessageAnnotation( + leading: Icon(icons.bookmark, color: colorScheme.accentPrimary), + label: Text('Saved for later', style: TextStyle(color: colorScheme.accentPrimary)), + ); + } + + Widget? pinnedAnnotation; + if (message.pinned case true) { + pinnedAnnotation = StreamMessageAnnotation( + leading: Icon(icons.pin), + label: switch (message.pinnedBy) { + final pinnedBy? => Text('Pinned by ${pinnedBy.name}'), + _ => const Text('Pinned by You'), + }, + ); + } + + Widget? showInChannelAnnotation; + if (message.showInChannel case true) { + final listKind = StreamMessagePlacement.listKindOf(context); + final annotationLabel = switch (listKind) { + .channel => 'Replied to a thread · ', + .thread => 'Also sent in channel · ', + }; + + showInChannelAnnotation = StreamMessageAnnotation( + onTap: onViewChannelTap, + leading: Icon(icons.arrowUp), + label: Text.rich( + TextSpan( + text: annotationLabel, + children: [ + TextSpan( + text: 'View', + style: textTheme.metadataDefault.copyWith(color: colorScheme.textLink), + ), + ], + ), + ), + ); + } + + Widget? reminderAnnotation; + if (message.reminder?.remindAt?.toLocal() case final remindAt?) { + reminderAnnotation = StreamMessageAnnotation( + leading: Icon(icons.bellNotification), + label: Text.rich( + TextSpan( + text: 'Reminder set · ', + children: [ + TextSpan( + text: 'Today at ${Jiffy.parseFromDateTime(remindAt).jm}', + style: textTheme.metadataDefault, + ), + ], + ), + ), + ); + } + + final children = [ + ?savedForLaterAnnotation, + ?pinnedAnnotation, + ?showInChannelAnnotation, + ?reminderAnnotation, + ]; + + if (children.isEmpty) return null; + + return StreamColumn( + mainAxisSize: .min, + crossAxisAlignment: crossAxisAlignment, + children: children, + ); +} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_leading.dart b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_leading.dart new file mode 100644 index 0000000000..ab1a956a83 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_leading.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +/// Displays the message author's avatar as the leading widget in a message +/// row. +/// +/// Visibility of this widget (visible, hidden, or gone) is controlled by +/// [StreamMessageItemThemeData.leadingVisibility] in the parent +/// [DefaultStreamMessage]. +/// +/// See also: +/// +/// * [StreamUserAvatar], which renders the avatar image. +/// * [DefaultStreamMessage], which controls when this widget is shown. +class StreamMessageLeading extends StatelessWidget { + /// Creates a message leading widget for the given [author]. + const StreamMessageLeading({ + super.key, + required this.author, + }); + + /// The user whose avatar to display. + final User author; + + @override + Widget build(BuildContext context) { + return StreamUserAvatar( + user: author, + showOnlineIndicator: false, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_reactions.dart b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_reactions.dart new file mode 100644 index 0000000000..679a069f90 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_reactions.dart @@ -0,0 +1,88 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/stream_chat_configuration.dart'; +import 'package:stream_chat_flutter/src/utils/device_segmentation.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; + +/// Displays reaction groups for a message as emoji chips overlaid on, or +/// placed beneath, the [child] widget. +/// +/// Reaction icons are resolved through the +/// [StreamChatConfigurationData.reactionIconResolver]. Groups are sorted +/// using [sorting] (defaults to [ReactionSorting.byFirstReactionAt]). +/// +/// See also: +/// +/// * [StreamMessageScaffold], which hosts this widget around the bubble. +/// * [StreamChatConfigurationData.reactionIconResolver], which maps reaction +/// type strings to emoji widgets. +class StreamMessageReactions extends StatelessWidget { + /// Creates a message reactions widget for the given [message]. + const StreamMessageReactions({ + super.key, + required this.message, + this.type, + this.position, + this.sorting, + this.onPressed, + this.child, + }); + + /// The message whose reactions to display. + final Message message; + + /// The visual type of the reactions display. + /// + /// Defaults to [core.StreamReactionsType.segmented] when null. + final core.StreamReactionsType? type; + + /// Where the reactions appear relative to the message bubble. + /// + /// Defaults to [core.StreamReactionsPosition.footer] on desktop and web, + /// and [core.StreamReactionsPosition.header] on mobile. + final core.StreamReactionsPosition? position; + + /// Controls how reaction groups are sorted when displayed. + /// + /// Defaults to [ReactionSorting.byFirstReactionAt] when null. + final Comparator? sorting; + + /// Called when the reactions area is pressed. + /// + /// If null, pressing the reactions area has no effect. + final VoidCallback? onPressed; + + /// The child widget (typically the message bubble) that reactions are + /// displayed on. + final Widget? child; + + @override + Widget build(BuildContext context) { + final config = StreamChatConfiguration.of(context); + final resolver = config.reactionIconResolver; + + final effectiveType = type ?? config.reactionType ?? core.StreamReactionsType.segmented; + final effectivePosition = position ?? config.reactionPosition ?? core.StreamReactionsPosition.header; + + final reactionGroups = message.reactionGroups?.entries; + final effectiveReactionSorting = sorting ?? ReactionSorting.byFirstReactionAt; + final sortedReactionGroups = reactionGroups?.sortedByCompare((it) => it.value, effectiveReactionSorting); + + final items = sortedReactionGroups?.map( + (group) => core.StreamReactionsItem( + count: group.value.count, + emoji: core.StreamEmoji(size: .sm, emoji: resolver.resolve(context, group.key)), + ), + ); + + return core.StreamReactions( + type: effectiveType, + position: effectivePosition, + overlap: !isDesktopDeviceOrWeb, + onPressed: onPressed, + items: [...?items], + child: child, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_sending_status.dart b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_sending_status.dart new file mode 100644 index 0000000000..9c9e157176 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_sending_status.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/indicators/sending_indicator.dart'; +import 'package:stream_chat_flutter/src/utils/extensions.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +/// Displays the sending status of a message, including attachment upload +/// progress and sent/delivered/read indicators. +/// +/// While attachments are still uploading, a textual progress label is shown. +/// Once the message is fully sent, an icon indicates whether it has been +/// sent, delivered, or read. +/// +/// This widget is typically used inside [StreamMessageFooter] and is only +/// shown for messages sent by the current user. +/// +/// See also: +/// +/// * [StreamSendingIndicator], which renders the sent/delivered/read icon. +/// * [StreamMessageFooter], which hosts this widget. +class StreamMessageSendingStatus extends StatelessWidget { + /// Creates a sending status widget for the given [message]. + const StreamMessageSendingStatus({ + super.key, + required this.message, + }); + + /// The message whose sending status to display. + final Message message; + + @override + Widget build(BuildContext context) { + final hasNonUrlAttachments = message.attachments.any((it) => it.type != AttachmentType.urlPreview); + + if (hasNonUrlAttachments && message.state.isOutgoing) { + final totalAttachments = message.attachments.length; + final attachmentsToUpload = message.attachments.where((it) => !it.uploadState.isSuccess); + + if (attachmentsToUpload.isNotEmpty) { + return Text( + context.translations.attachmentsUploadProgressText( + remaining: attachmentsToUpload.length, + total: totalAttachments, + ), + ); + } + } + + final channel = StreamChannel.maybeOf(context)?.channel; + + return BetterStreamBuilder>( + stream: channel?.state?.readStream, + initialData: channel?.state?.read, + builder: (context, data) { + final readList = data.readsOf(message: message); + final isMessageRead = readList.isNotEmpty; + + final deliveriesList = data.deliveriesOf(message: message); + final isMessageDelivered = deliveriesList.isNotEmpty; + + return StreamSendingIndicator( + message: message, + isMessageRead: isMessageRead, + isMessageDelivered: isMessageDelivered, + ); + }, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_text.dart b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_text.dart new file mode 100644 index 0000000000..f273f32ea2 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_text.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; +import 'package:stream_chat_flutter/src/stream_chat.dart'; +import 'package:stream_chat_flutter/src/utils/device_segmentation.dart'; +import 'package:stream_chat_flutter/src/utils/extensions.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; + +/// Displays the translated markdown message text, reacting to the current +/// user's language preference. +/// +/// The message text is translated into the current user's language, mention +/// syntax is replaced with display names, and the result is rendered as +/// markdown. +/// +/// The widget rebuilds automatically when the current user's language +/// changes, ensuring the displayed text stays in sync. +/// +/// On desktop and web the text is selectable; on mobile it is not. +/// +/// See also: +/// +/// * [StreamMessageScaffold], which hosts this widget inside a message bubble. +class StreamMessageText extends StatelessWidget { + /// Creates a message text widget for the given [message]. + const StreamMessageText({ + super.key, + required this.message, + this.onLinkTap, + this.onMentionTap, + }); + + /// The message whose text to display. + final Message message; + + /// Called when a link in the rendered markdown is tapped. + /// + /// If null, tapping a link has no effect. + final MarkdownTapLinkCallback? onLinkTap; + + /// Called when a `@mention` in the rendered markdown is tapped. + /// + /// Mentions use the `[text](mention:id)` format in the raw markdown. + /// If null, tapping a mention has no effect. + final core.MarkdownTapMentionCallback? onMentionTap; + + @override + Widget build(BuildContext context) { + final streamChat = StreamChat.of(context); + + return BetterStreamBuilder( + initialData: streamChat.currentUser?.language ?? 'en', + stream: streamChat.currentUserStream.map((it) => it?.language ?? 'en'), + builder: (context, language) { + final messageText = message.translate(language).replaceMentions().text?.replaceAll('\n', '\n\n').trim(); + + if (messageText == null || messageText.trim().isEmpty) return const Empty(); + + return core.StreamMessageText( + messageText, + selectable: isDesktopDeviceOrWeb, + onTapLink: onLinkTap, + onTapMention: onMentionTap, + ); + }, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/deleted_message.dart b/packages/stream_chat_flutter/lib/src/message_widget/deleted_message.dart deleted file mode 100644 index a184af5463..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/deleted_message.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template streamDeletedMessage} -/// Displays that a message was deleted at this position in the message list. -/// {@endtemplate} -class StreamDeletedMessage extends StatelessWidget { - /// {@macro streamDeletedMessage} - const StreamDeletedMessage({ - super.key, - required this.messageTheme, - this.borderRadiusGeometry, - this.shape, - this.borderSide, - this.reverse = false, - }); - - /// The theme of the message - final StreamMessageThemeData messageTheme; - - /// The border radius of the message text - final BorderRadiusGeometry? borderRadiusGeometry; - - /// The shape of the message text - final ShapeBorder? shape; - - /// The [BorderSide] of the message text - final BorderSide? borderSide; - - /// If true the widget will be mirrored - final bool reverse; - - @override - Widget build(BuildContext context) { - return Material( - color: messageTheme.messageBackgroundColor, - shape: - shape ?? - RoundedRectangleBorder( - borderRadius: borderRadiusGeometry ?? BorderRadius.zero, - side: - borderSide ?? - BorderSide( - color: messageTheme.messageBorderColor ?? Colors.transparent, - ), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 16, - ), - child: Text( - context.translations.messageDeletedLabel, - style: messageTheme.messageDeletedStyle, - ), - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_card.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_card.dart deleted file mode 100644 index 071feadbf4..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_card.dart +++ /dev/null @@ -1,270 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_core_flutter/stream_core_flutter.dart' as core; - -/// {@template messageCard} -/// The widget containing a quoted message. -/// -/// Used in [MessageWidgetContent]. Should not be used elsewhere. -/// {@endtemplate} -class MessageCard extends StatefulWidget { - /// {@macro messageCard} - const MessageCard({ - super.key, - required this.message, - required this.isFailedState, - required this.showUserAvatar, - required this.messageTheme, - required this.hasQuotedMessage, - required this.hasUrlAttachments, - required this.hasNonUrlAttachments, - required this.isOnlyEmoji, - required this.isGiphy, - required this.attachmentBuilders, - required this.attachmentPadding, - required this.attachmentShape, - required this.onAttachmentTap, - required this.onShowMessage, - required this.onReplyTap, - required this.attachmentActionsModalBuilder, - required this.textPadding, - required this.reverse, - this.shape, - this.borderSide, - this.borderRadiusGeometry, - this.textBuilder, - this.quotedMessageBuilder, - this.onLinkTap, - this.onMentionTap, - this.onQuotedMessageTap, - }); - - /// {@macro isFailedState} - final bool isFailedState; - - /// {@macro showUserAvatar} - final DisplayWidget showUserAvatar; - - /// {@macro shape} - final ShapeBorder? shape; - - /// {@macro borderSide} - final BorderSide? borderSide; - - /// {@macro messageTheme} - final StreamMessageThemeData messageTheme; - - /// {@macro borderRadiusGeometry} - final BorderRadiusGeometry? borderRadiusGeometry; - - /// {@macro hasQuotedMessage} - final bool hasQuotedMessage; - - /// {@macro hasUrlAttachments} - final bool hasUrlAttachments; - - /// {@macro hasNonUrlAttachments} - final bool hasNonUrlAttachments; - - /// {@macro isOnlyEmoji} - final bool isOnlyEmoji; - - /// {@macro isGiphy} - final bool isGiphy; - - /// {@macro message} - final Message message; - - /// {@macro attachmentBuilders} - final List? attachmentBuilders; - - /// {@macro attachmentPadding} - final EdgeInsetsGeometry attachmentPadding; - - /// {@macro attachmentShape} - final ShapeBorder? attachmentShape; - - /// {@macro onAttachmentWidgetTap} - final OnAttachmentWidgetTap? onAttachmentTap; - - /// {@macro onShowMessage} - final ShowMessageCallback? onShowMessage; - - /// {@macro onReplyTap} - final void Function(Message)? onReplyTap; - - /// {@macro attachmentActionsBuilder} - final AttachmentActionsBuilder? attachmentActionsModalBuilder; - - /// {@macro textPadding} - final EdgeInsets textPadding; - - /// {@macro textBuilder} - final Widget Function(BuildContext, Message)? textBuilder; - - /// {@macro quotedMessageBuilder} - final Widget Function(BuildContext, Message)? quotedMessageBuilder; - - /// {@macro onLinkTap} - final void Function(String)? onLinkTap; - - /// {@macro onMentionTap} - final void Function(User)? onMentionTap; - - /// {@macro onQuotedMessageTap} - final OnQuotedMessageTap? onQuotedMessageTap; - - /// {@macro reverse} - final bool reverse; - - @override - State createState() => _MessageCardState(); -} - -class _MessageCardState extends State { - final attachmentsKey = GlobalKey(); - double? widthLimit; - - void _updateWidthLimit() { - final attachmentContext = attachmentsKey.currentContext; - final renderBox = attachmentContext?.findRenderObject() as RenderBox?; - final attachmentsWidth = renderBox?.size.width; - - if (attachmentsWidth == null || attachmentsWidth == 0) return; - - if (mounted) { - setState(() => widthLimit = attachmentsWidth); - } - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - // If there is an attachment, we need to wait for the attachment to be - // rendered to get the width of the attachment and set it as the width - // limit of the message card. - WidgetsBinding.instance.addPostFrameCallback((_) { - _updateWidthLimit(); - }); - } - - @override - Widget build(BuildContext context) { - final onQuotedMessageTap = widget.onQuotedMessageTap; - final quotedMessageBuilder = widget.quotedMessageBuilder; - final coreTheme = context.streamMessageTheme.mergeWithDefaults(context); - final messageStyle = widget.reverse ? coreTheme.outgoing! : coreTheme.incoming!; - final currentUser = StreamChat.maybeOf(context)?.currentUser; - final colorScheme = context.streamColorScheme; - - return Container( - constraints: const BoxConstraints().copyWith(maxWidth: widthLimit), - margin: EdgeInsetsDirectional.only( - end: widget.reverse && widget.isFailedState ? 12.0 : 0.0, - start: !widget.reverse && widget.isFailedState ? 12.0 : 0.0, - ), - clipBehavior: Clip.hardEdge, - decoration: _buildDecoration(messageStyle, widget.messageTheme), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (widget.hasQuotedMessage) - InkWell( - onTap: !widget.message.quotedMessage!.isDeleted && onQuotedMessageTap != null - ? () => onQuotedMessageTap(widget.message.quotedMessageId) - : null, - child: core.StreamMessageTheme( - data: core.StreamMessageThemeData( - incoming: core.StreamMessageStyle( - backgroundColor: colorScheme.backgroundSurfaceStrong, - ), - outgoing: core.StreamMessageStyle( - backgroundColor: colorScheme.brand.shade150, - ), - ), - child: - quotedMessageBuilder?.call( - context, - widget.message.quotedMessage!, - ) ?? - core.MessageComposerReplyAttachment( - title: Text(widget.message.quotedMessage!.user?.name ?? ''), - subtitle: StreamMessagePreviewText(message: widget.message.quotedMessage!), - style: currentUser?.id == widget.message.quotedMessage!.user?.id ? .outgoing : .incoming, - ), - ), - ), - ParseAttachments( - key: attachmentsKey, - message: widget.message, - attachmentBuilders: widget.attachmentBuilders, - attachmentPadding: widget.attachmentPadding, - attachmentShape: widget.attachmentShape, - onAttachmentTap: widget.onAttachmentTap, - onShowMessage: widget.onShowMessage, - onLinkTap: widget.onLinkTap, - onReplyTap: widget.onReplyTap, - attachmentActionsModalBuilder: widget.attachmentActionsModalBuilder, - ), - TextBubble( - messageStyle: messageStyle, - message: widget.message, - textPadding: widget.textPadding, - textBuilder: widget.textBuilder, - isOnlyEmoji: widget.isOnlyEmoji, - hasQuotedMessage: widget.hasQuotedMessage, - hasUrlAttachments: widget.hasUrlAttachments, - onLinkTap: widget.onLinkTap, - onMentionTap: widget.onMentionTap, - ), - ], - ), - ); - } - - ShapeDecoration _buildDecoration(core.StreamMessageStyle messageStyle, StreamMessageThemeData theme) { - final gradient = _getBackgroundGradient(theme); - final color = gradient == null ? _getBackgroundColor(messageStyle) : null; - - final borderColor = theme.messageBorderColor ?? Colors.transparent; - final borderRadius = widget.borderRadiusGeometry ?? BorderRadius.zero; - - return ShapeDecoration( - color: color, - gradient: gradient, - shape: switch (widget.shape) { - final shape? => shape, - _ => RoundedRectangleBorder( - borderRadius: borderRadius, - side: switch (widget.borderSide) { - final side? => side, - _ => BorderSide(color: borderColor), - }, - ), - }, - ); - } - - Color? _getBackgroundColor(core.StreamMessageStyle theme) { - if (widget.hasQuotedMessage) { - return theme.backgroundColor; - } - - final containsOnlyUrlAttachment = widget.hasUrlAttachments && !widget.hasNonUrlAttachments; - - if (containsOnlyUrlAttachment) { - return theme.backgroundAttachmentColor; - } - - if (widget.isOnlyEmoji) return null; - - return theme.backgroundColor; - } - - Gradient? _getBackgroundGradient(StreamMessageThemeData theme) { - if (widget.isOnlyEmoji) return null; - - return theme.messageBackgroundGradient; - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_text.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_text.dart deleted file mode 100644 index 60975daf1d..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_text.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:collection/collection.dart' show IterableExtension; -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template streamMessageText} -/// The text content of a message. -/// {@endtemplate} -class StreamMessageText extends StatelessWidget { - /// {@macro streamMessageText} - const StreamMessageText({ - super.key, - required this.message, - this.messageTheme, - this.onMentionTap, - this.onLinkTap, - }); - - /// Message whose text is to be displayed - final Message message; - - /// The action to perform when a mention is tapped - final void Function(User)? onMentionTap; - - /// The action to perform when a link is tapped - final void Function(String)? onLinkTap; - - /// [StreamMessageThemeData] whose text theme is to be applied - final StreamMessageThemeData? messageTheme; - - @override - Widget build(BuildContext context) { - final streamChat = StreamChat.of(context); - assert(streamChat.currentUser != null, ''); - return BetterStreamBuilder( - stream: streamChat.currentUserStream.map((it) => it!.language ?? 'en'), - initialData: streamChat.currentUser!.language ?? 'en', - builder: (context, language) { - final messageText = message.translate(language).replaceMentions().text?.replaceAll('\n', '\n\n').trim(); - - return StreamMarkdownMessage( - data: messageText ?? '', - messageTheme: messageTheme, - selectable: isDesktopDeviceOrWeb, - onTapLink: - ( - String text, - String? href, - String title, - ) { - if (text.startsWith('@')) { - final mentionedUser = message.mentionedUsers.firstWhereOrNull( - (u) => '@${u.name}' == text, - ); - - if (mentionedUser == null) return; - - onMentionTap?.call(mentionedUser); - } else if (href != null) { - if (onLinkTap != null) { - onLinkTap!(href); - } else { - launchURL(context, href); - } - } - }, - ); - }, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart index 889271d245..42f3f253a8 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart @@ -1,763 +1,494 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_portal/flutter_portal.dart'; -import 'package:stream_chat_flutter/platform_widget_builder/platform_widget_builder.dart'; +import 'package:stream_chat_flutter/platform_widget_builder/src/platform_widget_builder.dart'; import 'package:stream_chat_flutter/src/context_menu/context_menu.dart'; import 'package:stream_chat_flutter/src/context_menu/context_menu_region.dart'; -import 'package:stream_chat_flutter/src/message_widget/message_widget_content.dart'; +import 'package:stream_chat_flutter/src/message_widget/components/stream_message_content.dart'; +import 'package:stream_chat_flutter/src/message_widget/components/stream_message_footer.dart'; +import 'package:stream_chat_flutter/src/message_widget/components/stream_message_header.dart'; +import 'package:stream_chat_flutter/src/message_widget/components/stream_message_leading.dart'; import 'package:stream_chat_flutter/src/misc/flexible_fractionally_sized_box.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' hide StreamMessageContent; -/// The display behaviour of a widget -enum DisplayWidget { - /// Hides the widget replacing its space with a spacer - hide, - - /// Hides the widget not replacing its space - gone, +/// A chat message widget that renders a single message with its attachments, +/// reactions, and interaction callbacks. +/// +/// [StreamMessageWidget] displays a single [Message] within a chat message +/// list. It handles the complete message layout including the author avatar, +/// message content (text, attachments, polls, quoted messages), reactions, +/// thread indicators, and user interaction gestures such as tap, long-press, +/// and context menus. +/// +/// On mobile platforms, a long-press opens the [StreamMessageActionsModal] +/// with available actions (reply, edit, delete, pin, etc.). On desktop and +/// web, those same actions appear in a right-click context menu. +/// +/// This widget delegates rendering to either a custom builder registered via +/// [StreamComponentFactory], or [DefaultStreamMessage] when no custom builder +/// is provided. Register a custom builder through [StreamChatConfigurationData] +/// to fully replace the default message layout while still receiving the same +/// [StreamMessageWidgetProps]. +/// +/// {@tool snippet} +/// +/// Display a message with default settings: +/// +/// ```dart +/// StreamMessageWidget( +/// message: message, +/// ) +/// ``` +/// {@end-tool} +/// +/// {@tool snippet} +/// +/// Customise interaction callbacks: +/// +/// ```dart +/// StreamMessageWidget( +/// message: message, +/// onMessageTap: (msg) => print('Tapped: ${msg.id}'), +/// onThreadTap: (msg) => Navigator.push(...), +/// onUserAvatarTap: (user) => showProfile(user), +/// ) +/// ``` +/// {@end-tool} +/// +/// See also: +/// +/// * [StreamMessageWidgetProps], which holds every configurable property. +/// * [DefaultStreamMessage], the default implementation used when no custom +/// builder is registered. +/// * [StreamMessageActionsModal], the modal shown on long-press (mobile). +/// * [StreamMessageListView], which hosts a scrollable list of these widgets. +class StreamMessageWidget extends StatelessWidget { + /// Creates a chat message widget. + /// + /// The [message] is required. All other parameters are optional and have + /// sensible defaults resolved from the ambient theme and message data. + StreamMessageWidget({ + super.key, + required Message message, + EdgeInsetsGeometry? padding, + double? spacing, + Color? backgroundColor, + double widthFactor = 0.8, + void Function(Message)? onMessageTap, + void Function(Message)? onMessageLongPress, + void Function(User)? onUserAvatarTap, + void Function(Message message, String url)? onMessageLinkTap, + void Function(User user)? onUserMentionTap, + void Function(Message)? onThreadTap, + void Function(Message)? onReplyTap, + void Function(Message)? onReactionsTap, + void Function(Message quotedMessage)? onQuotedMessageTap, + Comparator? reactionSorting, + MessageActionsBuilder? actionsBuilder, + void Function(BuildContext, Message)? onMessageActions, + void Function(BuildContext, Message)? onBouncedErrorMessageActions, + void Function(Message)? onEditMessageTap, + List? attachmentBuilders, + }) : props = .new( + message: message, + padding: padding, + spacing: spacing, + backgroundColor: backgroundColor, + widthFactor: widthFactor, + onMessageTap: onMessageTap, + onMessageLongPress: onMessageLongPress, + onUserAvatarTap: onUserAvatarTap, + onMessageLinkTap: onMessageLinkTap, + onUserMentionTap: onUserMentionTap, + onThreadTap: onThreadTap, + onReplyTap: onReplyTap, + onReactionsTap: onReactionsTap, + onQuotedMessageTap: onQuotedMessageTap, + reactionSorting: reactionSorting, + actionsBuilder: actionsBuilder, + onMessageActions: onMessageActions, + onBouncedErrorMessageActions: onBouncedErrorMessageActions, + onEditMessageTap: onEditMessageTap, + attachmentBuilders: attachmentBuilders, + ); + + /// Creates a chat message widget from pre-built [props]. + const StreamMessageWidget.fromProps({super.key, required this.props}); + + /// The properties that configure this message widget. + final StreamMessageWidgetProps props; - /// Shows the widget normally - show, + @override + Widget build(BuildContext context) { + final builder = context.chatComponentBuilder(); + if (builder != null) return builder(context, props); + return DefaultStreamMessage(props: props); + } } -/// {@template messageWidget} -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_widget.png) -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_widget_paint.png) +/// Properties for configuring a [StreamMessageWidget]. /// -/// Shows a message with reactions, replies and user avatar. +/// This class holds every configuration option for a chat message widget, +/// allowing them to be passed through the [StreamComponentFactory] when a +/// custom builder is registered. /// -/// Usually you don't use this widget as it's the default message widget used by -/// [MessageListView]. +/// Visual properties such as [padding], [spacing], and [backgroundColor] +/// override the corresponding values from [StreamMessageItemThemeData] when +/// non-null. When left null, the theme values are used instead. /// -/// The widget components render the ui based on the first ancestor of type -/// [StreamChatTheme]. -/// Modify it to change the widget appearance. -/// {@endtemplate} -class StreamMessageWidget extends StatefulWidget { - /// {@macro messageWidget} - const StreamMessageWidget({ - super.key, +/// See also: +/// +/// * [StreamMessageWidget], which uses these properties. +/// * [DefaultStreamMessage], the default implementation. +class StreamMessageWidgetProps { + /// Creates properties for a chat message widget. + const StreamMessageWidgetProps({ required this.message, - required this.messageTheme, - this.reverse = false, - this.translateUserAvatar = true, - this.shape, - this.borderSide, - this.borderRadiusGeometry, - this.attachmentShape, - this.onMentionTap, + this.padding, + this.spacing, + this.backgroundColor, + this.widthFactor = 0.8, this.onMessageTap, this.onMessageLongPress, - this.onReactionsTap, - this.onReactionsHover, - this.showReactionPicker = true, - this.showUserAvatar = DisplayWidget.show, - this.showSendingIndicator = true, - this.showThreadReplyIndicator = false, - this.showInChannelIndicator = false, - this.onReplyTap, - this.onThreadTap, - this.onEditMessageTap, - this.onConfirmDeleteTap, - this.showUsername = true, - this.showTimestamp = true, - this.showEditedLabel = true, - this.showReactions = true, - this.showDeleteMessage = true, - this.showEditMessage = true, - this.showReplyMessage = true, - this.showThreadReplyMessage = true, - this.showMarkUnreadMessage = true, - this.showResendMessage = true, - this.showCopyMessage = true, - this.showFlagButton = true, - this.showPinButton = true, - this.showPinHighlight = true, this.onUserAvatarTap, - this.onLinkTap, + this.onMessageLinkTap, + this.onUserMentionTap, + this.onThreadTap, + this.onReplyTap, + this.onReactionsTap, + this.onQuotedMessageTap, + this.reactionSorting, + this.actionsBuilder, this.onMessageActions, this.onBouncedErrorMessageActions, - this.onShowMessage, - this.userAvatarBuilder, - this.quotedMessageBuilder, - this.deletedMessageBuilder, - this.editMessageInputBuilder, - this.textBuilder, - this.bottomRowBuilderWithDefaultWidget, + this.onEditMessageTap, this.attachmentBuilders, - this.padding, - this.textPadding = const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - this.attachmentPadding = EdgeInsets.zero, - this.widthFactor = 0.78, - this.onQuotedMessageTap, - this.actionsBuilder, - this.onAttachmentTap, - this.imageAttachmentThumbnailSize = const Size(400, 400), - this.imageAttachmentThumbnailResizeType = 'clip', - this.imageAttachmentThumbnailCropType = 'center', - this.attachmentActionsModalBuilder, - this.reactionPickerBuilder = StreamReactionPicker.builder, - this.reactionIndicatorBuilder = StreamReactionIndicator.builder, }); - /// {@template onMentionTap} - /// Function called on mention tap - /// {@endtemplate} - final void Function(User)? onMentionTap; - - /// {@template onThreadTap} - /// The function called when tapping on threads - /// {@endtemplate} - final void Function(Message)? onThreadTap; - - /// {@template onReplyTap} - /// The function called when tapping on replies - /// {@endtemplate} - final void Function(Message)? onReplyTap; - - /// {@template onEditMessageTap} - /// The function called when tapping the edit action on a message. - /// If provided, the inline edit flow is used instead of the bottom sheet. - /// {@endtemplate} - final void Function(Message)? onEditMessageTap; - - /// {@template onDeleteTap} - /// The function called when delete confirmation button is tapped. - /// {@endtemplate} - final Future Function(Message)? onConfirmDeleteTap; - - /// {@template editMessageInputBuilder} - /// Widget builder for edit message layout - /// {@endtemplate} - final Widget Function(BuildContext, Message)? editMessageInputBuilder; - - /// {@template textBuilder} - /// Widget builder for building text - /// {@endtemplate} - final Widget Function(BuildContext, Message)? textBuilder; - - /// {@template onMessageActions} - /// Function called when a message is long-pressed to show actions. - /// If provided, this callback will be called instead of showing the default - /// message actions modal dialog. - /// {@endtemplate} - final void Function(BuildContext, Message)? onMessageActions; - - /// {@template onBouncedErrorMessageActions} - /// Function called when a message that has bounced with an error is long - /// pressed. If provided, this callback will be called instead of showing the - /// default bounced error message actions dialog. - /// {@endtemplate} - final void Function(BuildContext, Message)? onBouncedErrorMessageActions; - - /// {@template bottomRowBuilderWithDefaultWidget} - /// Widget builder for building a bottom row below the message. - /// Also contains the default bottom row widget. - /// {@endtemplate} - final BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget; - - /// {@template userAvatarBuilder} - /// Widget builder for building user avatar - /// {@endtemplate} - final Widget Function(BuildContext, User)? userAvatarBuilder; - - /// {@template quotedMessageBuilder} - /// Widget builder for building quoted message - /// {@endtemplate} - final Widget Function(BuildContext, Message)? quotedMessageBuilder; - - /// {@template deletedMessageBuilder} - /// Widget builder for building deleted message - /// {@endtemplate} - final Widget Function(BuildContext, Message)? deletedMessageBuilder; - - /// {@template message} /// The message to display. - /// {@endtemplate} final Message message; - /// {@template messageTheme} - /// The message theme - /// {@endtemplate} - final StreamMessageThemeData messageTheme; - - /// {@template reverse} - /// If true the widget will be mirrored - /// {@endtemplate} - final bool reverse; - - /// {@template shape} - /// The shape of the message text - /// {@endtemplate} - final ShapeBorder? shape; - - /// {@template attachmentShape} - /// The shape of an attachment - /// {@endtemplate} - final ShapeBorder? attachmentShape; - - /// {@template borderSide} - /// The borderSide of the message text - /// {@endtemplate} - final BorderSide? borderSide; - - /// {@template borderRadiusGeometry} - /// The border radius of the message text - /// {@endtemplate} - final BorderRadiusGeometry? borderRadiusGeometry; - - /// {@template padding} - /// The padding of the widget - /// {@endtemplate} + /// Outer padding around the entire message item. + /// + /// When non-null, takes precedence over the theme value from + /// [StreamMessageItemThemeData.padding]. + /// + /// When null (the default), the padding is determined by the theme. final EdgeInsetsGeometry? padding; - /// {@template textPadding} - /// The internal padding of the message text - /// {@endtemplate} - final EdgeInsets textPadding; + /// Horizontal spacing between the leading avatar and the content. + /// + /// When non-null, takes precedence over the theme value from + /// [StreamMessageItemThemeData.spacing]. + /// + /// When null (the default), the spacing is determined by the theme. + final double? spacing; - /// {@template attachmentPadding} - /// The internal padding of an attachment - /// {@endtemplate} - final EdgeInsetsGeometry attachmentPadding; + /// Background color for the entire message item row. + /// + /// When non-null, takes precedence over the theme value from + /// [StreamMessageItemThemeData.backgroundColor]. + /// + /// When null (the default), the background color is determined by the theme. + final Color? backgroundColor; - /// {@template widthFactor} - /// The percentage of the available width the message content should take - /// {@endtemplate} + /// Maximum width of the message content as a fraction of the parent width. + /// + /// Values should be between 0.0 and 1.0. Defaults to 0.8 when not specified. final double widthFactor; - /// {@template showUserAvatar} - /// It controls the display behaviour of the user avatar - /// {@endtemplate} - final DisplayWidget showUserAvatar; - - /// {@template showSendingIndicator} - /// It controls the display behaviour of the sending indicator - /// {@endtemplate} - final bool showSendingIndicator; - - /// {@template showReactions} - /// If `true` the message's reactions will be shown. - /// {@endtemplate} - final bool showReactions; - - /// {@template showThreadReplyIndicator} - /// If true the widget will show the thread reply indicator - /// {@endtemplate} - final bool showThreadReplyIndicator; - - /// {@template showInChannelIndicator} - /// If true the widget will show the show in channel indicator - /// {@endtemplate} - final bool showInChannelIndicator; - - /// {@template onUserAvatarTap} - /// The function called when tapping on UserAvatar - /// {@endtemplate} - final void Function(User)? onUserAvatarTap; - - /// {@template onLinkTap} - /// The function called when tapping on a link - /// {@endtemplate} - final void Function(String)? onLinkTap; - - /// {@template showReactionPicker} - /// Whether or not to show the reaction picker. - /// Used in [StreamMessageReactionsModal] and [StreamMessageActionsModal]. - /// {@endtemplate} - final bool showReactionPicker; - - /// {@template onShowMessage} - /// Callback when show message is tapped - /// {@endtemplate} - final ShowMessageCallback? onShowMessage; - - /// {@template showUsername} - /// If true show the users username next to the timestamp of the message - /// {@endtemplate} - final bool showUsername; - - /// {@template showTimestamp} - /// Show message timestamp - /// {@endtemplate} - final bool showTimestamp; - - /// {@template showTimestamp} - /// Show edited label if message is edited - /// {@endtemplate} - final bool showEditedLabel; - - /// {@template showReplyMessage} - /// Show reply action - /// {@endtemplate} - final bool showReplyMessage; - - /// {@template showThreadReplyMessage} - /// Show thread reply action - /// {@endtemplate} - final bool showThreadReplyMessage; - - /// {@template showMarkUnreadMessage} - /// Show mark unread action - /// {@endtemplate} - final bool showMarkUnreadMessage; - - /// {@template showEditMessage} - /// Show edit action - /// {@endtemplate} - final bool showEditMessage; - - /// {@template showCopyMessage} - /// Show copy action - /// {@endtemplate} - final bool showCopyMessage; - - /// {@template showDeleteMessage} - /// Show delete action - /// {@endtemplate} - final bool showDeleteMessage; - - /// {@template showResendMessage} - /// Show resend action - /// {@endtemplate} - final bool showResendMessage; - - /// {@template showFlagButton} - /// Show flag action - /// {@endtemplate} - final bool showFlagButton; - - /// {@template showPinButton} - /// Show pin action - /// {@endtemplate} - final bool showPinButton; - - /// {@template showPinHighlight} - /// Display Pin Highlight - /// {@endtemplate} - final bool showPinHighlight; - - /// {@template attachmentBuilders} - /// List of attachment builders for rendering attachment widgets pre-defined - /// and custom attachment types. + /// Called when the message is tapped. /// - /// If null, the widget will create a default list of attachment builders - /// based on the [Attachment.type] of the attachment. - /// {@endtemplate} - final List? attachmentBuilders; + /// If null, no tap gesture is registered on mobile. On desktop and web, + /// tap behaviour is unaffected because interactions are driven by the + /// context menu instead. + final void Function(Message message)? onMessageTap; - /// {@template translateUserAvatar} - /// Center user avatar with bottom of the message - /// {@endtemplate} - final bool translateUserAvatar; + /// Called when the message is long-pressed. + /// + /// If null, the default long-press behaviour is used, which opens the + /// [StreamMessageActionsModal] on mobile. Provide this callback to + /// override that behaviour entirely. + final void Function(Message message)? onMessageLongPress; - /// {@macro onQuotedMessageTap} - final OnQuotedMessageTap? onQuotedMessageTap; + /// Called when the author's avatar is tapped. + /// + /// If null, tapping the avatar has no effect. A common use is to navigate + /// to the user's profile screen. + final void Function(User user)? onUserAvatarTap; - /// {@macro onMessageTap} - final OnMessageTap? onMessageTap; + /// Called when a link is tapped in the message text. + /// + /// Receives the [Message] containing the link and the tapped URL string. + /// If null, the default link handling behaviour is used. + final void Function(Message message, String url)? onMessageLinkTap; - /// {@macro onMessageLongPress} - final OnMessageLongPress? onMessageLongPress; + /// Called when a `@mention` is tapped in the message text. + /// + /// Receives the mentioned [User] resolved from the message's + /// [Message.mentionedUsers] list. If null, tapping a mention has no effect. + final void Function(User user)? onUserMentionTap; - /// {@macro onReactionsTap} + /// Called when the thread reply indicator is tapped. /// - /// Note: Only used in mobile devices (iOS and Android). Do not confuse this - /// with the tap action on the reactions picker. - final OnReactionsTap? onReactionsTap; + /// Receives the parent [Message] of the thread. If the message was shown + /// in-channel via [Message.showInChannel], the original parent message is + /// fetched before invoking the callback. + /// + /// If null, tapping the thread indicator has no effect. + final void Function(Message message)? onThreadTap; - /// {@macro onReactionsHover} + /// Called when the quoted-reply action is selected from the actions list. + /// + /// Receives the [Message] that should be quoted. Typically used to set the + /// quoted message on the message input. /// - /// Note: Only used in desktop devices (web and desktop). - final OnReactionsHover? onReactionsHover; + /// If null, the quoted-reply action is still shown but has no effect. + final void Function(Message message)? onReplyTap; - /// {@macro messageActionsBuilder} - final MessageActionsBuilder? actionsBuilder; + /// Called when the reactions row beneath the message bubble is tapped. + /// + /// If null, the default behaviour opens a [ReactionDetailSheet] showing + /// the full list of reactions. Provide this callback to replace that + /// default with custom handling. + final void Function(Message message)? onReactionsTap; - /// {@macro onAttachmentWidgetTap} - final OnAttachmentWidgetTap? onAttachmentTap; + /// Called when an inline quoted message is tapped. + /// + /// Receives the [Message] that was quoted. Typically used to scroll to + /// the original message in the list. + /// + /// If null, tapping the quoted message has no effect. + final void Function(Message quotedMessage)? onQuotedMessageTap; - /// {@macro attachmentActionsBuilder} - final AttachmentActionsBuilder? attachmentActionsModalBuilder; + /// Controls how reaction groups are sorted when displayed. + /// + /// Defaults to [ReactionSorting.byFirstReactionAt]. + final Comparator? reactionSorting; - /// {@macro reactionPickerBuilder} - final ReactionPickerBuilder reactionPickerBuilder; + /// Allows customizing the default message actions list. + /// + /// Receives the [BuildContext] and the default list of + /// [StreamContextMenuAction] items built by the widget. Return a modified + /// list to add, remove, or reorder actions. + final MessageActionsBuilder? actionsBuilder; - /// {@macro reactionIndicatorBuilder} - final ReactionIndicatorBuilder reactionIndicatorBuilder; + /// Called when a normal message is long-pressed to show actions. + /// + /// When provided, this callback replaces the default behaviour of showing + /// the [StreamMessageActionsModal]. + final void Function(BuildContext context, Message message)? onMessageActions; - /// Size of the image attachment thumbnail. - final Size imageAttachmentThumbnailSize; + /// Called when a bounced-error message is long-pressed. + /// + /// When provided, this callback replaces the default behaviour of showing + /// the [ModeratedMessageActionsModal]. + final void Function(BuildContext context, Message message)? onBouncedErrorMessageActions; - /// Resize type of the image attachment thumbnail. + /// Called when the edit-message action is selected. /// - /// Defaults to [crop] - final String /*clip|crop|scale|fill*/ imageAttachmentThumbnailResizeType; + /// When provided, this callback replaces the default behaviour of showing + /// the edit-message bottom sheet via [showEditMessageSheet]. + final void Function(Message message)? onEditMessageTap; - /// Crop type of the image attachment thumbnail. + /// Custom attachment builders for rendering message attachments. /// - /// Defaults to [center] - final String /*center|top|bottom|left|right*/ imageAttachmentThumbnailCropType; - - /// {@template copyWith} - /// Creates a copy of [StreamMessageWidget] with specified attributes - /// overridden. - /// {@endtemplate} - StreamMessageWidget copyWith({ - Key? key, - void Function(User)? onMentionTap, - void Function(Message)? onThreadTap, - void Function(Message)? onReplyTap, - void Function(Message)? onEditMessageTap, - Future Function(Message)? onConfirmDeleteTap, - Widget Function(BuildContext, Message)? editMessageInputBuilder, - Widget Function(BuildContext, Message)? textBuilder, - Widget Function(BuildContext, Message)? quotedMessageBuilder, - Widget Function(BuildContext, Message)? deletedMessageBuilder, - BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget, - void Function(BuildContext, Message)? onMessageActions, - void Function(BuildContext, Message)? onBouncedErrorMessageActions, + /// When non-null, these builders are used instead of the default ones + /// provided by [StreamChatConfigurationData.attachmentBuilders]. + /// + /// Custom builders are prepended to the default builder list, so they take + /// priority for attachment types they can handle. + final List? attachmentBuilders; + + /// Returns a copy of this [StreamMessageWidgetProps] with the given fields + /// replaced with new values. + StreamMessageWidgetProps copyWith({ Message? message, - StreamMessageThemeData? messageTheme, - bool? reverse, - ShapeBorder? shape, - ShapeBorder? attachmentShape, - BorderSide? borderSide, - BorderRadiusGeometry? borderRadiusGeometry, EdgeInsetsGeometry? padding, - EdgeInsets? textPadding, - EdgeInsetsGeometry? attachmentPadding, + double? spacing, + Color? backgroundColor, double? widthFactor, - DisplayWidget? showUserAvatar, - bool? showSendingIndicator, - bool? showReactions, - bool? allRead, - bool? showThreadReplyIndicator, - bool? showInChannelIndicator, + void Function(Message)? onMessageTap, + void Function(Message)? onMessageLongPress, void Function(User)? onUserAvatarTap, - void Function(String)? onLinkTap, - bool? showReactionBrowser, - bool? showReactionPicker, - List? readList, - ShowMessageCallback? onShowMessage, - bool? showUsername, - bool? showTimestamp, - bool? showEditedLabel, - bool? showReplyMessage, - bool? showThreadReplyMessage, - bool? showEditMessage, - bool? showCopyMessage, - bool? showDeleteMessage, - bool? showResendMessage, - bool? showFlagButton, - bool? showPinButton, - bool? showPinHighlight, - bool? showMarkUnreadMessage, - List? attachmentBuilders, - bool? translateUserAvatar, - OnQuotedMessageTap? onQuotedMessageTap, - OnMessageTap? onMessageTap, - OnMessageLongPress? onMessageLongPress, - OnReactionsTap? onReactionsTap, - OnReactionsHover? onReactionsHover, + void Function(Message, String)? onMessageLinkTap, + void Function(User)? onUserMentionTap, + void Function(Message)? onThreadTap, + void Function(Message)? onReplyTap, + void Function(Message)? onReactionsTap, + void Function(Message)? onQuotedMessageTap, + Comparator? reactionSorting, MessageActionsBuilder? actionsBuilder, - OnAttachmentWidgetTap? onAttachmentTap, - Widget Function(BuildContext, User)? userAvatarBuilder, - Size? imageAttachmentThumbnailSize, - String? imageAttachmentThumbnailResizeType, - String? imageAttachmentThumbnailCropType, - AttachmentActionsBuilder? attachmentActionsModalBuilder, - ReactionPickerBuilder? reactionPickerBuilder, - ReactionIndicatorBuilder? reactionIndicatorBuilder, + void Function(BuildContext, Message)? onMessageActions, + void Function(BuildContext, Message)? onBouncedErrorMessageActions, + void Function(Message)? onEditMessageTap, + List? attachmentBuilders, }) { - return StreamMessageWidget( - key: key ?? this.key, - onMentionTap: onMentionTap ?? this.onMentionTap, - onThreadTap: onThreadTap ?? this.onThreadTap, - onReplyTap: onReplyTap ?? this.onReplyTap, - onEditMessageTap: onEditMessageTap ?? this.onEditMessageTap, - onConfirmDeleteTap: onConfirmDeleteTap ?? this.onConfirmDeleteTap, - editMessageInputBuilder: editMessageInputBuilder ?? this.editMessageInputBuilder, - textBuilder: textBuilder ?? this.textBuilder, - quotedMessageBuilder: quotedMessageBuilder ?? this.quotedMessageBuilder, - deletedMessageBuilder: deletedMessageBuilder ?? this.deletedMessageBuilder, - bottomRowBuilderWithDefaultWidget: bottomRowBuilderWithDefaultWidget ?? this.bottomRowBuilderWithDefaultWidget, - onMessageActions: onMessageActions ?? this.onMessageActions, - onBouncedErrorMessageActions: onBouncedErrorMessageActions ?? this.onBouncedErrorMessageActions, + return StreamMessageWidgetProps( message: message ?? this.message, - messageTheme: messageTheme ?? this.messageTheme, - reverse: reverse ?? this.reverse, - shape: shape ?? this.shape, - attachmentShape: attachmentShape ?? this.attachmentShape, - borderSide: borderSide ?? this.borderSide, - borderRadiusGeometry: borderRadiusGeometry ?? this.borderRadiusGeometry, padding: padding ?? this.padding, - textPadding: textPadding ?? this.textPadding, - attachmentPadding: attachmentPadding ?? this.attachmentPadding, + spacing: spacing ?? this.spacing, + backgroundColor: backgroundColor ?? this.backgroundColor, widthFactor: widthFactor ?? this.widthFactor, - showUserAvatar: showUserAvatar ?? this.showUserAvatar, - showSendingIndicator: showSendingIndicator ?? this.showSendingIndicator, - showEditedLabel: showEditedLabel ?? this.showEditedLabel, - showReactions: showReactions ?? this.showReactions, - showThreadReplyIndicator: showThreadReplyIndicator ?? this.showThreadReplyIndicator, - showInChannelIndicator: showInChannelIndicator ?? this.showInChannelIndicator, - onUserAvatarTap: onUserAvatarTap ?? this.onUserAvatarTap, - onLinkTap: onLinkTap ?? this.onLinkTap, - showReactionPicker: showReactionPicker ?? this.showReactionPicker, - onShowMessage: onShowMessage ?? this.onShowMessage, - showUsername: showUsername ?? this.showUsername, - showTimestamp: showTimestamp ?? this.showTimestamp, - showReplyMessage: showReplyMessage ?? this.showReplyMessage, - showThreadReplyMessage: showThreadReplyMessage ?? this.showThreadReplyMessage, - showEditMessage: showEditMessage ?? this.showEditMessage, - showCopyMessage: showCopyMessage ?? this.showCopyMessage, - showDeleteMessage: showDeleteMessage ?? this.showDeleteMessage, - showResendMessage: showResendMessage ?? this.showResendMessage, - showFlagButton: showFlagButton ?? this.showFlagButton, - showPinButton: showPinButton ?? this.showPinButton, - showPinHighlight: showPinHighlight ?? this.showPinHighlight, - showMarkUnreadMessage: showMarkUnreadMessage ?? this.showMarkUnreadMessage, - attachmentBuilders: attachmentBuilders ?? this.attachmentBuilders, - translateUserAvatar: translateUserAvatar ?? this.translateUserAvatar, - onQuotedMessageTap: onQuotedMessageTap ?? this.onQuotedMessageTap, onMessageTap: onMessageTap ?? this.onMessageTap, onMessageLongPress: onMessageLongPress ?? this.onMessageLongPress, + onUserAvatarTap: onUserAvatarTap ?? this.onUserAvatarTap, + onMessageLinkTap: onMessageLinkTap ?? this.onMessageLinkTap, + onUserMentionTap: onUserMentionTap ?? this.onUserMentionTap, + onThreadTap: onThreadTap ?? this.onThreadTap, + onReplyTap: onReplyTap ?? this.onReplyTap, onReactionsTap: onReactionsTap ?? this.onReactionsTap, - onReactionsHover: onReactionsHover ?? this.onReactionsHover, + onQuotedMessageTap: onQuotedMessageTap ?? this.onQuotedMessageTap, + reactionSorting: reactionSorting ?? this.reactionSorting, actionsBuilder: actionsBuilder ?? this.actionsBuilder, - onAttachmentTap: onAttachmentTap ?? this.onAttachmentTap, - userAvatarBuilder: userAvatarBuilder ?? this.userAvatarBuilder, - imageAttachmentThumbnailSize: imageAttachmentThumbnailSize ?? this.imageAttachmentThumbnailSize, - imageAttachmentThumbnailResizeType: imageAttachmentThumbnailResizeType ?? this.imageAttachmentThumbnailResizeType, - imageAttachmentThumbnailCropType: imageAttachmentThumbnailCropType ?? this.imageAttachmentThumbnailCropType, - attachmentActionsModalBuilder: attachmentActionsModalBuilder ?? this.attachmentActionsModalBuilder, - reactionPickerBuilder: reactionPickerBuilder ?? this.reactionPickerBuilder, - reactionIndicatorBuilder: reactionIndicatorBuilder ?? this.reactionIndicatorBuilder, + onMessageActions: onMessageActions ?? this.onMessageActions, + onBouncedErrorMessageActions: onBouncedErrorMessageActions ?? this.onBouncedErrorMessageActions, + onEditMessageTap: onEditMessageTap ?? this.onEditMessageTap, + attachmentBuilders: attachmentBuilders ?? this.attachmentBuilders, ); } - - @override - _StreamMessageWidgetState createState() => _StreamMessageWidgetState(); } -class _StreamMessageWidgetState extends State - with AutomaticKeepAliveClientMixin { - bool get showThreadReplyIndicator => widget.showThreadReplyIndicator; - - bool get showSendingIndicator => widget.showSendingIndicator; - - bool get isDeleted => widget.message.isDeleted; - - bool get showUsername => widget.showUsername; - - bool get showTimeStamp => widget.showTimestamp; - - bool get showEditedLabel => widget.showEditedLabel; - - bool get isTextEdited => widget.message.messageTextUpdatedAt != null; - - bool get showInChannel => widget.showInChannelIndicator; - - /// {@template hasQuotedMessage} - /// `true` if [StreamMessageWidget.quotedMessage] is not null. - /// {@endtemplate} - bool get hasQuotedMessage => widget.message.quotedMessage != null; - - bool get isSendFailed => widget.message.state.isSendingFailed; +/// The default implementation of [StreamMessageWidget]. +/// +/// Composes a full message row with an author avatar, content bubble, +/// header annotations, footer metadata, and platform-adaptive interaction +/// handling (tap and long-press on mobile, right-click context menu on +/// desktop and web). +/// +/// Message actions can be customised through +/// [StreamMessageWidgetProps.actionsBuilder]. +/// +/// See also: +/// +/// * [StreamMessageWidget], the public API widget. +/// * [StreamMessageWidgetProps], which configures this widget. +/// * [StreamMessageItemTheme], provides theme data to this widget. +class DefaultStreamMessage extends StatelessWidget { + /// Creates a default chat message widget with the given [props]. + const DefaultStreamMessage({super.key, required this.props}); - bool get isUpdateFailed => widget.message.state.isUpdatingFailed; + /// The properties that configure this widget. + final StreamMessageWidgetProps props; - bool get isDeleteFailed => widget.message.state.isDeletingFailed; + @override + Widget build(BuildContext context) { + final message = props.message; - bool get isBouncedWithError => widget.message.isBouncedWithError; + final placement = StreamMessagePlacement.of(context); + final theme = StreamMessageItemTheme.of(context); + final defaults = _StreamMessageWidgetDefaults(context, isPinned: message.pinned, state: message.state); - /// {@template isFailedState} - /// Whether the message has failed to be sent, updated, deleted or is bounced - /// back with the message type as error. - /// {@endtemplate} - bool get isFailedState => isSendFailed || isUpdateFailed || isDeleteFailed || isBouncedWithError; + final resolve = StreamMessageStyleResolver(placement, [theme, defaults]); - /// {@template isGiphy} - /// `true` if any of the [message]'s attachments are a giphy. - /// {@endtemplate} - bool get isGiphy => widget.message.attachments.any((element) => element.type == AttachmentType.giphy); + final effectivePadding = props.padding ?? theme.padding ?? defaults.padding; + final effectiveSpacing = props.spacing ?? theme.spacing ?? defaults.spacing; + final effectiveBackgroundColor = props.backgroundColor ?? theme.backgroundColor ?? defaults.backgroundColor; + final effectiveLeadingVisibility = resolve((theme) => theme?.leadingVisibility); + final effectiveHeaderVisibility = resolve((theme) => theme?.headerVisibility); + final effectiveFooterVisibility = resolve((theme) => theme?.footerVisibility); - /// {@template isOnlyEmoji} - /// `true` if [message.text] contains only emoji. - /// {@endtemplate} - bool get isOnlyEmoji => widget.message.text?.isOnlyEmoji == true; + Widget? leadingWidget; + if (props.message.user case final user?) { + final effectiveAvatarSize = theme.avatarSize ?? defaults.avatarSize; - /// {@template hasNonUrlAttachments} - /// `true` if any of the [message]'s attachments are a giphy and do not - /// have a [Attachment.titleLink]. - /// {@endtemplate} - bool get hasNonUrlAttachments => widget.message.attachments.any((it) => it.type != AttachmentType.urlPreview); + leadingWidget = effectiveLeadingVisibility.apply( + StreamAvatarTheme( + data: .new(size: effectiveAvatarSize), + child: StreamMessageLeading(author: user), + ), + ); + } - /// {@template hasUrlAttachments} - /// `true` if any of the [message]'s attachments are a giphy with a - /// [Attachment.titleLink]. - /// {@endtemplate} - bool get hasUrlAttachments => widget.message.attachments.any((it) => it.type == AttachmentType.urlPreview); + final headerWidget = effectiveHeaderVisibility.apply( + streamMessageHeader( + context: context, + message: message, + onViewChannelTap: () => _onViewThread(context, message), + ), + ); + final footerWidget = effectiveFooterVisibility.apply(StreamMessageFooter(message: message)); - /// {@template showBottomRow} - /// Show the [BottomRow] widget if any of the following are `true`: - /// * [StreamMessageWidget.showThreadReplyIndicator] - /// * [StreamMessageWidget.showUsername] - /// * [StreamMessageWidget.showTimestamp] - /// * [StreamMessageWidget.showInChannelIndicator] - /// * [StreamMessageWidget.showSendingIndicator] - /// * [StreamMessageWidget.message.isDeleted] - /// {@endtemplate} - bool get showBottomRow => - showThreadReplyIndicator || - showUsername || - showTimeStamp || - showInChannel || - showSendingIndicator || - isTextEdited; + final contentWidget = StreamMessageContent( + message: message, + header: headerWidget, + footer: footerWidget, + attachmentBuilders: props.attachmentBuilders, + reactionSorting: props.reactionSorting, + onQuotedMessageTap: props.onQuotedMessageTap, + onRepliesTap: () => _onViewThread(context, message), + onLinkTap: (_, href, __) { + if (href == null) return; + if (props.onMessageLinkTap case final onTap?) return onTap(message, href); + return launchURL(context, href).ignore(); + }, + onMentionTap: switch (props.onUserMentionTap) { + final onTap? => (_, id) { + final user = message.mentionedUsers.firstWhereOrNull((u) => u.id == id); + if (user != null) onTap(user); + }, + _ => null, + }, + onReactionsTap: switch (props.onReactionsTap) { + final onReactionsTap? => () => onReactionsTap(message), + _ => () => _showMessageReactionsModal(context, message), + }, + ); - /// {@template isPinned} - /// Whether [StreamMessageWidget.message] is pinned or not. - /// {@endtemplate} - bool get isPinned => widget.message.pinned && !widget.message.isDeleted; + return Material( + animateColor: true, + color: effectiveBackgroundColor, + child: PlatformWidgetBuilder( + mobile: (context, child) => InkWell( + onTap: switch (props.onMessageTap) { + final onMessageTap? => () => onMessageTap(message), + _ => null, + }, + onLongPress: switch (props.onMessageLongPress) { + final onMessageLongPress? => () => onMessageLongPress(message), + _ when message.state.isDeleted => null, + _ when message.state.isOutgoing => null, + _ => () => _onMessageLongPressed(context, message), + }, + child: child, + ), + desktopOrWeb: (context, child) { + final messageState = message.state; - /// {@template shouldShowReactions} - /// Should show message reactions if [StreamMessageWidget.showReactions] is - /// `true`, if there are reactions to show, and if the message is not deleted. - /// {@endtemplate} - bool get shouldShowReactions => - widget.showReactions && (widget.message.reactionGroups?.isNotEmpty == true) && !widget.message.isDeleted; + // If the message is deleted or not yet sent, we don't want to + // show any context menu actions. + if (messageState.isDeleted || messageState.isOutgoing) return child; - @override - bool get wantKeepAlive => widget.message.attachments.isNotEmpty; + final channel = StreamChannel.of(context).channel; + final menuItems = _buildDesktopOrWebActions(context, message); + if (menuItems.isEmpty) return MouseRegion(child: child); - @override - Widget build(BuildContext context) { - super.build(context); - final theme = StreamChatTheme.of(context); - final streamChat = StreamChat.of(context); - - final avatarWidth = widget.messageTheme.avatarTheme?.constraints.maxWidth ?? 40; - final bottomRowPadding = widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 0.5; - - return Portal( - child: Material( - color: switch (isPinned && widget.showPinHighlight) { - true => theme.colorTheme.highlight, - false => Colors.transparent, - }, - child: PlatformWidgetBuilder( - mobile: (context, child) { - final message = widget.message; - return InkWell( - onTap: switch (widget.onMessageTap) { - final onTap? => () => onTap(message), - _ => null, - }, - onLongPress: switch (widget.onMessageLongPress) { - final onLongPress? => () => onLongPress(message), - // If the message is not yet sent or deleted, we don't want - // to handle long press events by default. - _ when message.state.isDeleted => null, - _ when message.state.isOutgoing => null, - _ => () => _onMessageLongPressed(context, message), - }, - child: child, - ); - }, - desktopOrWeb: (context, child) { - final message = widget.message; - final messageState = message.state; - - // If the message is deleted or not yet sent, we don't want to - // show any context menu actions. - if (messageState.isDeleted || messageState.isOutgoing) return child; - - final channel = StreamChannel.of(context).channel; - final menuItems = _buildDesktopOrWebActions(context, message); - if (menuItems.isEmpty) return MouseRegion(child: child); - - return ContextMenuRegion( - onSelected: (result) { - if (result is! MessageAction) return; - return _onActionTap(context, channel, result).ignore(); - }, - menuBuilder: (_, anchor) => ContextMenu( - anchor: anchor, - menuItems: menuItems, - ), - child: MouseRegion(child: child), - ); - }, - child: FlexibleFractionallySizedBox( - widthFactor: widget.widthFactor, - alignment: switch (widget.reverse) { - true => AlignmentDirectional.centerEnd, - false => AlignmentDirectional.centerStart, + return ContextMenuRegion( + onSelected: (result) { + if (result is! MessageAction) return; + return _onActionTap(context, channel, result).ignore(); }, - child: Padding( - padding: widget.padding ?? const EdgeInsets.symmetric(horizontal: 16), - child: MessageWidgetContent( - streamChatTheme: theme, - showUsername: showUsername, - showTimeStamp: showTimeStamp, - showEditedLabel: showEditedLabel, - showThreadReplyIndicator: showThreadReplyIndicator, - showSendingIndicator: showSendingIndicator, - showInChannel: showInChannel, - isGiphy: isGiphy, - isOnlyEmoji: isOnlyEmoji, - hasUrlAttachments: hasUrlAttachments, - messageTheme: widget.messageTheme, - reverse: widget.reverse, - message: widget.message, - hasNonUrlAttachments: hasNonUrlAttachments, - hasQuotedMessage: hasQuotedMessage, - textPadding: widget.textPadding, - attachmentBuilders: widget.attachmentBuilders, - attachmentPadding: widget.attachmentPadding, - attachmentShape: widget.attachmentShape, - onAttachmentTap: widget.onAttachmentTap, - onReplyTap: widget.onReplyTap, - onThreadTap: widget.onThreadTap, - onShowMessage: widget.onShowMessage, - attachmentActionsModalBuilder: widget.attachmentActionsModalBuilder, - avatarWidth: avatarWidth, - bottomRowPadding: bottomRowPadding, - isFailedState: isFailedState, - isPinned: isPinned, - messageWidget: widget, - showBottomRow: showBottomRow, - showPinHighlight: widget.showPinHighlight, - showReactions: shouldShowReactions, - onReactionsTap: () { - final message = widget.message; - return switch (widget.onReactionsTap) { - final onReactionsTap? => onReactionsTap(message), - _ => _showMessageReactionsModal(context, message), - }; - }, - onReactionsHover: widget.onReactionsHover, - showUserAvatar: widget.showUserAvatar, - streamChat: streamChat, - translateUserAvatar: widget.translateUserAvatar, - shape: widget.shape, - borderSide: widget.borderSide, - borderRadiusGeometry: widget.borderRadiusGeometry, - textBuilder: widget.textBuilder, - quotedMessageBuilder: widget.quotedMessageBuilder, - deletedMessageBuilder: widget.deletedMessageBuilder, - onLinkTap: widget.onLinkTap, - onMentionTap: widget.onMentionTap, - onQuotedMessageTap: widget.onQuotedMessageTap, - bottomRowBuilderWithDefaultWidget: widget.bottomRowBuilderWithDefaultWidget, - onUserAvatarTap: widget.onUserAvatarTap, - userAvatarBuilder: widget.userAvatarBuilder, - reactionIndicatorBuilder: widget.reactionIndicatorBuilder, - ), + menuBuilder: (_, anchor) => ContextMenu( + anchor: anchor, + menuItems: menuItems, + ), + child: MouseRegion(child: child), + ); + }, + child: FlexibleFractionallySizedBox( + widthFactor: props.widthFactor, + alignment: StreamMessagePlacement.alignmentDirectionalOf(context), + child: Padding( + padding: effectivePadding, + child: Row( + mainAxisSize: .min, + spacing: effectiveSpacing, + crossAxisAlignment: .end, + children: [ + ?leadingWidget, + Flexible(child: contentWidget), + ], ), ), ), @@ -765,6 +496,7 @@ class _StreamMessageWidgetState extends State ); } + // Builds the action list for a bounced (moderation-error) message. List _buildBouncedErrorMessageActions({ required BuildContext context, required Message message, @@ -775,51 +507,40 @@ class _StreamMessageWidgetState extends State ); } + // Builds the standard action list, applying the custom actionsBuilder if set. List _buildMessageActions({ required BuildContext context, required Message message, required Channel channel, OwnUser? currentUser, }) { - final actions = - StreamMessageActionsBuilder.buildActions( - context: context, - message: message, - channel: channel, - currentUser: currentUser, - )..retainWhere( - (it) => switch (it.props.value) { - QuotedReply() => widget.showReplyMessage, - ThreadReply() => widget.showThreadReplyMessage, - MarkUnread() => widget.showMarkUnreadMessage, - ResendMessage() => widget.showResendMessage, - EditMessage() => widget.showEditMessage, - CopyMessage() => widget.showCopyMessage, - FlagMessage() => widget.showFlagButton, - PinMessage() => widget.showPinButton, - DeleteMessage() => widget.showDeleteMessage, - _ => true, // Retain all the remaining actions. - }, - ); + final actions = StreamMessageActionsBuilder.buildActions( + context: context, + message: message, + channel: channel, + currentUser: currentUser, + ); - if (widget.actionsBuilder case final builder?) { + if (props.actionsBuilder case final builder?) { return builder(context, actions); } return StreamContextMenuAction.partitioned(items: actions); } + // Dispatches to bounced-error or normal actions for desktop/web. List _buildDesktopOrWebActions( BuildContext context, Message message, ) { - if (isBouncedWithError) { + if (message.isBouncedWithError) { return _buildBouncedErrorMessageDesktopOrWebActions(context, message); } return _buildMessageDesktopOrWebActions(context, message); } + // Builds partitioned bounced-error actions for the desktop/web context menu. List _buildBouncedErrorMessageDesktopOrWebActions( BuildContext context, Message message, @@ -832,13 +553,14 @@ class _StreamMessageWidgetState extends State return StreamContextMenuAction.partitioned(items: actions); } + // Builds normal actions + reaction picker for the desktop/web context menu. List _buildMessageDesktopOrWebActions( BuildContext context, Message message, ) { final channel = StreamChannel.of(context).channel; final currentUser = channel.client.state.currentUser; - final showPicker = widget.showReactionPicker && channel.canSendReaction; + final showPicker = channel.canSendReaction; final actions = _buildMessageActions( context: context, @@ -853,11 +575,12 @@ class _StreamMessageWidgetState extends State } return [ - if (showPicker) widget.reactionPickerBuilder(context, message, onReactionPicked), + if (showPicker) StreamMessageReactionPicker(message: message, onReactionPicked: onReactionPicked), ...actions, ]; } + // Opens the reaction detail sheet and handles the returned action. Future _showMessageReactionsModal( BuildContext context, Message message, @@ -873,28 +596,49 @@ class _StreamMessageWidgetState extends State return _onActionTap(context, channel, action).ignore(); } + // Resolves the thread parent (fetching if shown in-channel) and invokes + // the onThreadTap callback. + Future _onViewThread( + BuildContext context, + Message message, + ) async { + try { + var threadMessage = message; + if (message.showInChannel case true) { + final streamChannel = StreamChannel.of(context); + threadMessage = await streamChannel.getMessage(message.parentId!); + } + return props.onThreadTap?.call(threadMessage); + } catch (e, stk) { + debugPrint('Error while fetching message: $e, $stk'); + } + } + + // Routes a long-press to bounced-error or normal actions handler. Future _onMessageLongPressed( BuildContext context, Message message, ) { - if (isBouncedWithError) { + if (message.isBouncedWithError) { return _onBouncedErrorMessageActions(context, message); } return _onMessageActions(context, message); } + // Delegates to the custom callback or falls back to the default dialog. Future _onBouncedErrorMessageActions( BuildContext context, Message message, ) async { - if (widget.onBouncedErrorMessageActions case final onActions?) { + if (props.onBouncedErrorMessageActions case final onActions?) { return onActions(context, message); } return _showBouncedErrorMessageActionsDialog(context, message); } + // Shows the ModeratedMessageActionsModal for a bounced-error message. Future _showBouncedErrorMessageActionsDialog( BuildContext context, Message message, @@ -919,24 +663,26 @@ class _StreamMessageWidgetState extends State return _onActionTap(context, channel, action).ignore(); } + // Delegates to the custom callback or falls back to the default modal. Future _onMessageActions( BuildContext context, Message message, ) async { - if (widget.onMessageActions case final onActions?) { + if (props.onMessageActions case final onActions?) { return onActions(context, message); } return _showMessageActionModalDialog(context, message); } + // Shows the StreamMessageActionsModal with a reaction picker and actions. Future _showMessageActionModalDialog( BuildContext context, Message message, ) async { final channel = StreamChannel.of(context).channel; final currentUser = channel.client.state.currentUser; - final showPicker = widget.showReactionPicker && channel.canSendReaction; + final showPicker = channel.canSendReaction; final actions = _buildMessageActions( context: context, @@ -949,31 +695,21 @@ class _StreamMessageWidgetState extends State context: context, useRootNavigator: false, builder: (_) => StreamChatConfiguration( - // This is needed to provide the nearest reaction icons to the - // StreamMessageActionsModal. data: StreamChatConfiguration.of(context), - child: StreamMessageActionsModal( - message: message, - reverse: widget.reverse, - messageActions: actions, - showReactionPicker: showPicker, - reactionPickerBuilder: widget.reactionPickerBuilder, - messageWidget: StreamChannel( - channel: channel, - child: widget.copyWith( - key: const Key('MessageWidget'), - message: message.trimmed, - showReactions: false, - showUsername: false, - showTimestamp: false, - translateUserAvatar: false, - showSendingIndicator: false, - padding: EdgeInsets.zero, - showPinHighlight: false, - showUserAvatar: switch (widget.reverse) { - true => DisplayWidget.gone, - false => DisplayWidget.show, - }, + child: StreamMessagePlacement( + data: StreamMessagePlacement.of(context), + child: StreamMessageActionsModal( + message: message, + messageActions: actions, + showReactionPicker: showPicker, + messageWidget: StreamChannel( + channel: channel, + child: StreamMessageWidget( + key: const Key('MessageWidget'), + message: message.trimmed, + padding: EdgeInsets.zero, + backgroundColor: StreamColors.transparent, + ), ), ), ), @@ -984,29 +720,29 @@ class _StreamMessageWidgetState extends State return _onActionTap(context, channel, action).ignore(); } + // Dispatches a MessageAction to the appropriate channel or callback handler. Future _onActionTap( BuildContext context, Channel channel, MessageAction action, - ) async { - return switch (action) { - SelectReaction() => _selectReaction(context, action.message, channel, action.reaction), - CopyMessage() => _copyMessage(action.message, channel), - DeleteMessage() => _maybeDeleteMessage(context, action.message, channel), - HardDeleteMessage() => channel.deleteMessage(action.message, hard: true), - EditMessage() => _editMessage(context, action.message, channel), - FlagMessage() => _maybeFlagMessage(context, action.message, channel), - MarkUnread() => channel.markUnread(action.message.id), - MuteUser() => channel.client.muteUser(action.user.id), - UnmuteUser() => channel.client.unmuteUser(action.user.id), - PinMessage() => channel.pinMessage(action.message), - UnpinMessage() => channel.unpinMessage(action.message), - ResendMessage() => channel.retryMessage(action.message), - QuotedReply() => widget.onReplyTap?.call(action.message), - ThreadReply() => widget.onThreadTap?.call(action.message), - }; - } - + ) async => switch (action) { + SelectReaction() => _selectReaction(context, action.message, channel, action.reaction), + CopyMessage() => _copyMessage(action.message, channel), + DeleteMessage() => _maybeDeleteMessage(context, action.message, channel), + HardDeleteMessage() => channel.deleteMessage(action.message, hard: true), + EditMessage() => props.onEditMessageTap?.call(action.message), + FlagMessage() => _maybeFlagMessage(context, action.message, channel), + MarkUnread() => channel.markUnread(action.message.id), + MuteUser() => channel.client.muteUser(action.user.id), + UnmuteUser() => channel.client.unmuteUser(action.user.id), + PinMessage() => channel.pinMessage(action.message), + UnpinMessage() => channel.unpinMessage(action.message), + ResendMessage() => channel.retryMessage(action.message), + QuotedReply() => props.onReplyTap?.call(action.message), + ThreadReply() => props.onThreadTap?.call(action.message), + }; + + // Copies the message text (with mentions replaced) to the clipboard. Future _copyMessage( Message message, Channel channel, @@ -1019,6 +755,7 @@ class _StreamMessageWidgetState extends State return Clipboard.setData(ClipboardData(text: messageText)); } + // Shows a confirmation dialog before deleting the message. Future _maybeDeleteMessage( BuildContext context, Message message, @@ -1040,24 +777,7 @@ class _StreamMessageWidgetState extends State return channel.deleteMessage(message); } - Future _editMessage( - BuildContext context, - Message message, - Channel channel, - ) { - final onEditMessageTap = widget.onEditMessageTap; - if (onEditMessageTap != null) { - onEditMessageTap(message); - return Future.value(); - } - return showEditMessageSheet( - context: context, - channel: channel, - message: message, - editMessageInputBuilder: widget.editMessageInputBuilder, - ); - } - + // Shows a confirmation dialog before flagging the message. Future _maybeFlagMessage( BuildContext context, Message message, @@ -1080,6 +800,7 @@ class _StreamMessageWidgetState extends State return channel.client.flagMessage(messageId); } + // Toggles a reaction: removes it if already present, otherwise sends it. Future _selectReaction( BuildContext context, Message message, @@ -1104,7 +825,9 @@ class _StreamMessageWidgetState extends State } } +// Truncates long message text for display in the actions modal preview. extension on Message { + // Returns a copy with text and nested content truncated to 100 characters. Message get trimmed { final trimmedText = switch (text) { final text? when text.length > 100 => '${text.substring(0, 100)}...', @@ -1119,7 +842,9 @@ extension on Message { } } +// Truncates long poll names for display in the actions modal preview. extension on Poll { + // Returns a copy with name truncated to 100 characters. Poll get trimmed { final trimmedName = switch (name) { final name when name.length > 100 => '${name.substring(0, 100)}...', @@ -1129,3 +854,58 @@ extension on Poll { return copyWith(name: trimmedName); } } + +// Built-in fallback theme values for [DefaultStreamMessage]. +// +// Used when neither the explicit props nor the ambient +// [StreamMessageItemThemeData] provide a value for a given property. +class _StreamMessageWidgetDefaults extends StreamMessageItemThemeData { + _StreamMessageWidgetDefaults( + this._context, { + this.isPinned = false, + required MessageState state, + }) : _messageState = state; + + final bool isPinned; + + final BuildContext _context; + final MessageState _messageState; + + late final StreamSpacing _spacing = _context.streamSpacing; + late final StreamColorScheme _colorScheme = _context.streamColorScheme; + + @override + double get spacing => _spacing.xs; + + @override + StreamAvatarSize get avatarSize => .md; + + @override + EdgeInsetsGeometry get padding => .symmetric(horizontal: _spacing.md); + + @override + Color? get backgroundColor { + if (isPinned && !_messageState.isDeleted) return _colorScheme.backgroundHighlight; + return StreamColors.transparent; + } + + @override + StreamMessageStyleVisibility get leadingVisibility => .resolveWith( + (placement) => switch ((placement.channelKind, placement.alignment, placement.stackPosition)) { + (.direct, _, _) || (_, .end, _) => .gone, + (_, _, .top || .middle) => .hidden, + (_, _, .single || .bottom) => .visible, + }, + ); + + @override + StreamMessageStyleVisibility get headerVisibility => .all(.visible); + + @override + StreamMessageStyleVisibility get footerVisibility => .resolveWith( + (placement) => switch (placement.stackPosition) { + .single || .bottom => .visible, + _ => .gone, + }, + ); +} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart deleted file mode 100644 index 1079cedd83..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart +++ /dev/null @@ -1,446 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:meta/meta.dart'; -import 'package:stream_chat_flutter/src/reactions/desktop_reactions_builder.dart'; -import 'package:stream_chat_flutter/src/reactions/indicator/reaction_indicator_bubble_overlay.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// Signature for the builder function that will be called when the message -/// bottom row is built. Includes the [Message]. -typedef BottomRowBuilder = Widget Function(BuildContext, Message); - -/// Signature for the builder function that will be called when the message -/// bottom row is built. Includes the [Message] and the default [BottomRow]. -typedef BottomRowBuilderWithDefaultWidget = - Widget Function( - BuildContext, - Message, - BottomRow, - ); - -/// {@template messageWidgetContent} -/// The main content of a [StreamMessageWidget]. -/// -/// Should not be used outside of [MessageWidget. -/// {@endtemplate} -@internal -class MessageWidgetContent extends StatelessWidget { - /// {@macro messageWidgetContent} - const MessageWidgetContent({ - super.key, - required this.reverse, - required this.isPinned, - required this.showPinHighlight, - required this.showBottomRow, - required this.message, - required this.showUserAvatar, - required this.avatarWidth, - required this.showReactions, - required this.onReactionsTap, - required this.onReactionsHover, - required this.messageTheme, - required this.streamChatTheme, - required this.isFailedState, - required this.hasQuotedMessage, - required this.hasUrlAttachments, - required this.hasNonUrlAttachments, - required this.isOnlyEmoji, - required this.isGiphy, - required this.attachmentBuilders, - required this.attachmentPadding, - required this.attachmentShape, - required this.onAttachmentTap, - required this.onShowMessage, - required this.onReplyTap, - required this.attachmentActionsModalBuilder, - required this.textPadding, - required this.translateUserAvatar, - required this.bottomRowPadding, - required this.showInChannel, - required this.streamChat, - required this.showSendingIndicator, - required this.showThreadReplyIndicator, - required this.showTimeStamp, - required this.showUsername, - required this.showEditedLabel, - required this.messageWidget, - required this.onThreadTap, - required this.reactionIndicatorBuilder, - this.onUserAvatarTap, - this.borderRadiusGeometry, - this.borderSide, - this.shape, - this.onQuotedMessageTap, - this.onMentionTap, - this.onLinkTap, - this.textBuilder, - this.quotedMessageBuilder, - this.deletedMessageBuilder, - this.bottomRowBuilderWithDefaultWidget, - this.userAvatarBuilder, - }); - - /// {@macro reverse} - final bool reverse; - - /// {@macro isPinned} - final bool isPinned; - - /// {@macro showPinHighlight} - final bool showPinHighlight; - - /// {@macro showBottomRow} - final bool showBottomRow; - - /// {@macro message} - final Message message; - - /// {@macro showUserAvatar} - final DisplayWidget showUserAvatar; - - /// The width of the avatar. - final double avatarWidth; - - /// {@macro showReactions} - final bool showReactions; - - /// {@macro onReactionsTap} - final VoidCallback onReactionsTap; - - /// {@macro onReactionsHover} - final OnReactionsHover? onReactionsHover; - - /// {@macro messageTheme} - final StreamMessageThemeData messageTheme; - - /// {@macro onUserAvatarTap} - final void Function(User)? onUserAvatarTap; - - /// {@macro streamChatThemeData} - final StreamChatThemeData streamChatTheme; - - /// {@macro isFailedState} - final bool isFailedState; - - /// {@macro borderRadiusGeometry} - final BorderRadiusGeometry? borderRadiusGeometry; - - /// {@macro borderSide} - final BorderSide? borderSide; - - /// {@macro shape} - final ShapeBorder? shape; - - /// {@macro hasQuotedMessage} - final bool hasQuotedMessage; - - /// {@macro hasUrlAttachments} - final bool hasUrlAttachments; - - /// {@macro hasNonUrlAttachments} - final bool hasNonUrlAttachments; - - /// {@macro isOnlyEmoji} - final bool isOnlyEmoji; - - /// {@macro isGiphy} - final bool isGiphy; - - /// {@macro attachmentBuilders} - final List? attachmentBuilders; - - /// {@macro attachmentPadding} - final EdgeInsetsGeometry attachmentPadding; - - /// {@macro attachmentShape} - final ShapeBorder? attachmentShape; - - /// {@macro onAttachmentWidgetTap} - final OnAttachmentWidgetTap? onAttachmentTap; - - /// {@macro onShowMessage} - final ShowMessageCallback? onShowMessage; - - /// {@macro onReplyTap} - final void Function(Message)? onReplyTap; - - /// {@macro onThreadTap} - final void Function(Message)? onThreadTap; - - /// {@macro attachmentActionsBuilder} - final AttachmentActionsBuilder? attachmentActionsModalBuilder; - - /// {@macro textPadding} - final EdgeInsets textPadding; - - /// {@macro onQuotedMessageTap} - final OnQuotedMessageTap? onQuotedMessageTap; - - /// {@macro onMentionTap} - final void Function(User)? onMentionTap; - - /// {@macro onLinkTap} - final void Function(String)? onLinkTap; - - /// {@macro textBuilder} - final Widget Function(BuildContext, Message)? textBuilder; - - /// {@macro quotedMessageBuilder} - final Widget Function(BuildContext, Message)? quotedMessageBuilder; - - /// {@macro deletedMessageBuilder} - final Widget Function(BuildContext, Message)? deletedMessageBuilder; - - /// {@macro translateUserAvatar} - final bool translateUserAvatar; - - /// The padding to use for this widget. - final double bottomRowPadding; - - /// {@macro bottomRowBuilderWithDefaultWidget} - final BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget; - - /// {@macro showInChannelIndicator} - final bool showInChannel; - - /// {@macro streamChat} - final StreamChatState streamChat; - - /// {@macro showSendingIndicator} - final bool showSendingIndicator; - - /// {@macro showThreadReplyIndicator} - final bool showThreadReplyIndicator; - - /// {@macro showTimestamp} - final bool showTimeStamp; - - /// {@macro showUsername} - final bool showUsername; - - /// {@macro showEdited} - final bool showEditedLabel; - - /// {@macro messageWidget} - final StreamMessageWidget messageWidget; - - /// {@macro userAvatarBuilder} - final Widget Function(BuildContext, User)? userAvatarBuilder; - - /// {@macro reactionIndicatorBuilder} - final ReactionIndicatorBuilder reactionIndicatorBuilder; - - @override - Widget build(BuildContext context) { - final hasThreadParticipants = message.threadParticipants?.isNotEmpty == true; - - return Column( - crossAxisAlignment: reverse ? CrossAxisAlignment.end : CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Stack( - clipBehavior: Clip.none, - alignment: reverse ? AlignmentDirectional.bottomEnd : AlignmentDirectional.bottomStart, - children: [ - if (showBottomRow) - Padding( - padding: EdgeInsets.only( - left: !reverse ? bottomRowPadding : 0, - right: reverse ? bottomRowPadding : 0, - bottom: isPinned && showPinHighlight ? 6.0 : 0.0, - ), - child: _buildBottomRow(context), - ), - Padding( - padding: EdgeInsets.only( - bottom: isPinned && showPinHighlight ? 8.0 : 0.0, - ), - child: Column( - crossAxisAlignment: reverse ? CrossAxisAlignment.end : CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (isPinned && message.pinnedBy != null && showPinHighlight) - PinnedMessage( - pinnedBy: message.pinnedBy!, - currentUser: streamChat.currentUser!, - ), - Row( - spacing: 8, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - ...[ - Flexible( - child: ReactionIndicatorBubbleOverlay( - reverse: reverse, - message: message, - onTap: onReactionsTap, - visible: isMobileDevice && showReactions, - anchorOffset: const Offset(0, 36), - reactionIndicatorBuilder: reactionIndicatorBuilder, - child: Padding( - padding: switch (showReactions) { - true => const EdgeInsets.only(top: 28), - false => EdgeInsets.zero, - }, - child: _buildMessageCard(context), - ), - ), - ), - ].addConditionally( - reverse: reverse, - condition: (_) => message.user != null, - switch (showUserAvatar) { - DisplayWidget.gone => null, - DisplayWidget.hide => SizedBox(width: avatarWidth), - DisplayWidget.show => UserAvatarTransform( - onUserAvatarTap: onUserAvatarTap, - userAvatarBuilder: userAvatarBuilder, - translateUserAvatar: translateUserAvatar, - messageTheme: messageTheme, - message: message, - ), - }, - ), - ], - ), - if (isDesktopDeviceOrWeb && showReactions) ...[ - Padding( - padding: switch (showUserAvatar) { - DisplayWidget.gone => EdgeInsets.zero, - _ => EdgeInsets.only( - left: avatarWidth + 4, - right: avatarWidth + 4, - ), - }, - child: DesktopReactionsBuilder( - message: message, - messageTheme: messageTheme, - onHover: onReactionsHover, - borderSide: borderSide, - reverse: reverse, - ), - ), - ], - if (showBottomRow) - SizedBox( - height: context.textScaleFactor * (hasThreadParticipants ? 24.0 : 18.0), - ), - ], - ), - ), - if (isFailedState) - Positioned( - right: reverse ? 0 : null, - left: reverse ? null : 0, - bottom: showBottomRow ? 18 : -2, - child: Icon( - context.streamIcons.exclamationCircle1, - color: streamChatTheme.colorTheme.accentError, - ), - ), - ], - ), - ], - ); - } - - Widget _buildDeletedMessage(BuildContext context) { - if (deletedMessageBuilder case final builder?) { - return builder(context, message); - } - - return StreamDeletedMessage( - borderRadiusGeometry: borderRadiusGeometry, - borderSide: borderSide, - shape: shape, - messageTheme: messageTheme, - ); - } - - Widget _buildMessageCard(BuildContext context) { - if (message.isDeleted) { - return Container( - margin: EdgeInsetsDirectional.only( - end: reverse && isFailedState ? 12.0 : 0.0, - start: !reverse && isFailedState ? 12.0 : 0.0, - ), - child: _buildDeletedMessage(context), - ); - } - - return MessageCard( - message: message, - isFailedState: isFailedState, - showUserAvatar: showUserAvatar, - messageTheme: messageTheme, - hasQuotedMessage: hasQuotedMessage, - hasUrlAttachments: hasUrlAttachments, - hasNonUrlAttachments: hasNonUrlAttachments, - isOnlyEmoji: isOnlyEmoji, - isGiphy: isGiphy, - attachmentBuilders: attachmentBuilders, - attachmentPadding: attachmentPadding, - attachmentShape: attachmentShape, - onAttachmentTap: onAttachmentTap, - onReplyTap: onReplyTap, - onShowMessage: onShowMessage, - attachmentActionsModalBuilder: attachmentActionsModalBuilder, - textPadding: textPadding, - reverse: reverse, - onQuotedMessageTap: onQuotedMessageTap, - onMentionTap: onMentionTap, - onLinkTap: onLinkTap, - textBuilder: textBuilder, - quotedMessageBuilder: quotedMessageBuilder, - borderRadiusGeometry: borderRadiusGeometry, - borderSide: borderSide, - shape: shape, - ); - } - - Widget _buildBottomRow(BuildContext context) { - final defaultWidget = BottomRow( - onThreadTap: onThreadTap, - message: message, - reverse: reverse, - messageTheme: messageTheme, - hasUrlAttachments: hasUrlAttachments, - isOnlyEmoji: isOnlyEmoji, - isDeleted: message.isDeleted, - isGiphy: isGiphy, - showInChannel: showInChannel, - showSendingIndicator: showSendingIndicator, - showThreadReplyIndicator: showThreadReplyIndicator, - showTimeStamp: showTimeStamp, - showUsername: showUsername, - showEditedLabel: showEditedLabel, - streamChatTheme: streamChatTheme, - streamChat: streamChat, - hasNonUrlAttachments: hasNonUrlAttachments, - ); - - if (bottomRowBuilderWithDefaultWidget != null) { - return bottomRowBuilderWithDefaultWidget!( - context, - message, - defaultWidget, - ); - } - - return defaultWidget; - } -} - -extension on Iterable { - Iterable addConditionally( - T? item, { - required bool condition(T element), - bool reverse = false, - }) sync* { - for (final element in this) { - if (item != null && !reverse && condition(element)) yield item; - yield element; - if (item != null && reverse && condition(element)) yield item; - } - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content_components.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content_components.dart deleted file mode 100644 index a3a6782630..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content_components.dart +++ /dev/null @@ -1,6 +0,0 @@ -export 'bottom_row.dart'; -export 'message_card.dart'; -export 'parse_attachments.dart'; -export 'pinned_message.dart'; -export 'quoted_message.dart'; -export 'user_avatar_transform.dart'; diff --git a/packages/stream_chat_flutter/lib/src/message_widget/parse_attachments.dart b/packages/stream_chat_flutter/lib/src/message_widget/parse_attachments.dart index b0c9080cd5..5e4c386576 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/parse_attachments.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/parse_attachments.dart @@ -29,12 +29,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// ) /// ``` /// {@endtemplate} -typedef OnAttachmentWidgetTap = - FutureOr Function( - BuildContext context, - Message message, - Attachment attachment, - ); +typedef OnAttachmentWidgetTap = FutureOr Function(BuildContext context, Message message, Attachment attachment); /// {@template parseAttachments} /// Parses the attachments of a [StreamMessageWidget]. @@ -46,8 +41,8 @@ class ParseAttachments extends StatelessWidget { const ParseAttachments({ super.key, required this.message, - required this.attachmentBuilders, - required this.attachmentPadding, + this.attachmentBuilders, + this.attachmentPadding, this.attachmentShape, this.onAttachmentTap, this.onShowMessage, @@ -63,7 +58,7 @@ class ParseAttachments extends StatelessWidget { final List? attachmentBuilders; /// {@macro attachmentPadding} - final EdgeInsetsGeometry attachmentPadding; + final EdgeInsetsGeometry? attachmentPadding; /// {@macro attachmentShape} final ShapeBorder? attachmentShape; @@ -98,13 +93,16 @@ class ParseAttachments extends StatelessWidget { return _defaultAttachmentTapHandler(context, message, attachment); } + final config = StreamChatConfiguration.maybeOf(context); + final effectiveAttachmentBuilder = attachmentBuilders ?? config?.attachmentBuilders; + // Create a default attachmentBuilders list if not provided. final builders = StreamAttachmentWidgetBuilder.defaultBuilders( message: message, shape: attachmentShape, padding: attachmentPadding, onAttachmentTap: effectiveOnAttachmentTap, - customAttachmentBuilders: attachmentBuilders, + customAttachmentBuilders: effectiveAttachmentBuilder, ); final catalog = AttachmentWidgetCatalog(builders: builders); diff --git a/packages/stream_chat_flutter/lib/src/message_widget/pinned_message.dart b/packages/stream_chat_flutter/lib/src/message_widget/pinned_message.dart deleted file mode 100644 index ae33dcb9ed..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/pinned_message.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template pinnedMessage} -/// A pinned message in a chat. -/// -/// Used in [MessageWidgetContent]. Should not be used elsewhere. -/// {@endtemplate} -class PinnedMessage extends StatelessWidget { - /// {@macro pinnedMessage} - const PinnedMessage({ - super.key, - required this.pinnedBy, - required this.currentUser, - }); - - /// The [User] who pinned this message. - final User pinnedBy; - - /// The current [User]. - final User currentUser; - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 8), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - context.streamIcons.pin, - size: 16, - ), - const SizedBox( - width: 4, - ), - Text( - context.translations.pinnedByUserText( - pinnedBy: pinnedBy, - currentUser: currentUser, - ), - style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, - fontSize: 13, - fontWeight: FontWeight.w400, - ), - ), - ], - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/quoted_message.dart b/packages/stream_chat_flutter/lib/src/message_widget/quoted_message.dart deleted file mode 100644 index bd9cef05bb..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/quoted_message.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template quotedMessage} -/// A quoted message in a chat. -/// -/// Used in [QuotedMessageCard]. Should not be used elsewhere. -/// {@endtemplate} -class QuotedMessage extends StatelessWidget { - /// {@macro quotedMessage} - const QuotedMessage({ - super.key, - required this.message, - required this.hasNonUrlAttachments, - this.textBuilder, - }); - - /// {@macro message} - final Message message; - - /// {@macro hasNonUrlAttachments} - final bool hasNonUrlAttachments; - - /// {@macro textBuilder} - final Widget Function(BuildContext, Message)? textBuilder; - - @override - Widget build(BuildContext context) { - final streamChat = StreamChat.of(context); - final chatThemeData = StreamChatTheme.of(context); - - final isMyMessage = message.user?.id == streamChat.currentUser?.id; - final isMyQuotedMessage = message.quotedMessage?.user?.id == streamChat.currentUser?.id; - return StreamQuotedMessageWidget( - message: message.quotedMessage!, - messageTheme: isMyMessage ? chatThemeData.otherMessageTheme : chatThemeData.ownMessageTheme, - reverse: !isMyQuotedMessage, - textBuilder: textBuilder, - padding: EdgeInsets.only( - right: 8, - left: 8, - top: 8, - bottom: hasNonUrlAttachments ? 8 : 0, - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/sending_indicator_builder.dart b/packages/stream_chat_flutter/lib/src/message_widget/sending_indicator_builder.dart deleted file mode 100644 index 919f216c45..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/sending_indicator_builder.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template sendingIndicatorWrapper} -/// Helper widget for building a [StreamSendingIndicator]. -/// -/// Used in [BottomRow]. Should not be used elsewhere. -/// {@endtemplate} -class SendingIndicatorBuilder extends StatelessWidget { - /// {@macro sendingIndicatorWrapper} - const SendingIndicatorBuilder({ - super.key, - required this.messageTheme, - required this.message, - required this.hasNonUrlAttachments, - required this.streamChat, - required this.streamChatTheme, - this.channel, - }); - - /// {@macro messageTheme} - final StreamMessageThemeData messageTheme; - - /// {@macro message} - final Message message; - - /// {@macro hasNonUrlAttachments} - final bool hasNonUrlAttachments; - - /// {@macro streamChat} - final StreamChatState streamChat; - - /// {@macro streamChatThemeData} - final StreamChatThemeData streamChatTheme; - - /// {@macro channel} - final Channel? channel; - - @override - Widget build(BuildContext context) { - final style = messageTheme.createdAtStyle; - final channel = this.channel ?? StreamChannel.of(context).channel; - - if (hasNonUrlAttachments && message.state.isOutgoing) { - final totalAttachments = message.attachments.length; - final attachmentsToUpload = message.attachments.where((it) { - return !it.uploadState.isSuccess; - }); - - if (attachmentsToUpload.isNotEmpty) { - return Text( - context.translations.attachmentsUploadProgressText( - remaining: attachmentsToUpload.length, - total: totalAttachments, - ), - style: style, - ); - } - } - - return BetterStreamBuilder>( - stream: channel.state?.readStream, - initialData: channel.state?.read, - builder: (context, data) { - final readList = data.readsOf(message: message); - final isMessageRead = readList.isNotEmpty; - - final deliveriesList = data.deliveriesOf(message: message); - final isMessageDelivered = deliveriesList.isNotEmpty; - - return StreamSendingIndicator( - message: message, - isMessageRead: isMessageRead, - isMessageDelivered: isMessageDelivered, - size: 16, - ); - }, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/text_bubble.dart b/packages/stream_chat_flutter/lib/src/message_widget/text_bubble.dart deleted file mode 100644 index 4f3ad434ad..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/text_bubble.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_core_flutter/stream_core_flutter.dart' as core; - -/// {@template textBubble} -/// The bubble around a [StreamMessageText]. -/// -/// Used in [MessageCard]. Should not be used elsewhere. -/// {@endtemplate} -class TextBubble extends StatelessWidget { - /// {@macro textBubble} - const TextBubble({ - super.key, - required this.message, - required this.isOnlyEmoji, - required this.textPadding, - required this.messageStyle, - required this.hasUrlAttachments, - required this.hasQuotedMessage, - this.textBuilder, - this.onLinkTap, - this.onMentionTap, - }); - - /// {@macro message} - final Message message; - - /// {@macro isOnlyEmoji} - final bool isOnlyEmoji; - - /// {@macro textPadding} - final EdgeInsets textPadding; - - /// {@macro textBuilder} - final Widget Function(BuildContext, Message)? textBuilder; - - /// {@macro onLinkTap} - final void Function(String)? onLinkTap; - - /// {@macro onMentionTap} - final void Function(User)? onMentionTap; - - /// TODO: merge with messageTheme - final core.StreamMessageStyle messageStyle; - - /// {@macro hasUrlAttachments} - final bool hasUrlAttachments; - - /// {@macro hasQuotedMessage} - final bool hasQuotedMessage; - - @override - Widget build(BuildContext context) { - if (message.text?.trim().isEmpty ?? true) return const Empty(); - return DefaultTextStyle( - style: context.streamTextTheme.bodyDefault.copyWith( - color: messageStyle.textColor, - fontSize: isOnlyEmoji ? 42 : null, - ), - child: Padding( - padding: isOnlyEmoji ? EdgeInsets.zero : textPadding, - child: textBuilder != null - ? textBuilder!(context, message) - : StreamMessageText( - onLinkTap: onLinkTap, - message: message, - onMentionTap: onMentionTap, - ), - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/thread_painter.dart b/packages/stream_chat_flutter/lib/src/message_widget/thread_painter.dart deleted file mode 100644 index 09bb63c93f..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/thread_painter.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template threadReplyPainter} -/// A custom painter used to render thread replies. -/// -/// Used in [BottomRow]. -/// {@endtemplate} -class ThreadReplyPainter extends CustomPainter { - /// {@macro threadReplyPainter} - const ThreadReplyPainter({ - this.context, - required this.color, - this.reverse = false, - }); - - /// The color to paint the thread reply with. - final Color? color; - - /// The [BuildContext] to use to retrieve the [StreamChatTheme]. - final BuildContext? context; - - /// {@macro reverse} - final bool reverse; - - @override - void paint(Canvas canvas, Size size) { - final paint = Paint() - ..color = color ?? StreamChatTheme.of(context!).colorTheme.disabled - ..style = PaintingStyle.stroke - ..strokeWidth = 1 - ..strokeCap = StrokeCap.round; - - final path = Path() - ..moveTo(reverse ? size.width : 0, 0) - ..quadraticBezierTo( - reverse ? size.width : 0, - size.height * 0.38, - reverse ? size.width : 0, - size.height * 0.5, - ) - ..quadraticBezierTo( - reverse ? size.width : 0, - size.height, - reverse ? 0 : size.width, - size.height, - ); - canvas.drawPath(path, paint); - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) => false; -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/thread_participants.dart b/packages/stream_chat_flutter/lib/src/message_widget/thread_participants.dart deleted file mode 100644 index 488224c399..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/thread_participants.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template threadParticipants} -/// Shows the users participating in a thread. -/// -/// Used in [BottomRow]. -/// {@endtemplate} -class ThreadParticipants extends StatelessWidget { - /// {@macro threadParticipants} - const ThreadParticipants({ - super.key, - required this.threadParticipants, - }); - - /// The users participating in the thread. - final Iterable threadParticipants; - - @override - Widget build(BuildContext context) { - // TODO(redesign): Old design used 14px diameter avatars, but .xs is 20px. - return StreamUserAvatarStack( - max: 3, - size: .xs, - users: threadParticipants, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/user_avatar_transform.dart b/packages/stream_chat_flutter/lib/src/message_widget/user_avatar_transform.dart deleted file mode 100644 index 7893572f9a..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/user_avatar_transform.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template userAvatarTransform} -/// Transforms a [StreamUserAvatar] according to the specified translation. -/// -/// Used in [MessageWidgetContent]. -/// {@endtemplate} -class UserAvatarTransform extends StatelessWidget { - /// {@macro userAvatarTransform} - const UserAvatarTransform({ - super.key, - required this.translateUserAvatar, - required this.messageTheme, - required this.message, - this.userAvatarBuilder, - this.onUserAvatarTap, - }); - - /// {@macro translateUserAvatar} - final bool translateUserAvatar; - - /// {@macro messageTheme} - final StreamMessageThemeData messageTheme; - - /// {@macro userAvatarBuilder} - final Widget Function(BuildContext, User)? userAvatarBuilder; - - /// {@macro message} - final Message message; - - /// {@macro onUserAvatarTap} - final void Function(User)? onUserAvatarTap; - - @override - Widget build(BuildContext context) { - return Transform.translate( - offset: Offset( - 0, - translateUserAvatar ? (messageTheme.avatarTheme?.constraints.maxHeight ?? 40) / 2 : 0, - ), - child: switch (userAvatarBuilder) { - final builder? => builder(context, message.user!), - _ => GestureDetector( - onTap: switch (onUserAvatarTap) { - final onTap? => () => onTap(message.user!), - _ => null, - }, - child: StreamUserAvatar( - size: .md, - user: message.user!, - showOnlineIndicator: false, - ), - ), - }, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/username.dart b/packages/stream_chat_flutter/lib/src/message_widget/username.dart deleted file mode 100644 index 32f412ed65..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/username.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template username} -/// Displays the username of a particular message's sender. -/// {@endtemplate} -class Username extends StatelessWidget { - /// {@macro username} - const Username({ - super.key, - required this.message, - required this.textStyle, - }); - - /// {@macro message} - final Message message; - - /// {@macro messageTheme} - final TextStyle textStyle; - - @override - Widget build(BuildContext context) { - return Text( - message.user?.name ?? '', - maxLines: 1, - key: key, - style: textStyle, - overflow: TextOverflow.ellipsis, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator.dart b/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator.dart deleted file mode 100644 index fa442cf2ab..0000000000 --- a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator.dart +++ /dev/null @@ -1,123 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template reactionIndicatorBuilder} -/// Signature for a function that builds a custom reaction indicator widget. -/// -/// This allows users to customize how reactions are displayed on messages, -/// including showing reaction counts alongside emojis. -/// -/// Parameters: -/// - [context]: The build context. -/// - [message]: The message containing the reactions to display. -/// - [onTap]: An optional callback triggered when the reaction indicator -/// is tapped. -/// {@endtemplate} -typedef ReactionIndicatorBuilder = Widget Function(BuildContext context, Message message, VoidCallback? onTap); - -/// {@template streamReactionIndicator} -/// A widget that displays a horizontal list of reaction icons that users have -/// reacted with on a message. -/// -/// This widget is typically used to show the reactions on a message in a -/// compact way, allowing users to see which reactions have been added -/// to a message without opening a full user reactions view. -/// {@endtemplate} -class StreamReactionIndicator extends StatelessWidget { - /// {@macro streamReactionIndicator} - const StreamReactionIndicator({ - super.key, - this.onTap, - required this.message, - this.backgroundColor, - this.padding, - this.borderRadius, - this.reactionSorting, - }); - - /// Creates a [StreamReactionIndicator] using the default configuration. - /// - /// This is the recommended way to create a reaction indicator - /// as it ensures that the icons are consistent with the rest of the app. - factory StreamReactionIndicator.builder( - BuildContext _, - Message message, - VoidCallback? onTap, - ) { - return StreamReactionIndicator(onTap: onTap, message: message); - } - - /// Callback triggered when the reaction indicator is tapped. - final VoidCallback? onTap; - - /// Message to attach the reaction to. - final Message message; - - /// Background color for the reaction indicator. - final Color? backgroundColor; - - /// Padding around the reaction indicator. - /// - /// Defaults to `EdgeInsets.all(8)`. - final EdgeInsetsGeometry? padding; - - /// Border radius for the reaction indicator. - /// - /// Defaults to a circular border with a radius of 26. - final BorderRadiusGeometry? borderRadius; - - /// Sorting strategy for the reaction. - /// - /// Defaults to sorting by the first reaction at. - final Comparator? reactionSorting; - - @override - Widget build(BuildContext context) { - final radius = context.streamRadius; - final spacing = context.streamSpacing; - final colorScheme = context.streamColorScheme; - - final effectivePadding = padding ?? .symmetric(horizontal: spacing.xs, vertical: spacing.xxs); - final effectiveBorderRadius = borderRadius ?? BorderRadius.all(radius.max); - final effectiveBackgroundColor = backgroundColor ?? colorScheme.backgroundElevation3; - - final side = BorderSide(color: colorScheme.borderDefault); - final shape = RoundedSuperellipseBorder(borderRadius: effectiveBorderRadius, side: side); - - final config = StreamChatConfiguration.of(context); - final resolver = config.reactionIconResolver; - - final reactionGroups = message.reactionGroups?.entries; - final effectiveReactionSorting = reactionSorting ?? ReactionSorting.byFirstReactionAt; - final sortedReactionGroups = reactionGroups?.sortedByCompare((it) => it.value, effectiveReactionSorting); - - final indicatorIcons = sortedReactionGroups?.map( - (group) => StreamEmoji( - size: StreamEmojiSize.sm, - emoji: resolver.resolve(context, group.key), - ), - ); - - final indicatorContent = Row( - mainAxisSize: .min, - spacing: spacing.xxs, - children: [...?indicatorIcons], - ); - - return Material( - shape: shape, - elevation: 3, - clipBehavior: .antiAlias, - color: effectiveBackgroundColor, - child: InkWell( - onTap: onTap, - child: SingleChildScrollView( - padding: effectivePadding, - scrollDirection: .horizontal, - child: indicatorContent, - ), - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_bubble_overlay.dart b/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_bubble_overlay.dart deleted file mode 100644 index 7ef2202d63..0000000000 --- a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_bubble_overlay.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/reactions/indicator/reaction_indicator.dart'; -import 'package:stream_chat_flutter/src/reactions/reaction_bubble_overlay.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; - -/// {@template reactionIndicatorBubbleOverlay} -/// A widget that displays a reaction indicator bubble overlay attached to a -/// [child] widget. Typically used to show the reactions for a [Message]. -/// -/// It positions the reaction indicator relative to the provided [child] widget, -/// using the given [anchorOffset] and [childSizeDelta] for fine-tuned placement -/// {@endtemplate} -class ReactionIndicatorBubbleOverlay extends StatelessWidget { - /// {@macro reactionIndicatorBubbleOverlay} - const ReactionIndicatorBubbleOverlay({ - super.key, - this.onTap, - required this.message, - required this.child, - this.visible = true, - this.reverse = false, - this.anchorOffset = Offset.zero, - this.reactionIndicatorBuilder = StreamReactionIndicator.builder, - }); - - /// Whether the overlay should be visible. - final bool visible; - - /// Whether to reverse the alignment of the overlay. - final bool reverse; - - /// The widget to which the overlay is anchored. - final Widget child; - - /// The message to display reactions for. - final Message message; - - /// Callback triggered when the reaction indicator is tapped. - final VoidCallback? onTap; - - /// The offset to apply to the anchor position. - final Offset anchorOffset; - - /// Builder for the reaction indicator widget. - final ReactionIndicatorBuilder reactionIndicatorBuilder; - - @override - Widget build(BuildContext context) { - return ReactionBubbleOverlay( - visible: visible, - anchor: ReactionBubbleAnchor( - offset: anchorOffset, - follower: AlignmentDirectional.bottomCenter, - target: AlignmentDirectional(reverse ? -1 : 1, -1), - ), - reaction: reactionIndicatorBuilder.call(context, message, onTap), - child: child, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart b/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart index a7d1b8f361..69146611bf 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart @@ -1,5 +1,6 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/src/stream_chat_configuration.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template onReactionPicked} @@ -7,106 +8,39 @@ import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@endtemplate} typedef OnReactionPicked = ValueSetter; -/// {@template reactionPickerBuilder} -/// Function signature for building a custom reaction picker widget. +/// {@template streamMessageReactionPicker} +/// A chat-specific reaction picker that bridges [StreamReactionPicker] with +/// chat domain models. /// -/// Use this to provide a custom reaction picker in [StreamMessageActionsModal] -/// or [StreamMessageReactionsModal]. +/// Resolves reaction icons via [ReactionIconResolver], tracks the current +/// user's own reactions on the [Message], and wires the "add reaction" button +/// to [StreamEmojiPickerSheet]. /// -/// Parameters: -/// - [context]: The build context. -/// - [message]: The message to show reactions for. -/// - [onReactionPicked]: Callback when a reaction is picked. -/// {@endtemplate} -typedef ReactionPickerBuilder = - Widget Function( - BuildContext context, - Message message, - OnReactionPicked? onReactionPicked, - ); - -/// {@template streamReactionPicker} -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/reaction_picker.png) -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/reaction_picker_paint.png) +/// Visual customisation is controlled through [StreamReactionPickerTheme] in +/// the widget tree. /// -/// A widget that displays a horizontal list of reaction icons that users can -/// select to react to a message. +/// See also: /// -/// The reaction picker can be configured with custom reaction types, padding, -/// border radius, and can be made scrollable or static depending on the -/// specific needs. +/// * [StreamReactionPicker], the domain-agnostic core picker. +/// * [ReactionIconResolver], which maps reaction types to emoji widgets. +/// * [StreamReactionPickerTheme], for customising the picker appearance. /// {@endtemplate} -class StreamReactionPicker extends StatelessWidget { - /// {@macro streamReactionPicker} - const StreamReactionPicker({ +class StreamMessageReactionPicker extends StatelessWidget { + /// {@macro streamMessageReactionPicker} + const StreamMessageReactionPicker({ super.key, - this.onReactionPicked, required this.message, - this.backgroundColor, - this.padding, - this.borderRadius, + this.onReactionPicked, }); - /// Creates a [StreamReactionPicker] using the default reaction types - /// provided by the [StreamChatConfiguration]. - /// - /// This is the recommended way to create a reaction picker - /// as it ensures that the icons are consistent with the rest of the app. - /// - /// The [onReactionPicked] callback is optional and can be used to handle - /// the reaction selection. - factory StreamReactionPicker.builder( - BuildContext context, - Message message, - OnReactionPicked? onReactionPicked, - ) { - final platform = Theme.of(context).platform; - return switch (platform) { - TargetPlatform.iOS || TargetPlatform.android => StreamReactionPicker( - message: message, - onReactionPicked: onReactionPicked, - ), - _ => StreamReactionPicker( - message: message, - borderRadius: BorderRadius.zero, - onReactionPicked: onReactionPicked, - ), - }; - } - - /// Message to attach the reaction to. + /// The message to attach the reaction to. final Message message; - /// {@macro onReactionPressed} + /// {@macro onReactionPicked} final OnReactionPicked? onReactionPicked; - /// Background color for the reaction picker. - final Color? backgroundColor; - - /// Padding around the reaction picker. - /// - /// Defaults to `EdgeInsets.all(4)`. - final EdgeInsetsGeometry? padding; - - /// Border radius for the reaction picker. - /// - /// Defaults to a circular border with a radius of 24. - final BorderRadiusGeometry? borderRadius; - @override Widget build(BuildContext context) { - final icons = context.streamIcons; - final radius = context.streamRadius; - final spacing = context.streamSpacing; - final colorScheme = context.streamColorScheme; - - final effectivePadding = padding ?? EdgeInsetsDirectional.only(start: spacing.xxs); - final effectiveBorderRadius = borderRadius ?? BorderRadius.all(radius.xxxxl); - final effectiveBackgroundColor = backgroundColor ?? colorScheme.backgroundElevation2; - - final side = BorderSide(color: colorScheme.borderDefault); - final shape = RoundedSuperellipseBorder(borderRadius: effectiveBorderRadius, side: side); - final config = StreamChatConfiguration.of(context); final resolver = config.reactionIconResolver; final reactionTypes = resolver.defaultReactions; @@ -114,63 +48,42 @@ class StreamReactionPicker extends StatelessWidget { final ownReactions = [...?message.ownReactions]; final ownReactionsMap = {for (final it in ownReactions) it.type: it}; - final reactionButtons = reactionTypes.map( - (type) => StreamEmojiButton( - key: Key(type), - size: .lg, - emoji: resolver.resolve(context, type), - // If the reaction is present in ownReactions, it is selected. - isSelected: ownReactionsMap[type] != null, - onPressed: () { - final reactionEmojiCode = resolver.emojiCode(type); - final pickedReaction = switch (ownReactionsMap[type]) { - final reaction? => reaction, - _ => Reaction(type: type, emojiCode: reactionEmojiCode), - }; - - return onReactionPicked?.call(pickedReaction); - }, - ), - ); - - final pickerContent = Row( - mainAxisSize: .min, - spacing: spacing.none, - children: [ - // TODO: Re-enable staggered animation when MessageWidget redesign is finalized. - ...reactionButtons, - StreamButton.icon( - key: const Key('add_reaction'), - size: .small, - type: .outline, - style: .secondary, - icon: icons.plusLarge, - onTap: () async { - final selectedReactions = ownReactionsMap.keys.toSet(); - final emoji = await StreamEmojiPickerSheet.show( - context: context, - selectedReactions: selectedReactions, - ); - - if (!context.mounted || emoji == null) return; - - final reaction = Reaction(type: emoji.shortName, emojiCode: emoji.emoji); - return onReactionPicked?.call(reaction); - }, + final items = [ + ...reactionTypes.map( + (type) => StreamReactionPickerItem( + key: type, + emoji: resolver.resolve(context, type), + // If the reaction is present in ownReactions, it is selected. + isSelected: ownReactionsMap[type] != null, ), - ], - ); - - return Material( - shape: shape, - elevation: 3, - clipBehavior: .antiAlias, - color: effectiveBackgroundColor, - child: SingleChildScrollView( - padding: effectivePadding, - scrollDirection: .horizontal, - child: pickerContent, ), + ]; + + void onItemPicked(StreamReactionPickerItem item) { + final reactionEmojiCode = resolver.emojiCode(item.key); + final pickedReaction = switch (ownReactionsMap[item.key]) { + final reaction? => reaction, + _ => Reaction(type: item.key, emojiCode: reactionEmojiCode), + }; + + return onReactionPicked?.call(pickedReaction); + } + + return StreamReactionPicker( + items: items, + onReactionPicked: onItemPicked, + onAddReactionTap: () async { + final selectedReactions = ownReactionsMap.keys.toSet(); + final emoji = await StreamEmojiPickerSheet.show( + context: context, + selectedReactions: selectedReactions, + ); + + if (!context.mounted || emoji == null) return; + + final reaction = Reaction(type: emoji.shortName, emojiCode: emoji.emoji); + return onReactionPicked?.call(reaction); + }, ); } } diff --git a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_bubble_overlay.dart b/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_bubble_overlay.dart deleted file mode 100644 index 5b7da48e6c..0000000000 --- a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_bubble_overlay.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/reactions/picker/reaction_picker.dart'; -import 'package:stream_chat_flutter/src/reactions/reaction_bubble_overlay.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; - -/// {@template reactionPickerBubbleOverlay} -/// A widget that displays a reaction picker bubble overlay attached to a -/// [child] widget. Typically used with the [MessageWidget] as the child. -/// -/// It positions the reaction picker relative to the provided [child] widget, -/// using the given [anchorOffset] and [childSizeDelta] for fine-tuned placement -/// {@endtemplate} -class ReactionPickerBubbleOverlay extends StatelessWidget { - /// {@macro reactionPickerBubbleOverlay} - const ReactionPickerBubbleOverlay({ - super.key, - required this.message, - required this.child, - this.onReactionPicked, - this.visible = true, - this.reverse = false, - this.anchorOffset = Offset.zero, - this.reactionPickerBuilder = StreamReactionPicker.builder, - }); - - /// Whether the overlay should be visible. - final bool visible; - - /// Whether to reverse the alignment of the overlay. - final bool reverse; - - /// The widget to which the overlay is anchored. - final Widget child; - - /// The message to attach the reaction to. - final Message message; - - /// Callback triggered when a reaction is picked. - final OnReactionPicked? onReactionPicked; - - /// Builder for the reaction picker widget. - final ReactionPickerBuilder reactionPickerBuilder; - - /// The offset to apply to the anchor position. - final Offset anchorOffset; - - @override - Widget build(BuildContext context) { - return ReactionBubbleOverlay( - visible: visible, - anchor: ReactionBubbleAnchor( - offset: anchorOffset, - follower: AlignmentDirectional(reverse ? 1 : -1, 1), - target: AlignmentDirectional(reverse ? 1 : -1, -1), - ), - reaction: reactionPickerBuilder.call(context, message, onReactionPicked), - child: child, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble_overlay.dart b/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble_overlay.dart deleted file mode 100644 index 2a5087ecf2..0000000000 --- a/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble_overlay.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_portal/flutter_portal.dart'; - -/// Defines the anchor settings for positioning a ReactionBubble relative to a -/// target widget. -class ReactionBubbleAnchor { - /// Creates an anchor with custom alignment and offset. - const ReactionBubbleAnchor({ - this.offset = Offset.zero, - required this.target, - required this.follower, - this.shiftToWithinBound = const AxisFlag(x: true), - }); - - /// Creates an anchor that positions the bubble at the top-end of the - /// target widget. - const ReactionBubbleAnchor.topEnd({ - this.offset = Offset.zero, - this.shiftToWithinBound = const AxisFlag(x: true), - }) : target = AlignmentDirectional.topEnd, - follower = AlignmentDirectional.bottomCenter; - - /// Creates an anchor that positions the bubble at the top-start of the - /// target widget. - const ReactionBubbleAnchor.topStart({ - this.offset = Offset.zero, - this.shiftToWithinBound = const AxisFlag(x: true), - }) : target = AlignmentDirectional.topStart, - follower = AlignmentDirectional.bottomCenter; - - /// Additional offset applied to the bubble position. - final Offset offset; - - /// Target alignment relative to the target widget. - final AlignmentDirectional target; - - /// Alignment of the bubble follower relative to the target alignment. - final AlignmentDirectional follower; - - /// Whether to shift the bubble within the visible screen bounds along each - /// axis if it exceeds the screen size. - final AxisFlag shiftToWithinBound; -} - -/// An overlay widget that displays a reaction bubble near a child widget. -class ReactionBubbleOverlay extends StatelessWidget { - /// Creates a new instance of [ReactionBubbleOverlay]. - const ReactionBubbleOverlay({ - super.key, - this.visible = true, - required this.child, - required this.reaction, - this.anchor = const ReactionBubbleAnchor.topEnd(), - }); - - /// The target child widget to anchor the reaction bubble to. - final Widget child; - - /// The reaction widget to display inside the bubble. - final Widget reaction; - - /// Whether the reaction bubble is visible. - final bool visible; - - /// The anchor configuration to control bubble positioning. - final ReactionBubbleAnchor anchor; - - @override - Widget build(BuildContext context) { - // If the overlay should not be visible, return the child without any overlay. - if (!visible) return child; - - final alignment = anchor; - final direction = Directionality.maybeOf(context); - final targetAlignment = alignment.target.resolve(direction); - final followerAlignment = alignment.follower.resolve(direction); - - return PortalTarget( - anchor: Aligned( - target: targetAlignment, - follower: followerAlignment, - offset: anchor.offset, - shiftToWithinBound: anchor.shiftToWithinBound, - ), - portalFollower: reaction, - child: child, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index b24fdbdca2..0ed8077047 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -37,6 +37,7 @@ class StreamChat extends StatefulWidget { required this.child, this.streamChatThemeData, this.streamChatConfigData, + this.componentBuilders, this.onBackgroundEventReceived, this.backgroundKeepAlive = const Duration(minutes: 1), this.connectivityStream, @@ -54,6 +55,36 @@ class StreamChat extends StatefulWidget { /// Non-theme related UI configuration options. final StreamChatConfigurationData? streamChatConfigData; + /// Custom component builders for overriding default UI components. + /// + /// When provided, a [StreamComponentFactory] is inserted into the widget + /// tree below the theme and above [StreamChatCore], allowing all descendant + /// widgets to resolve custom builders. + /// + /// {@tool snippet} + /// + /// Override the default message widget with a custom builder: + /// + /// ```dart + /// StreamChat( + /// client: client, + /// componentBuilders: StreamComponentBuilders( + /// extensions: streamChatComponentBuilders( + /// messageWidget: (context, props) { + /// return DefaultStreamMessage( + /// props: props.copyWith( + /// actionsBuilder: myActionsBuilder, + /// ), + /// ); + /// }, + /// ), + /// ), + /// child: MyApp(), + /// ) + /// ``` + /// {@end-tool} + final StreamComponentBuilders? componentBuilders; + /// The amount of time that will pass before disconnecting the client /// in the background final Duration backgroundKeepAlive; @@ -155,39 +186,39 @@ class StreamChatState extends State { @override Widget build(BuildContext context) { final theme = _getTheme(context, widget.streamChatThemeData); - return Portal( - child: StreamChatConfiguration( - data: streamChatConfigData, - child: StreamChatTheme( - data: theme, - child: Builder( - builder: (context) { - final materialTheme = Theme.of(context); - final streamTheme = StreamChatTheme.of(context); - return Theme( - data: materialTheme.copyWith( - primaryIconTheme: streamTheme.primaryIconTheme, - colorScheme: materialTheme.colorScheme.copyWith( - secondary: streamTheme.colorTheme.accentPrimary, - ), - ), - child: StreamChatCore( - client: client, - onBackgroundEventReceived: widget.onBackgroundEventReceived, - backgroundKeepAlive: widget.backgroundKeepAlive, - connectivityStream: widget.connectivityStream, - child: Builder( - builder: (context) { - return widget.child ?? const Empty(); - }, - ), - ), - ); - }, - ), - ), + + Widget child = StreamChatTheme( + data: theme, + child: Builder( + builder: (context) { + final materialTheme = Theme.of(context); + final streamTheme = StreamChatTheme.of(context); + return Theme( + data: materialTheme.copyWith( + primaryIconTheme: streamTheme.primaryIconTheme, + colorScheme: materialTheme.colorScheme.copyWith( + secondary: streamTheme.colorTheme.accentPrimary, + ), + ), + child: StreamChatCore( + client: client, + onBackgroundEventReceived: widget.onBackgroundEventReceived, + backgroundKeepAlive: widget.backgroundKeepAlive, + connectivityStream: widget.connectivityStream, + child: widget.child ?? const Empty(), + ), + ); + }, ), ); + + if (widget.componentBuilders case final builders?) { + child = StreamComponentFactory(builders: builders, child: child); + } + + return Portal( + child: StreamChatConfiguration(data: streamChatConfigData, child: child), + ); } StreamChatThemeData _getTheme( diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart b/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart index dc015c83c3..bc9c5207c5 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart @@ -166,6 +166,9 @@ class StreamChatConfigurationData { bool draftMessagesEnabled = false, MessagePreviewFormatter? messagePreviewFormatter, StreamImageCDN imageCDN = const StreamImageCDN(), + List? attachmentBuilders, + StreamReactionsType? reactionType, + StreamReactionsPosition? reactionPosition, }) { return StreamChatConfigurationData._( loadingIndicator: loadingIndicator, @@ -176,6 +179,9 @@ class StreamChatConfigurationData { draftMessagesEnabled: draftMessagesEnabled, messagePreviewFormatter: messagePreviewFormatter ?? MessagePreviewFormatter(), imageCDN: imageCDN, + attachmentBuilders: attachmentBuilders, + reactionType: reactionType, + reactionPosition: reactionPosition, ); } @@ -188,6 +194,9 @@ class StreamChatConfigurationData { required this.draftMessagesEnabled, required this.messagePreviewFormatter, required this.imageCDN, + required this.attachmentBuilders, + this.reactionType, + this.reactionPosition, }); /// Copies the configuration options from one [StreamChatConfigurationData] to @@ -201,6 +210,9 @@ class StreamChatConfigurationData { bool? draftMessagesEnabled, MessagePreviewFormatter? messagePreviewFormatter, StreamImageCDN? imageCDN, + List? attachmentBuilders, + StreamReactionsType? reactionType, + StreamReactionsPosition? reactionPosition, }) { return StreamChatConfigurationData( reactionIconResolver: reactionIconResolver ?? this.reactionIconResolver, @@ -211,6 +223,9 @@ class StreamChatConfigurationData { draftMessagesEnabled: draftMessagesEnabled ?? this.draftMessagesEnabled, messagePreviewFormatter: messagePreviewFormatter ?? this.messagePreviewFormatter, imageCDN: imageCDN ?? this.imageCDN, + attachmentBuilders: attachmentBuilders ?? this.attachmentBuilders, + reactionType: reactionType ?? this.reactionType, + reactionPosition: reactionPosition ?? this.reactionPosition, ); } @@ -249,6 +264,26 @@ class StreamChatConfigurationData { /// Extend [StreamImageCDN] to customize behavior for a custom CDN. final StreamImageCDN imageCDN; + /// Custom attachment builders for rendering attachment widgets in messages. + /// + /// When non-null, these builders are prepended to the default builders + /// based on the [Attachment.type], allowing custom attachment types to be + /// rendered globally across all message widgets. + final List? attachmentBuilders; + + /// The visual type of the reactions display used across all message widgets. + /// + /// When null, the widget resolves its own default + /// ([StreamReactionsType.segmented]). + final StreamReactionsType? reactionType; + + /// Where reactions appear relative to the message bubble across all + /// message widgets. + /// + /// When null, the widget resolves its own default + /// ([StreamReactionsPosition.header]). + final StreamReactionsPosition? reactionPosition; + static Widget _defaultUserImage( BuildContext context, User user, diff --git a/packages/stream_chat_flutter/lib/src/utils/date_formatter.dart b/packages/stream_chat_flutter/lib/src/utils/date_formatter.dart index 7cad9fb564..0b0f22c2c4 100644 --- a/packages/stream_chat_flutter/lib/src/utils/date_formatter.dart +++ b/packages/stream_chat_flutter/lib/src/utils/date_formatter.dart @@ -3,11 +3,7 @@ import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; /// Represents a function type that formats a date. -typedef DateFormatter = - String Function( - BuildContext context, - DateTime date, - ); +typedef DateFormatter = String Function(BuildContext context, DateTime date); /// Formats the given [date] as a String. String formatDate(BuildContext context, DateTime date) { diff --git a/packages/stream_chat_flutter/lib/src/utils/typedefs.dart b/packages/stream_chat_flutter/lib/src/utils/typedefs.dart index ccc1fe6107..99c2b4a37f 100644 --- a/packages/stream_chat_flutter/lib/src/utils/typedefs.dart +++ b/packages/stream_chat_flutter/lib/src/utils/typedefs.dart @@ -256,32 +256,8 @@ typedef MessageSearchItemBuilder = GetMessageResponse, ); -/// {@template messageBuilder} -/// A widget builder for creating custom message UI. -/// -/// [defaultMessageWidget] is the default [StreamMessageWidget] configuration. -/// Use [defaultMessageWidget.copyWith] to customize it. -/// {@endtemplate} -typedef MessageBuilder = - Widget Function( - BuildContext, - MessageDetails, - List, - StreamMessageWidget defaultMessageWidget, - ); - -/// {@template parentMessageBuilder} -/// A widget builder for creating custom parent message UI. -/// -/// [defaultMessageWidget] is the default [StreamMessageWidget] configuration. -/// Use [defaultMessageWidget.copyWith] to customize it. -/// {@endtemplate} -typedef ParentMessageBuilder = - Widget Function( - BuildContext, - Message?, - StreamMessageWidget defaultMessageWidget, - ); +// Legacy MessageBuilder and ParentMessageBuilder typedefs removed. +// Use StreamMessageWidgetBuilder from message_list_view.dart instead. /// {@template systemMessageBuilder} /// A widget builder for creating custom system messages. diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index c2c86e280e..b5b8696cfd 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -24,6 +24,16 @@ export 'package:stream_core_flutter/stream_core_flutter.dart' StreamEmojiSize, StreamEmojiData, StreamEmojiPickerSheet, + StreamMessageAlignment, + StreamMessagePlacement, + StreamMessageStackPosition, + StreamReactionPicker, + StreamReactionPickerItem, + StreamReactionPickerProps, + StreamReactionPickerTheme, + StreamReactionPickerThemeData, + StreamReactionsPosition, + StreamReactionsType, streamSupportedEmojis; export 'src/ai_assistant/ai_typing_indicator_view.dart'; @@ -101,15 +111,10 @@ export 'src/message_list_view/message_list_view.dart'; export 'src/message_modal/message_action_confirmation_modal.dart'; export 'src/message_modal/message_actions_modal.dart'; export 'src/message_modal/message_modal.dart'; -export 'src/message_modal/message_reactions_modal.dart'; export 'src/message_modal/moderated_message_actions_modal.dart'; -export 'src/message_widget/deleted_message.dart'; -export 'src/message_widget/message_text.dart'; export 'src/message_widget/message_widget.dart'; -export 'src/message_widget/message_widget_content_components.dart'; export 'src/message_widget/moderated_message.dart'; export 'src/message_widget/system_message.dart'; -export 'src/message_widget/text_bubble.dart'; export 'src/misc/adaptive_dialog_action.dart'; export 'src/misc/animated_circle_border_painter.dart'; export 'src/misc/back_button.dart'; @@ -133,7 +138,6 @@ export 'src/poll/stream_poll_options_dialog.dart'; export 'src/poll/stream_poll_results_dialog.dart'; export 'src/poll/stream_poll_text_field.dart'; export 'src/reactions/detail/reaction_detail_sheet.dart'; -export 'src/reactions/indicator/reaction_indicator.dart'; export 'src/reactions/picker/reaction_picker.dart'; export 'src/reactions/user_reactions.dart'; export 'src/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart'; diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 4120ef87c8..bc6c041c9b 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: 57785868e62299901361affbddd06f71253e872f + ref: c7a31449e8632ea43f8c769be95a30ef9393a792 path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 diff --git a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_0.png b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_0.png index 07fd25e5c0fef8eaa2c8016e14661136585df5a6..be8989f375e94236113e1c79adc1dca1ef6cd834 100644 GIT binary patch delta 141 zcmX@Xc#Ls^V|{_Ai(^PeW$yu zv*sDL921ao&fr;S*rMj^VOzVP{_QCx!)F(3W_3$AC3EyDnzUU&XB<5rAOX<=)_bWX Z=lXSS`*q^#2N{6C)0NH7Wt~$(695!zH0J;S delta 143 zcmX@cc!F_)V||gQi(^Peg^N6oilhA8nzr0kb*I0nJ9ievd}+&{%48e-*Yp~k~n%5P1-IBQVG?f c=6ixW;2*dB*5x0H7=VDo)78&qol`;+05m={{r~^~ diff --git a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_1.png b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_1.png index 31faaf2e8ab6adb7e9cbdeeb0d09f710b03ab20c..311529e2474b0091ff2970f64e4acb566c943dcb 100644 GIT binary patch delta 141 zcmX@Xc#Ls^V|{_Ai(^PeVa4Qt_9?KTX69TaF1xIcM-JL}zr(UuXc)0@k~P a^QMv-w|&DNcU}e{@N{MKb6Mw<&;$Tzb~mp8 delta 143 zcmX@cc!F_)V||gQi(^PeC^b)3zoBl<4A2_XQs&sFX z!|uNgu{;Y6TaF1xIcLBbms-9Ex7#KEJ9k~?_Isbrf=E)U?sFNmU661}=IB*4fip6l a>Va4Qt_9?KTX69TaF1xIcM-JL}zr(UuXc)0@k~P a^QMv-w|&DNcU}e{@N{MKb6Mw<&;$Tzb~mp8 delta 143 zcmX@cc!F_)V||gQi(^PeC^b)3zoBl<4A2_XQs&sFX z!|uNgu{;Y6TaF1xIcLBbms-9Ex7#KEJ9k~?_Isbrf=E)U?sFNmU661}=IB*4fip6l a>Va4Qt_9?KTX69TaF1xIcM-JL}zr(UuXc)0@k~P a^QMv-w|&DNcU}e{@N{MKb6Mw<&;$Tzb~mp8 delta 143 zcmX@cc!F_)V||gQi(^PeC^b)3zoBl<4A2_XQs&sFX z!|uNgu{;Y6TaF1xIcLBbms-9Ex7#KEJ9k~?_Isbrf=E)U?sFNmU661}=IB*4fip6l a> channel.on(any(), any(), any(), any())).thenAnswer((_) => const Stream.empty()); - channelClientState = MockChannelState(); - when(() => channel.state).thenReturn(channelClientState); - when(() => channelClientState.messages).thenReturn([ - Message( - id: 'parentId', - ), - ]); - }); - - setUpAll(() { - registerFallbackValue(Message()); - }); - - testWidgets('BottomRow', (tester) async { - final theme = StreamChatThemeData.light(); - final onThreadTap = MockValueChanged(); - - await tester.pumpWidget( - StreamChatTheme( - data: theme, - child: MaterialApp( - home: Scaffold( - body: Center( - child: StreamChannel( - channel: channel, - child: BottomRow( - message: Message( - parentId: 'parentId', - ), - isDeleted: false, - showThreadReplyIndicator: false, - showUsername: false, - showInChannel: true, - showTimeStamp: false, - showEditedLabel: false, - reverse: false, - showSendingIndicator: false, - hasUrlAttachments: false, - isGiphy: false, - isOnlyEmoji: false, - messageTheme: theme.otherMessageTheme, - streamChatTheme: theme, - hasNonUrlAttachments: false, - streamChat: StreamChatState(), - onThreadTap: onThreadTap, - ), - ), - ), - ), - ), - ), - ); - - // wait for the initial state to be rendered. - await tester.pump(Duration.zero); - - await tester.tap(find.byType(GestureDetector)); - await tester.pumpAndSettle(); - - verify(() => onThreadTap.call(any())); - }); -} diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_dark.png index 564440b438f8f27227ca09cd42d7a3fb2b6b88c6..becc690ec7df4d432ea243c6717b2dabb53f97aa 100644 GIT binary patch delta 38 tcmdm@zeRt;My81mGBwAo8xCZh=BvJ-o{o^HIs00f?{elF{r5}E+i Cu@cJw delta 47 zcmezF{M~tj7W3v6%=2U>8!*NXB0Vp^4h$qd{u`SfWXt$&t;ucLK6U; Cyb#s^ delta 46 zcmX@=a@1vm8uR8Q%!_0ueyC%~KPugzPn5ZKB91fH&bF6*2UngH6M B621Tc diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png deleted file mode 100644 index 1a809d7150f823689ff503daca4e1b365a02a087..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8707 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 diff --git a/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart index 6de1cc9a08..a454847740 100644 --- a/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_core_flutter/stream_core_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' show StreamIconData; void main() { final message = Message( @@ -46,6 +46,7 @@ void main() { message: message, messageActions: messageActions, messageWidget: const Text('Message Widget'), + alignment: AlignmentDirectional.centerStart, ), ), ); @@ -66,6 +67,7 @@ void main() { message: message, messageActions: messageActions, messageWidget: const Text('Message Widget'), + alignment: AlignmentDirectional.centerStart, showReactionPicker: true, ), ), @@ -73,7 +75,7 @@ void main() { // Use a longer timeout to ensure everything is rendered await tester.pumpAndSettle(const Duration(seconds: 1)); - expect(find.byType(StreamReactionPicker), findsOneWidget); + expect(find.byType(StreamMessageReactionPicker), findsOneWidget); }); testWidgets( @@ -102,6 +104,7 @@ void main() { message: message, messageActions: messageActions, messageWidget: const Text('Message Widget'), + alignment: AlignmentDirectional.centerStart, showReactionPicker: true, ), ); @@ -117,7 +120,7 @@ void main() { await tester.pumpAndSettle(const Duration(seconds: 1)); // Verify reaction picker is shown - expect(find.byType(StreamReactionPicker), findsOneWidget); + expect(find.byType(StreamMessageReactionPicker), findsOneWidget); // Find and tap the first reaction (like) final reactionIconFinder = find.byIcon(Icons.thumb_up); @@ -183,6 +186,7 @@ void main() { message: message, messageActions: messageActions, messageWidget: buildMessageWidget(), + alignment: AlignmentDirectional.centerStart, ), ), ); @@ -197,6 +201,7 @@ void main() { message: message, messageActions: messageActions, messageWidget: buildMessageWidget(), + alignment: AlignmentDirectional.centerStart, showReactionPicker: true, ), ), @@ -212,7 +217,7 @@ void main() { message: message, messageActions: messageActions, messageWidget: buildMessageWidget(reverse: true), - reverse: true, + alignment: AlignmentDirectional.centerEnd, ), ), ); @@ -227,8 +232,8 @@ void main() { message: message, messageActions: messageActions, messageWidget: buildMessageWidget(reverse: true), + alignment: AlignmentDirectional.centerEnd, showReactionPicker: true, - reverse: true, ), ), ); diff --git a/packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart deleted file mode 100644 index aa2db5ea2d..0000000000 --- a/packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart +++ /dev/null @@ -1,327 +0,0 @@ -import 'package:alchemist/alchemist.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_portal/flutter_portal.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -import '../mocks.dart'; - -void main() { - final message = Message( - id: 'test-message', - text: 'This is a test message', - createdAt: DateTime.now(), - user: User(id: 'test-user', name: 'Test User'), - latestReactions: [ - Reaction( - type: 'love', - messageId: 'test-message', - user: User(id: 'user-1', name: 'User 1'), - createdAt: DateTime.now(), - ), - Reaction( - type: 'like', - messageId: 'test-message', - user: User(id: 'user-2', name: 'User 2'), - createdAt: DateTime.now(), - ), - ], - reactionGroups: { - 'love': ReactionGroup(count: 1, sumScores: 1), - 'like': ReactionGroup(count: 1, sumScores: 1), - }, - ); - - late MockClient mockClient; - - setUp(() { - mockClient = MockClient(); - - final mockClientState = MockClientState(); - when(() => mockClient.state).thenReturn(mockClientState); - - // Mock the current user for the message reactions test - final currentUser = OwnUser(id: 'current-user', name: 'Current User'); - when(() => mockClientState.currentUser).thenReturn(currentUser); - }); - - tearDown(() => reset(mockClient)); - - group('StreamMessageReactionsModal', () { - testWidgets( - 'renders message widget and reactions correctly', - (tester) async { - await tester.pumpWidget( - _wrapWithMaterialApp( - client: mockClient, - StreamMessageReactionsModal( - message: message, - messageWidget: const Text('Message Widget'), - ), - ), - ); - - // Use a longer timeout to ensure everything is rendered - await tester.pumpAndSettle(const Duration(seconds: 2)); - expect(find.text('Message Widget'), findsOneWidget); - // Check for reaction picker - expect(find.byType(StreamReactionPicker), findsOneWidget); - // Check for reaction details - expect(find.byType(StreamUserReactions), findsOneWidget); - }, - ); - - testWidgets( - 'calls onUserAvatarTap when user avatar is tapped', - (tester) async { - User? tappedUser; - - // Render the reactions modal and assert avatar tap callback behavior. - await tester.pumpWidget( - _wrapWithMaterialApp( - client: mockClient, - StreamMessageReactionsModal( - message: message, - messageWidget: const Text('Message Widget'), - onUserAvatarTap: (user) { - tappedUser = user; - }, - ), - ), - ); - - await tester.pumpAndSettle(); - - final avatar = find.descendant( - of: find.byType(StreamUserReactions), - matching: find.byType(StreamUserAvatar), - ); - - final avatarTapTarget = find.ancestor( - of: avatar.first, - matching: find.byWidgetPredicate( - (widget) => widget is GestureDetector && widget.child is StreamUserAvatar, - ), - ); - - // Verify the avatar widgets and scoped tap target are rendered. - expect(avatar, findsNWidgets(2)); - expect(avatarTapTarget, findsOneWidget); - - final gestureDetector = tester.widget(avatarTapTarget); - expect(gestureDetector.onTap, isNotNull); - - // Invoke only the tap target that wraps the first avatar. - gestureDetector.onTap!.call(); - await tester.pump(); - - // Verify the callback was called. - expect(tappedUser, isNotNull); - }, - ); - - testWidgets( - 'pops with SelectReaction when reaction is selected', - (tester) async { - MessageAction? messageAction; - - // Define a custom reaction resolver for testing. - const testReactionResolver = _TestReactionIconResolver( - defaultReactionTypes: {'like', 'love', 'camera', 'call'}, - iconByType: { - 'like': Icons.thumb_up, - 'love': Icons.favorite, - 'camera': Icons.camera, - 'call': Icons.call, - }, - ); - - await tester.pumpWidget( - _wrapWithMaterialApp( - client: mockClient, - reactionIconResolver: testReactionResolver, - Builder( - builder: (context) => TextButton( - onPressed: () async { - messageAction = await showStreamDialog( - context: context, - builder: (_) => StreamMessageReactionsModal( - message: message, - messageWidget: const Text('Message Widget'), - ), - ); - }, - child: const Text('Open Dialog'), - ), - ), - ), - ); - - await tester.tap(find.text('Open Dialog')); - - // Use a longer timeout to ensure everything is rendered - await tester.pumpAndSettle(const Duration(seconds: 1)); - - // Verify reaction picker is shown - expect(find.byType(StreamReactionPicker), findsOneWidget); - - // Find and tap the camera reaction - final reactionIconFinder = find.byIcon(Icons.camera); - expect(reactionIconFinder, findsOneWidget); - await tester.tap(reactionIconFinder); - await tester.pumpAndSettle(); - - expect(messageAction, isA()); - // Verify the popped value has correct reaction type - expect((messageAction! as SelectReaction).reaction.type, 'camera'); - - // Open dialog again and tap the call reaction - await tester.tap(find.text('Open Dialog')); - await tester.pumpAndSettle(const Duration(seconds: 1)); - - final callIconFinder = find.byIcon(Icons.call); - expect(callIconFinder, findsOneWidget); - await tester.tap(callIconFinder); - await tester.pumpAndSettle(); - - expect(messageAction, isA()); - // Verify the popped value has correct reaction type - expect((messageAction! as SelectReaction).reaction.type, 'call'); - }, - ); - }); - - group('StreamMessageReactionsModal Golden Tests', () { - Widget buildMessageWidget({bool reverse = false}) { - return Builder( - builder: (context) { - final theme = StreamChatTheme.of(context); - final messageTheme = theme.getMessageTheme(reverse: reverse); - - return Container( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 16, - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(14), - color: messageTheme.messageBackgroundColor, - ), - child: Text( - message.text ?? '', - style: messageTheme.messageTextStyle, - ), - ); - }, - ); - } - - for (final brightness in Brightness.values) { - final theme = brightness.name; - - goldenTest( - 'StreamMessageReactionsModal in $theme theme', - fileName: 'stream_message_reactions_modal_$theme', - constraints: const BoxConstraints(maxWidth: 400, maxHeight: 600), - builder: () => _wrapWithMaterialApp( - client: mockClient, - brightness: brightness, - StreamMessageReactionsModal( - message: message, - messageWidget: buildMessageWidget(), - ), - ), - ); - - goldenTest( - 'StreamMessageReactionsModal reversed in $theme theme', - fileName: 'stream_message_reactions_modal_reversed_$theme', - constraints: const BoxConstraints(maxWidth: 400, maxHeight: 600), - builder: () => _wrapWithMaterialApp( - client: mockClient, - brightness: brightness, - StreamMessageReactionsModal( - message: message, - messageWidget: buildMessageWidget(reverse: true), - reverse: true, - ), - ), - ); - } - }); -} - -Widget _wrapWithMaterialApp( - Widget child, { - required StreamChatClient client, - Brightness? brightness, - ReactionIconResolver? reactionIconResolver, -}) { - return MaterialApp( - debugShowCheckedModeBanner: false, - theme: ThemeData(brightness: brightness), - builder: (context, child) => Portal( - child: StreamChat( - client: client, - // Mock the connectivity stream to always return wifi. - connectivityStream: Stream.value([ConnectivityResult.wifi]), - streamChatThemeData: StreamChatThemeData(brightness: brightness), - streamChatConfigData: StreamChatConfigurationData( - reactionIconResolver: reactionIconResolver ?? const _TestReactionIconResolver(), - ), - child: child, - ), - ), - home: Builder( - builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: ColoredBox( - color: theme.colorTheme.overlay, - child: Padding( - padding: const EdgeInsets.all(8), - child: child, - ), - ), - ); - }, - ), - ); -} - -class _TestReactionIconResolver extends ReactionIconResolver { - const _TestReactionIconResolver({ - this.defaultReactionTypes = const {'like', 'love', 'haha', 'wow', 'sad'}, - this.iconByType = const {}, - }); - - final Set defaultReactionTypes; - final Map iconByType; - - @override - Set get defaultReactions => defaultReactionTypes; - - @override - Set get supportedReactions => { - ...defaultReactionTypes, - ...iconByType.keys, - }; - - @override - String? emojiCode(String type) => streamSupportedEmojis[type]?.emoji; - - @override - Widget resolve(BuildContext context, String type) { - if (iconByType[type] case final icon?) { - return Icon(icon); - } - - if (emojiCode(type) case final emoji?) { - return Text(emoji); - } - - return const Text('❓'); - } -} diff --git a/packages/stream_chat_flutter/test/src/message_widget/deleted_message_test.dart b/packages/stream_chat_flutter/test/src/message_widget/deleted_message_test.dart deleted file mode 100644 index 1768a5cbc3..0000000000 --- a/packages/stream_chat_flutter/test/src/message_widget/deleted_message_test.dart +++ /dev/null @@ -1,214 +0,0 @@ -import 'package:alchemist/alchemist.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -import '../material_app_wrapper.dart'; -import '../mocks.dart'; - -void main() { - testWidgets('control test', (tester) async { - final client = MockClient(); - final clientState = MockClientState(); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - - await tester.pumpWidget( - MaterialApp( - home: StreamChat( - client: client, - child: const Scaffold( - body: StreamDeletedMessage( - messageTheme: StreamMessageThemeData( - createdAtStyle: TextStyle( - color: Colors.black, - ), - messageTextStyle: TextStyle(), - ), - ), - ), - ), - ), - ); - - expect(find.text('Message deleted'), findsOneWidget); - }); - - goldenTest( - 'control golden light', - fileName: 'deleted_message_light', - constraints: const BoxConstraints.tightFor(width: 200, height: 200), - builder: () { - final client = MockClient(); - final clientState = MockClientState(); - final channel = MockChannel(); - final channelState = MockChannelState(); - final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => channel.lastMessageAt).thenReturn(lastMessageAt); - when(() => channel.state).thenReturn(channelState); - when(() => channel.client).thenReturn(client); - when(() => channel.extraDataStream).thenAnswer( - (i) => Stream.value({ - 'name': 'test', - }), - ); - when(() => channel.extraData).thenReturn({ - 'name': 'test', - }); - - when(() => clientState.totalUnreadCount).thenReturn(10); - when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(10)); - - final materialTheme = ThemeData.light( - useMaterial3: false, - ); - final theme = StreamChatThemeData.fromTheme(materialTheme); - return MaterialAppWrapper( - theme: materialTheme, - home: StreamChat( - streamChatThemeData: theme, - client: client, - connectivityStream: Stream.value([ConnectivityResult.mobile]), - child: StreamChannel( - showLoading: false, - channel: channel, - child: Scaffold( - body: Center( - child: StreamDeletedMessage( - messageTheme: theme.ownMessageTheme, - ), - ), - ), - ), - ), - ); - }, - ); - - goldenTest( - 'control golden dark', - fileName: 'deleted_message_dark', - constraints: const BoxConstraints.tightFor(width: 200, height: 200), - builder: () { - final client = MockClient(); - final clientState = MockClientState(); - final channel = MockChannel(); - final channelState = MockChannelState(); - final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => channel.lastMessageAt).thenReturn(lastMessageAt); - when(() => channel.state).thenReturn(channelState); - when(() => channel.client).thenReturn(client); - when(() => channel.extraDataStream).thenAnswer( - (i) => Stream.value({ - 'name': 'test', - }), - ); - when(() => channel.extraData).thenReturn({ - 'name': 'test', - }); - - when(() => clientState.totalUnreadCount).thenReturn(10); - when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(10)); - - final materialTheme = ThemeData.dark( - useMaterial3: false, - ); - final theme = StreamChatThemeData.fromTheme(materialTheme); - return MaterialAppWrapper( - theme: materialTheme, - home: StreamChat( - streamChatThemeData: theme, - client: client, - connectivityStream: Stream.value([ConnectivityResult.mobile]), - child: StreamChannel( - showLoading: false, - channel: channel, - child: Scaffold( - body: Center( - child: StreamDeletedMessage( - messageTheme: theme.ownMessageTheme, - ), - ), - ), - ), - ), - ); - }, - ); - - goldenTest( - 'golden customization test', - fileName: 'deleted_message_custom', - constraints: const BoxConstraints.tightFor(width: 200, height: 200), - builder: () { - final client = MockClient(); - final clientState = MockClientState(); - final channel = MockChannel(); - final channelState = MockChannelState(); - final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => channel.lastMessageAt).thenReturn(lastMessageAt); - when(() => channel.state).thenReturn(channelState); - when(() => channel.client).thenReturn(client); - when(() => channel.extraDataStream).thenAnswer( - (i) => Stream.value({ - 'name': 'test', - }), - ); - when(() => channel.extraData).thenReturn({ - 'name': 'test', - }); - - when(() => clientState.totalUnreadCount).thenReturn(10); - when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(10)); - - final materialTheme = ThemeData.light( - useMaterial3: false, - ); - - var theme = StreamChatThemeData.fromTheme(materialTheme); - theme = theme.copyWith( - ownMessageTheme: theme.ownMessageTheme.copyWith( - messageDeletedStyle: theme.ownMessageTheme.messageTextStyle!.copyWith( - fontWeight: FontWeight.bold, - color: Colors.red, - ), - ), - ); - - return MaterialAppWrapper( - theme: materialTheme, - home: StreamChat( - streamChatThemeData: theme, - client: client, - connectivityStream: Stream.value([ConnectivityResult.mobile]), - child: StreamChannel( - showLoading: false, - channel: channel, - child: Scaffold( - body: Center( - child: StreamDeletedMessage( - messageTheme: theme.ownMessageTheme, - reverse: true, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - ), - ), - ), - ), - ); - }, - ); -} diff --git a/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_custom.png b/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_custom.png deleted file mode 100644 index 3fcf5ac84b8d27d0b28d0e54db74b93eeb4f6888..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 945 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yu@pObhHwBu4M$1`kk42gi^K9h!lVkHA-`qRLZ~4peN3U;Z?%B7m_w|>V1>Dn8CH?)6 z7uHyunRn`r!?s-M__(;^KWk>1vgX~W+b=zTp8WE4YQfhxey$W|uYXmuujg~+Y^(On zMxYgYYHH`2+LS+f`ug>izc02p_$~ilxb@`&>vh*(SJcLix#hp(zn8DCE}vij>-w`p_3uyX@BjDqew_UyIhJ>g4hkGBO?Zhz zTY`P<>gyz*cQZ@>*!Q>2yzbA#%2PKkhfTP9?$Ocs&G+9f>$NqUP$_=4c4p;zAMPg) z6YZ~+&ICF&DRz3HMa=;Rpr_CK_it`{?q_3f+m|}?yTaUEf)+C09vAGG`FZnBLw@su zdW$`?W^dLxT~TGz_xNM8eB9q^yZR3sfk9gKI&aPHtIzo##NBaQRhy94_vEmvTrcNin!rB4S=+#9WRv?QG_vpB)C;b1omK zKKYcDxp+o#oY@0+^VQ-F*=hnDOpOY|gfIx>wVS_x+xPzSp^tyMzw_IETjIFt=2#5h zoxbPz@6hARpI?dIc=+~f>Q}D)&zc+rI9M795Ef@F^|jf4i+At+)6E+0r69n<zopr07+t&DgXcg diff --git a/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_light.png b/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_light.png deleted file mode 100644 index 16071e2b62c404f2e0c0f141f17a127a7ac6d668..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 688 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yu@pObhHwBu4M$1`kk42g+h$J=x7zjsgDjp4gv z_ZU>5RpaSW-L^Y*S`UUPte!$E(RearRJ-n~?Nxk$rtqxRv$##38oubsEe z;Z4czyLRjj2W*Z>Tz_5q-jd?`|rQM z)sfTe|KHxwz;lqvh=u!vfR2JQ5u6);ir!<(!R|iF7Jd8Bhn0G1I%XdMET6{Sj2Tus+Rl3B-?HA_^eOp31C)X@O1TaS?83{1OTw?7!m*g diff --git a/packages/stream_chat_flutter/test/src/message_widget/message_text_test.dart b/packages/stream_chat_flutter/test/src/message_widget/message_text_test.dart deleted file mode 100644 index 47949604af..0000000000 --- a/packages/stream_chat_flutter/test/src/message_widget/message_text_test.dart +++ /dev/null @@ -1,253 +0,0 @@ -import 'package:alchemist/alchemist.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -import '../material_app_wrapper.dart'; -import '../mocks.dart'; -import '../simple_frame.dart'; - -void expectTextStrings(Iterable widgets, List strings) { - var currentString = 0; - for (final widget in widgets) { - if (widget is RichText) { - final span = widget.text as TextSpan; - final text = _extractTextFromTextSpan(span); - expect(text, equals(strings[currentString])); - currentString += 1; - } - } -} - -String _extractTextFromTextSpan(TextSpan span) { - var text = span.text ?? ''; - if (span.children != null) { - for (final child in span.children! as Iterable) { - text += _extractTextFromTextSpan(child); - } - } - return text; -} - -void main() { - testWidgets( - 'it should show correct message text', - (WidgetTester tester) async { - final currentUser = OwnUser(id: 'user-id'); - final client = MockClient(); - final clientState = MockClientState(); - final channel = MockChannel(); - final channelState = MockChannelState(); - final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.fromTheme(themeData); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(currentUser); - when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(currentUser)); - when(() => channel.lastMessageAt).thenReturn(lastMessageAt); - when(() => channel.state).thenReturn(channelState); - when(() => channel.client).thenReturn(client); - when(() => channel.isMuted).thenReturn(false); - when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(() => channel.extraDataStream).thenAnswer( - (i) => Stream.value({ - 'name': 'test', - }), - ); - when(() => channel.extraData).thenReturn({ - 'name': 'test', - }); - - await tester.pumpWidget( - MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamMessageText( - message: Message( - text: 'demo', - ), - messageTheme: streamTheme.otherMessageTheme, - ), - ), - ), - ), - ), - ); - - // wait for the initial state to be rendered. - await tester.pumpAndSettle(); - - expect(find.byType(MarkdownBody), findsOneWidget); - }, - ); - - group('Message with i18n field', () { - final client = MockClient(); - final clientState = MockClientState(); - final channel = MockChannel(); - final channelState = MockChannelState(); - const messageTheme = StreamMessageThemeData(); - - final currentUser = OwnUser( - id: 'sahil', - language: 'hi', - ); - - setUp(() { - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(currentUser); - when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(currentUser)); - - when(() => channel.state).thenReturn(channelState); - when(() => channel.client).thenReturn(client); - when(() => channel.isMuted).thenReturn(false); - when(() => channel.isMutedStream).thenAnswer((_) => Stream.value(false)); - }); - - testWidgets( - 'should show correct translated message text as per user language', - (WidgetTester tester) async { - final message = Message( - text: 'Hello', - i18n: const { - 'en_text': 'Hello', - 'hi_text': 'नमस्ते', - 'language': 'en', - }, - ); - - await tester.pumpWidget( - MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamMessageText( - message: message, - messageTheme: messageTheme, - ), - ), - ), - ), - ), - ); - - // wait for the initial state to be rendered. - await tester.pump(Duration.zero); - - expect(find.byType(MarkdownBody), findsOneWidget); - - final widgets = tester.allWidgets; - expectTextStrings(widgets, ['नमस्ते']); - }, - ); - - testWidgets( - '''should show default text if i18n does not contain translations as per user language''', - (WidgetTester tester) async { - final message = Message( - text: 'Hello', - i18n: const { - 'en_text': 'Hello', - 'fr_text': 'Bonjour', - 'language': 'en', - }, - ); - - await tester.pumpWidget( - MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamMessageText( - message: message, - messageTheme: messageTheme, - ), - ), - ), - ), - ), - ); - - // wait for the initial state to be rendered. - await tester.pump(Duration.zero); - - expect(find.byType(MarkdownBody), findsOneWidget); - - final widgets = tester.allWidgets; - expectTextStrings(widgets, ['Hello']); - }, - ); - }); - - goldenTest( - 'control test', - fileName: 'message_text', - constraints: const BoxConstraints.tightFor(width: 300, height: 200), - builder: () { - final currentUser = OwnUser(id: 'user-id'); - final client = MockClient(); - final clientState = MockClientState(); - final channel = MockChannel(); - final channelState = MockChannelState(); - final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.fromTheme(themeData); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(currentUser); - when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(currentUser)); - when(() => channel.lastMessageAt).thenReturn(lastMessageAt); - when(() => channel.state).thenReturn(channelState); - when(() => channel.client).thenReturn(client); - when(() => channel.isMuted).thenReturn(false); - when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(() => channel.extraDataStream).thenAnswer( - (i) => Stream.value({ - 'name': 'test', - }), - ); - when(() => channel.extraData).thenReturn({ - 'name': 'test', - }); - - const messageText = ''' -a message. -with multiple lines -and a list: -- a. okasd -- b lllll - -cool.'''; - - return MaterialAppWrapper( - home: SimpleFrame( - child: StreamChat( - client: client, - connectivityStream: Stream.value([ConnectivityResult.wifi]), - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamMessageText( - message: Message( - text: messageText, - ), - messageTheme: streamTheme.otherMessageTheme, - ), - ), - ), - ), - ), - ); - }, - ); -} diff --git a/packages/stream_chat_flutter/test/src/message_widget/username_test.dart b/packages/stream_chat_flutter/test/src/message_widget/username_test.dart deleted file mode 100644 index e37f5bfe72..0000000000 --- a/packages/stream_chat_flutter/test/src/message_widget/username_test.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat_flutter/src/message_widget/username.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -void main() { - testWidgets('Username', (tester) async { - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: Center( - child: Username( - message: Message(), - textStyle: StreamChatThemeData.light().ownMessageTheme.messageAuthorStyle!, - ), - ), - ), - ), - ); - - expect(find.byType(Text), findsOneWidget); - }); -} diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_dark.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_dark.png index b0c221516224ab0be605bfebfeb922e71a090b04..a71e6327d6726f87eeb1000cf1b4879cb214f752 100644 GIT binary patch literal 11341 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%0DG1kfE8%)Z~er1@W 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 W2)1?0tEG_l$7CBDbhxA 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 diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_dark.png index 8b3a297331fbbab6fc8f0378379bf01f5d6e7de0..5a7a51af8849ebbf0d6e26b7ebdeae3e44eb051e 100644 GIT binary patch literal 6925 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 literal 5719 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 G<39kp+bE>~ literal 5568 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 Xx_{r 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 diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_light.png index 9b1ebdc7521c7b1bb8d622caeb1ba9a5f0d85e0d..7fb7d88921a11e628582d7f27e4dca3a497743ba 100644 GIT binary patch literal 7290 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 diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_dark.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_dark.png deleted file mode 100644 index 819784cbb0bc9febfc47b4991227c781baa195dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1644 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 QlwS@&xwt!XoPzKD3pn5w1ONa4 diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_selected_dark.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_selected_dark.png deleted file mode 100644 index e9bae86872ed3f4d4ae61ca4a1f99fa210596a0d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1783 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 diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_dark.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_dark.png deleted file mode 100644 index fcca635d0b7ac886079649c09cba4ea446fec97d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1449 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 Kof;hiQ~m+N;zZ*B diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_light.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_light.png deleted file mode 100644 index b9abc15b6cd3dbd07e8eca42a4405088394ad9c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1938 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 Kof;hiQ~m+N;zZ*B diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_test.dart b/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_test.dart deleted file mode 100644 index cf7ea2c28c..0000000000 --- a/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_test.dart +++ /dev/null @@ -1,494 +0,0 @@ -import 'package:alchemist/alchemist.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -void main() { - const resolver = _TestReactionIconResolver(); - - testWidgets( - 'renders with correct message and reaction icons', - (WidgetTester tester) async { - final message = Message( - id: 'test-message', - text: 'Hello world', - user: User(id: 'test-user'), - reactionGroups: { - 'love': ReactionGroup( - count: 3, - sumScores: 3, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - 'thumbsUp': ReactionGroup( - count: 2, - sumScores: 2, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - }, - ); - - await tester.pumpWidget( - _wrapWithMaterialApp( - StreamReactionIndicator( - message: message, - ), - reactionIconResolver: resolver, - ), - ); - - await tester.pumpAndSettle(); - - // Verify the widget renders with correct structure. - expect(find.byType(StreamReactionIndicator), findsOneWidget); - expect(find.byType(StreamEmoji), findsNWidgets(2)); - }, - ); - - testWidgets( - 'triggers onTap callback when tapped', - (WidgetTester tester) async { - final message = Message( - id: 'test-message', - text: 'Hello world', - user: User(id: 'test-user'), - reactionGroups: { - 'love': ReactionGroup( - count: 1, - sumScores: 1, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - }, - ); - - var tapped = false; - - await tester.pumpWidget( - _wrapWithMaterialApp( - StreamReactionIndicator( - message: message, - onTap: () { - tapped = true; - }, - ), - reactionIconResolver: resolver, - ), - ); - - await tester.pumpAndSettle(); - - // Tap the indicator. - await tester.tap(find.byType(InkWell)); - await tester.pump(); - - // Verify the callback was called. - expect(tapped, isTrue); - }, - ); - - testWidgets( - 'renders no emojis when reactionGroups are missing', - (WidgetTester tester) async { - final message = Message( - id: 'test-message', - text: 'Hello world', - user: User(id: 'test-user'), - ); - - await tester.pumpWidget( - _wrapWithMaterialApp( - StreamReactionIndicator( - message: message, - ), - reactionIconResolver: resolver, - ), - ); - - await tester.pumpAndSettle(); - - expect(find.byType(StreamEmoji), findsNothing); - }, - ); - - testWidgets( - 'updates emoji count when reaction groups change', - (WidgetTester tester) async { - final initialMessage = Message( - id: 'test-message', - text: 'Hello world', - user: User(id: 'test-user'), - reactionGroups: { - 'love': ReactionGroup( - count: 1, - sumScores: 1, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - }, - ); - - final updatedMessage = Message( - id: 'test-message', - text: 'Hello world', - user: User(id: 'test-user'), - reactionGroups: { - 'love': ReactionGroup( - count: 1, - sumScores: 1, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - 'like': ReactionGroup( - count: 1, - sumScores: 1, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - 'wow': ReactionGroup( - count: 1, - sumScores: 1, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - }, - ); - - await tester.pumpWidget( - _wrapWithMaterialApp( - StreamReactionIndicator(message: initialMessage), - reactionIconResolver: resolver, - ), - ); - - await tester.pumpAndSettle(); - // Initially only one reaction group is visible. - expect(find.byType(StreamEmoji), findsOneWidget); - - await tester.pumpWidget( - _wrapWithMaterialApp( - StreamReactionIndicator(message: updatedMessage), - reactionIconResolver: resolver, - ), - ); - - await tester.pumpAndSettle(); - // Updated message contains three reaction groups. - expect(find.byType(StreamEmoji), findsNWidgets(3)); - }, - ); - - testWidgets( - 'respects custom reaction sorting', - (WidgetTester tester) async { - final message = Message( - id: 'test-message', - text: 'Hello world', - user: User(id: 'test-user'), - reactionGroups: { - 'love': ReactionGroup( - count: 5, - sumScores: 5, - firstReactionAt: DateTime(2026, 1, 1, 10, 0), - lastReactionAt: DateTime(2026, 1, 1, 10, 0), - ), - 'like': ReactionGroup( - count: 1, - sumScores: 1, - firstReactionAt: DateTime(2026, 1, 1, 9, 0), - lastReactionAt: DateTime(2026, 1, 1, 9, 0), - ), - }, - ); - - await tester.pumpWidget( - _wrapWithMaterialApp( - StreamReactionIndicator( - message: message, - reactionSorting: ReactionSorting.byCount, - ), - reactionIconResolver: resolver, - ), - ); - - await tester.pumpAndSettle(); - - // Validate display order for custom sorting (ascending count). - final rendered = tester.widgetList( - find.byType(StreamEmoji), - ); - - final first = rendered.first.props.emoji as Text; - final second = rendered.elementAt(1).props.emoji as Text; - - expect(first.data, resolver.emojiCode('like')); - expect(second.data, resolver.emojiCode('love')); - }, - ); - - testWidgets( - 'uses custom reaction resolver rendering', - (WidgetTester tester) async { - final message = Message( - id: 'test-message', - text: 'Hello world', - user: User(id: 'test-user'), - reactionGroups: { - 'customParty': ReactionGroup( - count: 1, - sumScores: 1, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - }, - ); - - await tester.pumpWidget( - _wrapWithMaterialApp( - StreamReactionIndicator(message: message), - reactionIconResolver: const _TypeBasedReactionIconResolver(), - ), - ); - - await tester.pumpAndSettle(); - - expect(find.byKey(const Key('custom-type-customParty')), findsOneWidget); - }, - ); - - testWidgets( - 'renders resolver fallback for unsupported reaction type', - (WidgetTester tester) async { - final message = Message( - id: 'test-message', - text: 'Hello world', - user: User(id: 'test-user'), - reactionGroups: { - 'customUnsupported': ReactionGroup( - count: 1, - sumScores: 1, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - }, - ); - - await tester.pumpWidget( - _wrapWithMaterialApp( - StreamReactionIndicator(message: message), - reactionIconResolver: const _StrictReactionIconResolver(), - ), - ); - - await tester.pumpAndSettle(); - - expect(find.byType(StreamEmoji), findsOneWidget); - expect(find.text('❓'), findsOneWidget); - }, - ); - - group('Golden tests', () { - for (final brightness in [Brightness.light, Brightness.dark]) { - final theme = brightness.name; - - goldenTest( - 'StreamReactionIndicator in $theme theme', - fileName: 'stream_reaction_indicator_$theme', - constraints: const BoxConstraints.tightFor(width: 200, height: 60), - builder: () { - final message = Message( - id: 'test-message', - text: 'Hello world', - user: User(id: 'test-user'), - reactionGroups: { - 'love': ReactionGroup( - count: 3, - sumScores: 3, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - 'thumbsUp': ReactionGroup( - count: 2, - sumScores: 2, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - }, - ); - - return _wrapWithMaterialApp( - brightness: brightness, - StreamReactionIndicator( - message: message, - ), - reactionIconResolver: resolver, - ); - }, - ); - - goldenTest( - 'StreamReactionIndicator with own reaction in $theme theme', - fileName: 'stream_reaction_indicator_own_$theme', - constraints: const BoxConstraints.tightFor(width: 200, height: 60), - builder: () { - final message = Message( - id: 'test-message', - text: 'Hello world', - user: User(id: 'test-user'), - reactionGroups: { - 'love': ReactionGroup( - count: 1, - sumScores: 1, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - }, - ownReactions: [ - Reaction( - type: 'love', - messageId: 'test-message', - userId: 'test-user', - ), - ], - ); - - return _wrapWithMaterialApp( - brightness: brightness, - StreamReactionIndicator( - message: message, - ), - reactionIconResolver: resolver, - ); - }, - ); - - goldenTest( - 'StreamReactionIndicator with resolver fallback in $theme theme', - fileName: 'stream_reaction_indicator_fallback_$theme', - constraints: const BoxConstraints.tightFor(width: 200, height: 60), - builder: () { - final message = Message( - id: 'test-message', - text: 'Hello world', - user: User(id: 'test-user'), - reactionGroups: { - 'customUnsupported': ReactionGroup( - count: 1, - sumScores: 1, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - }, - ); - - return _wrapWithMaterialApp( - brightness: brightness, - StreamReactionIndicator( - message: message, - ), - reactionIconResolver: const _StrictReactionIconResolver(), - ); - }, - ); - } - }); -} - -Widget _wrapWithMaterialApp( - Widget child, { - Brightness? brightness, - ReactionIconResolver? reactionIconResolver, -}) { - return MaterialApp( - debugShowCheckedModeBanner: false, - theme: ThemeData(brightness: brightness), - builder: (context, child) => StreamChatConfiguration( - data: StreamChatConfigurationData( - reactionIconResolver: reactionIconResolver ?? const _TestReactionIconResolver(), - ), - child: StreamChatTheme( - data: StreamChatThemeData(brightness: brightness), - child: child ?? const SizedBox.shrink(), - ), - ), - home: Builder( - builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.overlay, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: child, - ), - ), - ); - }, - ), - ); -} - -class _TestReactionIconResolver extends ReactionIconResolver { - const _TestReactionIconResolver(); - - static const _reactionTypes = {'like', 'haha', 'love', 'wow', 'sad'}; - - @override - Set get defaultReactions => _reactionTypes; - - @override - Set get supportedReactions => _reactionTypes; - - @override - String? emojiCode(String type) => streamSupportedEmojis[type]?.emoji; - - @override - Widget resolve(BuildContext context, String type) { - return Text(emojiCode(type) ?? type); - } -} - -class _TypeBasedReactionIconResolver extends ReactionIconResolver { - const _TypeBasedReactionIconResolver(); - - @override - Set get defaultReactions => const {'customParty'}; - - @override - Set get supportedReactions => const {'customParty'}; - - @override - String? emojiCode(String type) => null; - - @override - Widget resolve(BuildContext context, String type) { - return SizedBox.square(key: Key('custom-type-$type')); - } -} - -class _StrictReactionIconResolver extends ReactionIconResolver { - const _StrictReactionIconResolver(); - - @override - Set get defaultReactions => const {'love'}; - - @override - Set get supportedReactions => const {'love'}; - - @override - String? emojiCode(String type) => streamSupportedEmojis[type]?.emoji; - - @override - Widget resolve(BuildContext context, String type) { - if (!supportedReactions.contains(type)) { - return const Text('❓'); - } - - if (emojiCode(type) case final emoji?) { - return Text(emoji); - } - - return const Text('❓'); - } -} diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_test.dart b/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_test.dart index b8aa9c6bc3..bd39c55e6a 100644 --- a/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_test.dart +++ b/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_test.dart @@ -18,7 +18,7 @@ void main() { await tester.pumpWidget( _wrapWithMaterialApp( - StreamReactionPicker( + StreamMessageReactionPicker( message: message, onReactionPicked: (_) {}, ), @@ -29,7 +29,7 @@ void main() { await tester.pumpAndSettle(const Duration(seconds: 1)); // Verify the widget renders with correct structure. - expect(find.byType(StreamReactionPicker), findsOneWidget); + expect(find.byType(StreamMessageReactionPicker), findsOneWidget); // Verify the correct number of reaction buttons. expect( find.byType(StreamEmojiButton), @@ -52,7 +52,7 @@ void main() { await tester.pumpWidget( _wrapWithMaterialApp( - StreamReactionPicker( + StreamMessageReactionPicker( message: message, onReactionPicked: (reaction) { pickedReaction = reaction; @@ -96,7 +96,7 @@ void main() { await tester.pumpWidget( _wrapWithMaterialApp( - StreamReactionPicker( + StreamMessageReactionPicker( message: message, onReactionPicked: (reaction) { pickedReaction = reaction; @@ -134,7 +134,7 @@ void main() { await tester.pumpWidget( _wrapWithMaterialApp( - StreamReactionPicker( + StreamMessageReactionPicker( message: message, onReactionPicked: (_) {}, ), @@ -170,7 +170,7 @@ void main() { await tester.pumpWidget( _wrapWithMaterialApp( - StreamReactionPicker( + StreamMessageReactionPicker( message: message, onReactionPicked: (_) {}, ), @@ -184,7 +184,7 @@ void main() { await tester.pumpWidget( _wrapWithMaterialApp( - StreamReactionPicker( + StreamMessageReactionPicker( message: message, onReactionPicked: (_) {}, ), @@ -214,7 +214,7 @@ void main() { await tester.pumpWidget( _wrapWithMaterialApp( - StreamReactionPicker( + StreamMessageReactionPicker( message: message, onReactionPicked: (_) {}, ), @@ -244,7 +244,7 @@ void main() { await tester.pumpWidget( _wrapWithMaterialApp( - StreamReactionPicker( + StreamMessageReactionPicker( message: message, onReactionPicked: (_) {}, ), @@ -270,7 +270,7 @@ void main() { await tester.pumpWidget( _wrapWithMaterialApp( - StreamReactionPicker( + StreamMessageReactionPicker( message: message, onReactionPicked: (_) {}, ), @@ -289,7 +289,7 @@ void main() { final theme = brightness.name; goldenTest( - 'StreamReactionPicker in $theme theme', + 'StreamMessageReactionPicker in $theme theme', fileName: 'stream_reaction_picker_$theme', constraints: const BoxConstraints.tightFor(width: 400, height: 100), builder: () { @@ -301,7 +301,7 @@ void main() { return _wrapWithMaterialApp( brightness: brightness, - StreamReactionPicker( + StreamMessageReactionPicker( message: message, onReactionPicked: (_) {}, ), @@ -311,7 +311,7 @@ void main() { ); goldenTest( - 'StreamReactionPicker with selected reaction in $theme theme', + 'StreamMessageReactionPicker with selected reaction in $theme theme', fileName: 'stream_reaction_picker_selected_$theme', constraints: const BoxConstraints.tightFor(width: 400, height: 100), builder: () { @@ -330,7 +330,7 @@ void main() { return _wrapWithMaterialApp( brightness: brightness, - StreamReactionPicker( + StreamMessageReactionPicker( message: message, onReactionPicked: (_) {}, ), @@ -340,7 +340,7 @@ void main() { ); goldenTest( - 'StreamReactionPicker with subset defaults in $theme theme', + 'StreamMessageReactionPicker with subset defaults in $theme theme', fileName: 'stream_reaction_picker_subset_$theme', constraints: const BoxConstraints.tightFor(width: 400, height: 100), builder: () { @@ -352,7 +352,7 @@ void main() { return _wrapWithMaterialApp( brightness: brightness, - StreamReactionPicker( + StreamMessageReactionPicker( message: message, onReactionPicked: (_) {}, ), diff --git a/packages/stream_chat_flutter/test/src/reactions/reaction_bubble_overlay_test.dart b/packages/stream_chat_flutter/test/src/reactions/reaction_bubble_overlay_test.dart deleted file mode 100644 index 80eccd5a27..0000000000 --- a/packages/stream_chat_flutter/test/src/reactions/reaction_bubble_overlay_test.dart +++ /dev/null @@ -1,219 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_portal/flutter_portal.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat_flutter/src/reactions/reaction_bubble_overlay.dart'; - -void main() { - testWidgets( - 'returns child directly when not visible', - (tester) async { - await tester.pumpWidget( - const MaterialApp( - home: Portal( - child: Scaffold( - body: ReactionBubbleOverlay( - visible: false, - reaction: Text('reaction'), - child: Text('child'), - ), - ), - ), - ), - ); - - expect(find.text('child'), findsOneWidget); - expect(find.text('reaction'), findsNothing); - expect(find.byType(PortalTarget), findsNothing); - }, - ); - - testWidgets( - 'shows portal target when visible', - (tester) async { - await tester.pumpWidget( - const MaterialApp( - home: Portal( - child: Scaffold( - body: ReactionBubbleOverlay( - visible: true, - reaction: Text('reaction'), - child: Text('child'), - ), - ), - ), - ), - ); - - expect(find.text('child'), findsOneWidget); - expect(find.text('reaction'), findsOneWidget); - expect(find.byType(PortalTarget), findsOneWidget); - }, - ); - - testWidgets( - 'supports custom anchor', - (tester) async { - await tester.pumpWidget( - const MaterialApp( - home: Portal( - child: Scaffold( - body: ReactionBubbleOverlay( - visible: true, - anchor: ReactionBubbleAnchor.topStart(offset: Offset(4, -8)), - reaction: Text('reaction'), - child: Text('child'), - ), - ), - ), - ), - ); - - final portalTarget = tester.widget(find.byType(PortalTarget)); - final anchor = portalTarget.anchor as Aligned; - - expect(anchor.target, Alignment.topLeft); - expect(anchor.follower, Alignment.bottomCenter); - expect(anchor.offset, const Offset(4, -8)); - expect(anchor.shiftToWithinBound.x, isTrue); - expect(anchor.shiftToWithinBound.y, isFalse); - expect(find.text('child'), findsOneWidget); - }, - ); - - testWidgets( - 'forwards payload and callback to custom builder', - (tester) async { - String? capturedPayload; - ValueSetter? capturedCallback; - String? pickedValue; - - await tester.pumpWidget( - MaterialApp( - home: Portal( - child: Scaffold( - body: GenericBubbleOverlay( - payload: 'payload-value', - onPicked: (value) { - pickedValue = value; - }, - reactionBuilder: (context, payload, onPicked) { - capturedPayload = payload; - capturedCallback = onPicked; - return const Text('reaction'); - }, - child: const Text('child'), - ), - ), - ), - ), - ); - - expect(capturedPayload, 'payload-value'); - expect(capturedCallback, isNotNull); - - capturedCallback?.call('picked-value'); - expect(pickedValue, 'picked-value'); - }, - ); - - testWidgets( - 'uses start anchor when reverse is false in LTR', - (tester) async { - await tester.pumpWidget( - const MaterialApp( - home: Portal( - child: Scaffold( - body: GenericBubbleOverlay( - payload: 'payload', - reverse: false, - anchorOffset: Offset(12, -6), - reactionBuilder: _defaultReactionBuilder, - child: Text('child'), - ), - ), - ), - ), - ); - - final portalTarget = tester.widget(find.byType(PortalTarget)); - final anchor = portalTarget.anchor as Aligned; - - expect(anchor.target, Alignment.topLeft); - expect(anchor.follower, Alignment.bottomLeft); - expect(anchor.offset, const Offset(12, -6)); - }, - ); - - testWidgets( - 'uses end anchor when reverse is true in LTR', - (tester) async { - await tester.pumpWidget( - const MaterialApp( - home: Portal( - child: Scaffold( - body: GenericBubbleOverlay( - payload: 'payload', - reverse: true, - anchorOffset: Offset(-3, 9), - reactionBuilder: _defaultReactionBuilder, - child: Text('child'), - ), - ), - ), - ), - ); - - final portalTarget = tester.widget(find.byType(PortalTarget)); - final anchor = portalTarget.anchor as Aligned; - - expect(anchor.target, Alignment.topRight); - expect(anchor.follower, Alignment.bottomRight); - expect(anchor.offset, const Offset(-3, 9)); - }, - ); -} - -typedef GenericReactionBuilder = Widget Function(BuildContext context, String payload, ValueSetter? onPicked); - -class GenericBubbleOverlay extends StatelessWidget { - const GenericBubbleOverlay({ - super.key, - required this.payload, - required this.reactionBuilder, - required this.child, - this.onPicked, - this.visible = true, - this.reverse = false, - this.anchorOffset = Offset.zero, - }); - - final String payload; - final GenericReactionBuilder reactionBuilder; - final ValueSetter? onPicked; - final Widget child; - final bool visible; - final bool reverse; - final Offset anchorOffset; - - @override - Widget build(BuildContext context) { - return ReactionBubbleOverlay( - visible: visible, - anchor: ReactionBubbleAnchor( - offset: anchorOffset, - follower: AlignmentDirectional(reverse ? 1 : -1, 1), - target: AlignmentDirectional(reverse ? 1 : -1, -1), - ), - reaction: reactionBuilder(context, payload, onPicked), - child: child, - ); - } -} - -Widget _defaultReactionBuilder( - BuildContext context, - String payload, - ValueSetter? onPicked, -) { - return const SizedBox.shrink(); -} diff --git a/sample_app/ios/Runner.xcodeproj/project.pbxproj b/sample_app/ios/Runner.xcodeproj/project.pbxproj index 3debb7913b..5543119cb8 100644 --- a/sample_app/ios/Runner.xcodeproj/project.pbxproj +++ b/sample_app/ios/Runner.xcodeproj/project.pbxproj @@ -296,7 +296,7 @@ "${BUILT_PRODUCTS_DIR}/package_info_plus/package_info_plus.framework", "${BUILT_PRODUCTS_DIR}/path_provider_foundation/path_provider_foundation.framework", "${BUILT_PRODUCTS_DIR}/photo_manager/photo_manager.framework", - "${BUILT_PRODUCTS_DIR}/record_darwin/record_darwin.framework", + "${BUILT_PRODUCTS_DIR}/record_ios/record_ios.framework", "${BUILT_PRODUCTS_DIR}/sentry_flutter/sentry_flutter.framework", "${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework", "${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework", @@ -340,7 +340,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_foundation.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/photo_manager.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/record_darwin.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/record_ios.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sentry_flutter.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework", diff --git a/sample_app/lib/app.dart b/sample_app/lib/app.dart index d37248f6a1..d1e0441752 100644 --- a/sample_app/lib/app.dart +++ b/sample_app/lib/app.dart @@ -20,6 +20,9 @@ import 'package:sample_app/state/init_data.dart'; import 'package:sample_app/utils/app_config.dart'; import 'package:sample_app/utils/local_notification_observer.dart'; import 'package:sample_app/utils/localizations.dart'; +import 'package:sample_app/widgets/custom_message_actions.dart'; +import 'package:sample_app/widgets/location/location_attachment.dart'; +import 'package:sample_app/widgets/location/location_detail_dialog.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_localizations/stream_chat_localizations.dart'; @@ -469,66 +472,72 @@ class _StreamChatSampleAppState extends State @override Widget build(BuildContext context) { - return StreamComponentFactory( - builders: StreamComponentBuilders( - extensions: streamChatComponentBuilders( - /// Add your custom component builders here. - ), - ), - child: Stack( - alignment: Alignment.center, - children: [ - if (_initNotifier.initData != null) - ChangeNotifierProvider.value( - value: _initNotifier, - builder: (context, child) => Builder( - builder: (context) { - context.watch(); // rebuild on change - return PreferenceBuilder( - preference: _initNotifier.initData!.preferences.getInt( - 'theme', - defaultValue: 0, + return Stack( + alignment: Alignment.center, + children: [ + if (_initNotifier.initData != null) + ChangeNotifierProvider.value( + value: _initNotifier, + builder: (context, child) => Builder( + builder: (context) { + context.watch(); // rebuild on change + return PreferenceBuilder( + preference: _initNotifier.initData!.preferences.getInt( + 'theme', + defaultValue: 0, + ), + builder: (context, snapshot) => MaterialApp.router( + theme: ThemeData( + brightness: .light, + extensions: [StreamTheme.light()], ), - builder: (context, snapshot) => MaterialApp.router( - theme: ThemeData( - brightness: .light, - extensions: [StreamTheme.light()], - ), - darkTheme: ThemeData( - brightness: .dark, - extensions: [StreamTheme.dark()], - ), - themeMode: const { - -1: ThemeMode.dark, - 0: ThemeMode.system, - 1: ThemeMode.light, - }[snapshot], - supportedLocales: const [ - Locale('en'), - Locale('it'), - ], - localizationsDelegates: const [ - AppLocalizationsDelegate(), - GlobalStreamChatLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ], - builder: (context, child) => StreamChat( - client: _initNotifier.initData!.client, - streamChatConfigData: StreamChatConfigurationData( - draftMessagesEnabled: false, + darkTheme: ThemeData( + brightness: .dark, + extensions: [StreamTheme.dark()], + ), + themeMode: const { + -1: ThemeMode.dark, + 0: ThemeMode.system, + 1: ThemeMode.light, + }[snapshot], + supportedLocales: const [ + Locale('en'), + Locale('it'), + ], + localizationsDelegates: const [ + AppLocalizationsDelegate(), + GlobalStreamChatLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + builder: (context, child) => StreamChat( + client: _initNotifier.initData!.client, + componentBuilders: StreamComponentBuilders( + extensions: streamChatComponentBuilders( + messageWidget: customMessageWidgetBuilder, ), - child: child, ), - routerConfig: _setupRouter(), + streamChatConfigData: StreamChatConfigurationData( + draftMessagesEnabled: false, + attachmentBuilders: [ + LocationAttachmentBuilder( + onAttachmentTap: (context, location) { + showLocationDetailDialog(context: context, location: location); + }, + ), + ], + ), + + child: child, ), - ); - }, - ), + routerConfig: _setupRouter(), + ), + ); + }, ), - if (!animationCompleted) buildAnimation(), - ], - ), + ), + if (!animationCompleted) buildAnimation(), + ], ); } } diff --git a/sample_app/lib/pages/channel_page.dart b/sample_app/lib/pages/channel_page.dart index e66ea7c48b..54f2896c7b 100644 --- a/sample_app/lib/pages/channel_page.dart +++ b/sample_app/lib/pages/channel_page.dart @@ -8,12 +8,8 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:sample_app/pages/thread_page.dart'; import 'package:sample_app/routes/routes.dart'; -import 'package:sample_app/widgets/location/location_attachment.dart'; -import 'package:sample_app/widgets/location/location_detail_dialog.dart'; import 'package:sample_app/widgets/location/location_picker_dialog.dart'; import 'package:sample_app/widgets/location/location_picker_option.dart'; -import 'package:sample_app/widgets/message_info_sheet.dart'; -import 'package:sample_app/widgets/reminder_dialog.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class ChannelPage extends StatefulWidget { @@ -109,9 +105,9 @@ class _ChannelPageState extends State { initialAlignment: widget.initialAlignment, highlightInitialMessage: widget.highlightInitialMessage, onEditMessageTap: _editMessage, - //onMessageSwiped: _reply, + onReplyTap: _reply, messageFilter: defaultFilter, - messageBuilder: customMessageBuilder, + messageBuilder: _messageBuilder, threadBuilder: (_, parentMessage) { return ThreadPage(parent: parentMessage!); }, @@ -209,165 +205,30 @@ class _ChannelPageState extends State { return channel.sendStaticLocation(location: result.coordinates); } - Widget customMessageBuilder( + Widget _messageBuilder( BuildContext context, - MessageDetails details, - List messages, - StreamMessageWidget defaultMessageWidget, + Message message, + StreamMessageWidgetProps defaultProps, ) { - final theme = StreamChatTheme.of(context); - final icons = context.streamIcons; - final textTheme = theme.textTheme; - final colorTheme = theme.colorTheme; + final defaultWidget = StreamMessageWidget.fromProps(props: defaultProps); - final message = details.message; - final reminder = message.reminder; - final channel = StreamChannel.of(context).channel; - final channelConfig = channel.config; - - final currentUser = StreamChat.of(context).currentUser; - final isSentByCurrentUser = message.user?.id == currentUser?.id; - final canDeleteOwnMessage = channel.canDeleteOwnMessage; - - List actionsBuilder( - BuildContext context, - List defaultActions, - ) { - return StreamContextMenuAction.partitioned( - items: [ - ...defaultActions, - if (isSentByCurrentUser && canDeleteOwnMessage) - StreamContextMenuAction.destructive( - label: const Text('Delete Message for Me'), - leading: Icon(icons.trashBin), - onTap: () => _deleteMessageForMe(message), - ), - if (channelConfig?.userMessageReminders == true) ...[ - if (reminder != null) ...[ - StreamContextMenuAction( - label: const Text('Edit Reminder'), - leading: Icon(icons.clock), - onTap: () => _editReminder(message, reminder), - ), - StreamContextMenuAction( - label: const Text('Remove from later'), - leading: Icon(icons.checkmark2), - onTap: () => _removeReminder(message, reminder), - ), - ] else ...[ - StreamContextMenuAction( - label: const Text('Remind me'), - leading: Icon(icons.bellNotification), - onTap: () => _createReminder(message), - ), - StreamContextMenuAction( - label: const Text('Save for later'), - leading: Icon(icons.fileBend), - onTap: () => _createBookmark(message), - ), - ], - ], - if (channelConfig?.deliveryEvents == true) - StreamContextMenuAction( - label: const Text('Message Info'), - leading: Icon(icons.circleInfoTooltip), - onTap: () => _showMessageInfo(message), - ), - ], - ); - } - - final locationAttachmentBuilder = LocationAttachmentBuilder( - onAttachmentTap: (location) => showLocationDetailDialog( - context: context, - location: location, - ), - ); - - final child = Container( - color: reminder != null ? colorTheme.accentPrimary.withOpacity(.1) : null, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (reminder != null) - Align( - alignment: switch (defaultMessageWidget.reverse) { - true => AlignmentDirectional.centerEnd, - false => AlignmentDirectional.centerStart, - }, - child: Padding( - padding: const EdgeInsetsDirectional.fromSTEB(16, 4, 16, 8), - child: Row( - spacing: 4, - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - size: 16, - Icons.bookmark_rounded, - color: colorTheme.accentPrimary, - ), - Text( - 'Saved for later', - style: textTheme.footnote.copyWith( - color: colorTheme.accentPrimary, - ), - ), - ], - ), - ), - ), - defaultMessageWidget.copyWith( - onReplyTap: _reply, - actionsBuilder: actionsBuilder, - showEditMessage: message.sharedLocation == null, - attachmentBuilders: [locationAttachmentBuilder], - onShowMessage: (message, channel) => GoRouter.of(context).goNamed( - Routes.CHANNEL_PAGE.name, - pathParameters: Routes.CHANNEL_PAGE.params(channel), - queryParameters: Routes.CHANNEL_PAGE.queryParams(message), - ), - bottomRowBuilderWithDefaultWidget: (_, __, defaultWidget) { - return defaultWidget.copyWith( - deletedBottomRowBuilder: (context, message) { - return const StreamVisibleFootnote(); - }, - ); - }, - ), - // If the message has a reminder, add some space below it. - if (reminder != null) const SizedBox(height: 4), - ], - ), - ); + if (message.isDeleted || message.state.isFailed) return defaultWidget; - // We do not support quoting deleted messages. - if (message.isDeleted || message.state.isFailed) return child; + final alignment = StreamMessagePlacement.alignmentDirectionalOf(context); + final isEnd = alignment == AlignmentDirectional.centerEnd; - // The threshold after which the message is considered swiped. const threshold = 0.2; - final isMyMessage = details.isMyMessage; - // The direction in which the message can be swiped. - final swipeDirection = details.isMyMessage ? SwipeDirection.endToStart : SwipeDirection.startToEnd; - return Swipeable( - key: ValueKey(details.message.id), - direction: swipeDirection, + key: ValueKey(message.id), + direction: isEnd ? SwipeDirection.endToStart : SwipeDirection.startToEnd, swipeThreshold: threshold, - onSwiped: (_) => _reply(details.message), + onSwiped: (_) => _reply(message), backgroundBuilder: (context, details) { - // The alignment of the swipe action. - final alignment = isMyMessage ? AlignmentDirectional.centerEnd : AlignmentDirectional.centerStart; - - // The progress of the swipe action. final progress = math.min(details.progress, threshold) / threshold; - // The offset for the reply icon. var offset = Offset.lerp(const Offset(-24, 0), const Offset(12, 0), progress)!; - - // If the message is mine, we need to flip the offset. - if (isMyMessage) offset = Offset(-offset.dx, -offset.dy); + if (isEnd) offset = Offset(-offset.dx, -offset.dy); return Align( alignment: alignment, @@ -395,80 +256,8 @@ class _ChannelPageState extends State { ), ); }, - child: child, - ); - } - - Future _editReminder( - Message message, - MessageReminder reminder, - ) async { - final option = await showDialog( - context: context, - builder: (_) => EditReminderDialog( - isBookmarkReminder: reminder.remindAt == null, - ), + child: defaultWidget, ); - - if (option == null) return; - final client = StreamChat.of(context).client; - final messageId = message.id; - final remindAt = option.remindAt; - - return client.updateReminder(messageId, remindAt: remindAt).ignore(); - } - - Future _removeReminder( - Message message, - MessageReminder reminder, - ) async { - final client = StreamChat.of(context).client; - final messageId = message.id; - - return client.deleteReminder(messageId).ignore(); - } - - Future _createReminder(Message message) async { - final reminder = await showDialog( - context: context, - builder: (_) => const CreateReminderDialog(), - ); - - if (reminder == null) return; - final client = StreamChat.of(context).client; - final messageId = message.id; - final remindAt = reminder.remindAt; - - return client.createReminder(messageId, remindAt: remindAt).ignore(); - } - - Future _createBookmark(Message message) async { - final client = StreamChat.of(context).client; - final messageId = message.id; - - return client.createReminder(messageId).ignore(); - } - - Future _deleteMessageForMe(Message message) async { - final confirmDelete = await showStreamDialog( - context: context, - builder: (context) => const StreamMessageActionConfirmationModal( - isDestructiveAction: true, - title: Text('Delete for me'), - content: Text('Are you sure you want to delete this message for you?'), - cancelActionTitle: Text('Cancel'), - confirmActionTitle: Text('Delete'), - ), - ); - - if (confirmDelete != true) return; - - final channel = StreamChannel.of(context).channel; - return channel.deleteMessageForMe(message).ignore(); - } - - Future _showMessageInfo(Message message) async { - return MessageInfoSheet.show(context: context, message: message); } bool defaultFilter(Message m) { diff --git a/sample_app/lib/pages/thread_page.dart b/sample_app/lib/pages/thread_page.dart index c43f9c7b50..197fb72152 100644 --- a/sample_app/lib/pages/thread_page.dart +++ b/sample_app/lib/pages/thread_page.dart @@ -1,6 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:sample_app/widgets/location/location_attachment.dart'; -import 'package:sample_app/widgets/location/location_detail_dialog.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class ThreadPage extends StatefulWidget { @@ -45,13 +43,6 @@ class _ThreadPageState extends State { @override Widget build(BuildContext context) { - final locationAttachmentBuilder = LocationAttachmentBuilder( - onAttachmentTap: (location) => showLocationDetailDialog( - context: context, - location: location, - ), - ); - return Scaffold( backgroundColor: StreamChatTheme.of(context).colorTheme.appBg, appBar: StreamThreadHeader(parent: widget.parent), @@ -62,36 +53,10 @@ class _ThreadPageState extends State { parentMessage: widget.parent, initialScrollIndex: widget.initialScrollIndex, initialAlignment: widget.initialAlignment, - //onMessageSwiped: _reply, + onReplyTap: _reply, messageFilter: defaultFilter, showScrollToBottom: false, highlightInitialMessage: true, - parentMessageBuilder: (context, message, defaultMessage) { - return defaultMessage.copyWith( - attachmentBuilders: [locationAttachmentBuilder], - ); - }, - messageBuilder: (context, details, messages, defaultMessage) { - final message = details.message; - - return defaultMessage.copyWith( - onReplyTap: _reply, - showEditMessage: message.sharedLocation == null, - attachmentBuilders: [locationAttachmentBuilder], - bottomRowBuilderWithDefaultWidget: - ( - context, - message, - defaultWidget, - ) { - return defaultWidget.copyWith( - deletedBottomRowBuilder: (context, message) { - return const StreamVisibleFootnote(); - }, - ); - }, - ); - }, ), ), if (widget.parent.type != 'deleted') diff --git a/sample_app/lib/widgets/custom_message_actions.dart b/sample_app/lib/widgets/custom_message_actions.dart new file mode 100644 index 0000000000..44197a3ec1 --- /dev/null +++ b/sample_app/lib/widgets/custom_message_actions.dart @@ -0,0 +1,196 @@ +import 'package:flutter/material.dart'; +import 'package:sample_app/widgets/message_info_sheet.dart'; +import 'package:sample_app/widgets/reminder_dialog.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Custom [StreamComponentBuilder] for [StreamMessageWidgetProps] that +/// composes app-specific message action customizations via a delegation +/// chain. +/// +/// Delegation chain: +/// ``` +/// customMessageWidgetBuilder +/// → _ReminderActions (remind me, save for later, edit/remove reminder) +/// → _DeleteForMeAction (delete message for current user only) +/// → _MessageInfoAction (show message delivery info sheet) +/// ``` +Widget customMessageWidgetBuilder( + BuildContext context, + StreamMessageWidgetProps props, +) { + return DefaultStreamMessage( + props: props.copyWith( + actionsBuilder: (context, defaultActions) { + final message = props.message; + return StreamContextMenuAction.partitioned( + items: [ + ...defaultActions, + ..._ReminderActions.build(context, message), + ..._DeleteForMeAction.build(context, message), + ..._MessageInfoAction.build(context, message), + ], + ); + }, + ), + ); +} + +// --------------------------------------------------------------------------- +// Reminder actions +// --------------------------------------------------------------------------- + +abstract final class _ReminderActions { + static List build( + BuildContext context, + Message message, + ) { + final icons = context.streamIcons; + final channel = StreamChannel.of(context).channel; + final channelConfig = channel.config; + if (channelConfig?.userMessageReminders != true) return const []; + + final reminder = message.reminder; + if (reminder != null) { + return [ + StreamContextMenuAction( + label: const Text('Edit Reminder'), + leading: Icon(icons.clock), + onTap: () => _editReminder(context, message, reminder), + ), + StreamContextMenuAction( + label: const Text('Remove from later'), + leading: Icon(icons.checkmark2), + onTap: () => _removeReminder(context, message), + ), + ]; + } + + return [ + StreamContextMenuAction( + label: const Text('Remind me'), + leading: Icon(icons.bellNotification), + onTap: () => _createReminder(context, message), + ), + StreamContextMenuAction( + label: const Text('Save for later'), + leading: Icon(icons.fileBend), + onTap: () => _createBookmark(context, message), + ), + ]; + } + + static Future _editReminder( + BuildContext context, + Message message, + MessageReminder reminder, + ) async { + final option = await showDialog( + context: context, + builder: (_) => EditReminderDialog( + isBookmarkReminder: reminder.remindAt == null, + ), + ); + + if (option == null) return; + final client = StreamChat.of(context).client; + return client.updateReminder(message.id, remindAt: option.remindAt).ignore(); + } + + static Future _removeReminder( + BuildContext context, + Message message, + ) async { + final client = StreamChat.of(context).client; + return client.deleteReminder(message.id).ignore(); + } + + static Future _createReminder( + BuildContext context, + Message message, + ) async { + final reminder = await showDialog( + context: context, + builder: (_) => const CreateReminderDialog(), + ); + + if (reminder == null) return; + final client = StreamChat.of(context).client; + return client.createReminder(message.id, remindAt: reminder.remindAt).ignore(); + } + + static Future _createBookmark( + BuildContext context, + Message message, + ) async { + final client = StreamChat.of(context).client; + return client.createReminder(message.id).ignore(); + } +} + +// --------------------------------------------------------------------------- +// Delete-for-me action +// --------------------------------------------------------------------------- + +abstract final class _DeleteForMeAction { + static List build( + BuildContext context, + Message message, + ) { + final icons = context.streamIcons; + final channel = StreamChannel.of(context).channel; + final currentUser = StreamChat.of(context).currentUser; + final isSentByCurrentUser = message.user?.id == currentUser?.id; + if (!isSentByCurrentUser || !channel.canDeleteOwnMessage) return const []; + + return [ + StreamContextMenuAction.destructive( + label: const Text('Delete Message for Me'), + leading: Icon(icons.trashBin), + onTap: () => _confirmAndDelete(context, message), + ), + ]; + } + + static Future _confirmAndDelete( + BuildContext context, + Message message, + ) async { + final confirmed = await showStreamDialog( + context: context, + builder: (context) => const StreamMessageActionConfirmationModal( + isDestructiveAction: true, + title: Text('Delete for me'), + content: Text('Are you sure you want to delete this message for you?'), + cancelActionTitle: Text('Cancel'), + confirmActionTitle: Text('Delete'), + ), + ); + + if (confirmed != true) return; + final channel = StreamChannel.of(context).channel; + return channel.deleteMessageForMe(message).ignore(); + } +} + +// --------------------------------------------------------------------------- +// Message info action +// --------------------------------------------------------------------------- + +abstract final class _MessageInfoAction { + static List build( + BuildContext context, + Message message, + ) { + final icons = context.streamIcons; + final channel = StreamChannel.of(context).channel; + if (channel.config?.deliveryEvents != true) return const []; + + return [ + StreamContextMenuAction( + label: const Text('Message Info'), + leading: Icon(icons.circleInfoTooltip), + onTap: () => MessageInfoSheet.show(context: context, message: message), + ), + ]; + } +} diff --git a/sample_app/lib/widgets/location/location_attachment.dart b/sample_app/lib/widgets/location/location_attachment.dart index 5a7038ddd5..979e878aa2 100644 --- a/sample_app/lib/widgets/location/location_attachment.dart +++ b/sample_app/lib/widgets/location/location_attachment.dart @@ -15,7 +15,7 @@ class LocationAttachmentBuilder extends StreamAttachmentWidgetBuilder { /// {@macro locationAttachmentBuilder} const LocationAttachmentBuilder({ this.constraints = _defaultLocationConstraints, - this.padding = const EdgeInsets.all(4), + this.padding = const .symmetric(horizontal: 8), this.onAttachmentTap, }); @@ -26,7 +26,11 @@ class LocationAttachmentBuilder extends StreamAttachmentWidgetBuilder { final EdgeInsetsGeometry padding; /// Optional callback to handle tap events on the attachment. - final ValueSetter? onAttachmentTap; + /// + /// Receives the [BuildContext] from the widget tree where the attachment + /// is rendered, along with the [Location] data. This allows showing + /// dialogs or navigating from the correct context. + final void Function(BuildContext context, Location location)? onAttachmentTap; @override bool canHandle(Message message, _) => message.sharedLocation != null; @@ -47,7 +51,7 @@ class LocationAttachmentBuilder extends StreamAttachmentWidgetBuilder { constraints: constraints, padding: padding, onLocationTap: switch (onAttachmentTap) { - final onTap? => () => onTap(location), + final onTap? => () => onTap(context, location), _ => null, }, ); @@ -62,7 +66,7 @@ class LocationAttachment extends StatelessWidget { required this.user, required this.sharedLocation, this.constraints = _defaultLocationConstraints, - this.padding = const EdgeInsets.all(2), + this.padding = const .symmetric(horizontal: 8), this.onLocationTap, }); From 1acb695f73fad3eac30d9e4c9419081a2aa90a51 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 18 Mar 2026 14:58:21 +0100 Subject: [PATCH 29/33] feat(UI): show command chip (#2541) Co-authored-by: renefloor <15101411+renefloor@users.noreply.github.com> Co-authored-by: Sahil Kumar --- .../message_composer/message_composer_leading.dart | 7 ++++--- .../message_composer/stream_chat_message_composer.dart | 2 ++ .../lib/src/stream_message_input_controller.dart | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart index 942100e1c6..26114de383 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart @@ -32,17 +32,18 @@ class _DefaultStreamMessageComposerLeading extends StatelessWidget { Widget build(BuildContext context) { // 45 degrees = 0.125 turns const closedRotation = 0.125; + final showButton = !props.isAudioRecordingFlowActive && props.controller.message.command == null; return AnimatedOpacity( - opacity: props.isAudioRecordingFlowActive ? 0.0 : 1.0, - duration: props.isAudioRecordingFlowActive ? Duration.zero : const Duration(milliseconds: 200), + opacity: showButton ? 1.0 : 0.0, + duration: showButton ? const Duration(milliseconds: 200) : Duration.zero, curve: Curves.easeInQuint, child: AnimatedSize( duration: const Duration(milliseconds: 200), alignment: Alignment.bottomCenter, child: Row( children: [ - if (!props.isAudioRecordingFlowActive) ...[ + if (showButton) ...[ AnimatedRotation( turns: props.isPickerOpen ? closedRotation : 0, duration: const Duration(milliseconds: 150), diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart index 00eb5cb53c..cb976d2df3 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart @@ -307,6 +307,8 @@ class DefaultStreamChatMessageComposer extends StatelessWidget { controller: inputController.textFieldController, placeholder: props.placeholder, focusNode: props.focusNode, + command: inputController.message.command?.toUpperCase(), + onDismissCommand: inputController.clear, ), if (props.canAlsoSendToChannel) DmCheckboxListTile( diff --git a/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart index a9b121914d..78576690da 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart @@ -182,7 +182,7 @@ class StreamMessageInputController extends ValueNotifier { } /// Sets a command for the message. - set command(String command) { + set command(String? command) { // Setting the command should also clear the text and attachments. message = message.copyWith( text: '', From a3a95180037ef8b95252903935ff697f1862b585 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Thu, 19 Mar 2026 12:41:23 +0100 Subject: [PATCH 30/33] fix crossSmall to medium (#2551) --- .../lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart b/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart index 6e6aa09763..042f0f8408 100644 --- a/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart @@ -157,7 +157,7 @@ class StreamChannelInfoBottomSheet extends StatelessWidget { leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Icon( - context.streamIcons.crossSmall, + context.streamIcons.crossMedium, color: colorTheme.textLowEmphasis, ), ), From 64253d7bf98dafc6a8dd8e8e7b4cd44b83b8dccb Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Thu, 19 Mar 2026 12:56:11 +0100 Subject: [PATCH 31/33] improvements on attachment in quote (#2538) --- melos.yaml | 2 +- .../message_composer_input_header.dart | 34 +++++++++++++++++-- packages/stream_chat_flutter/pubspec.yaml | 2 +- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/melos.yaml b/melos.yaml index fde910433b..7dc48f1206 100644 --- a/melos.yaml +++ b/melos.yaml @@ -95,7 +95,7 @@ command: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: c7a31449e8632ea43f8c769be95a30ef9393a792 + ref: 8057a775c2ed764dbd5cbabd2dd60d3cd68d2f08 path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart index 4f4a5e97db..257985c7df 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart @@ -176,24 +176,54 @@ class _QuotedMessageInHeader extends StatelessWidget { if (attachments.isEmpty || attachments.length > 1) return null; final attachment = attachments.first; + if (attachment.type == AttachmentType.file) return null; final imageUrl = attachment.imageUrl ?? attachment.thumbUrl ?? attachment.assetUrl; if (imageUrl == null) return null; return CachedNetworkImageProvider(imageUrl); } + String? _mimeTypeAttachment(Message message) { + final attachments = message.attachments; + if (attachments.isEmpty) return null; + final attachment = attachments.first; + + if (attachment.type != AttachmentType.file) return null; + if (attachments.any((it) => it.mimeType != attachment.mimeType)) return null; + + return attachment.mimeType; + } + @override Widget build(BuildContext context) { final isIncoming = currentUserId != quotedMessage.user?.id; + final image = _imageProvider(quotedMessage); + final mimeType = _mimeTypeAttachment(quotedMessage); + + Widget? trailing; + if (image != null) { + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(context.streamRadius.md), + image: DecorationImage(image: image, fit: BoxFit.cover), + ), + ); + } else if (mimeType != null) { + trailing = StreamFileTypeIcon.fromMimeType(mimeType: mimeType); + } else { + trailing = null; + } + return - // TODO: improve attachment and add trailing to the component instead. // TODO: localize strings MessageComposerReplyAttachment( title: Text(isIncoming ? 'Reply to ${quotedMessage.user?.name}' : 'You'), subtitle: StreamMessagePreviewText(message: quotedMessage), onRemovePressed: onRemovePressed, - image: _imageProvider(quotedMessage), + trailing: trailing, style: isIncoming ? .incoming : .outgoing, ); } diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index bc6c041c9b..779f8109e6 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: c7a31449e8632ea43f8c769be95a30ef9393a792 + ref: 8057a775c2ed764dbd5cbabd2dd60d3cd68d2f08 path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 From a0e886ba905066d6dbf62ab9839cbf511b2b3b7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Bra=C5=BCewicz?= Date: Thu, 19 Mar 2026 13:12:15 +0100 Subject: [PATCH 32/33] refactor(ui): unread indicator redesign (#2548) * unread indicator redesign * tweak --- .../stream_chat_component_builders.dart | 2 + .../message_list_view/message_list_view.dart | 5 +- .../unread_indicator_button.dart | 113 +++++++++++++----- .../lib/stream_chat_flutter.dart | 1 + 4 files changed, 86 insertions(+), 35 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart b/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart index a692917a5f..08c065e4e3 100644 --- a/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart +++ b/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart @@ -12,6 +12,7 @@ Iterable> streamChatComponentBuilders({ StreamComponentBuilder? messageComposerInputHeader, StreamComponentBuilder? messageComposerInputTrailing, StreamComponentBuilder? messageWidget, + StreamComponentBuilder? unreadIndicator, }) { final builders = [ if (channelListItem != null) StreamComponentBuilderExtension(builder: channelListItem), @@ -23,6 +24,7 @@ Iterable> streamChatComponentBuilders({ if (messageComposerInputHeader != null) StreamComponentBuilderExtension(builder: messageComposerInputHeader), if (messageComposerInputTrailing != null) StreamComponentBuilderExtension(builder: messageComposerInputTrailing), if (messageWidget != null) StreamComponentBuilderExtension(builder: messageWidget), + if (unreadIndicator != null) StreamComponentBuilderExtension(builder: unreadIndicator), ]; return builders; diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart index 51cc86cf6e..fdf1717e35 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart @@ -9,7 +9,6 @@ import 'package:stream_chat_flutter/src/message_list_view/floating_date_divider. import 'package:stream_chat_flutter/src/message_list_view/loading_indicator.dart'; import 'package:stream_chat_flutter/src/message_list_view/mlv_utils.dart'; import 'package:stream_chat_flutter/src/message_list_view/thread_separator.dart'; -import 'package:stream_chat_flutter/src/message_list_view/unread_indicator_button.dart'; import 'package:stream_chat_flutter/src/message_list_view/unread_messages_separator.dart'; import 'package:stream_chat_flutter/src/message_widget/ephemeral_message.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; @@ -1115,8 +1114,8 @@ class _StreamMessageListViewState extends State { ); return Positioned( - bottom: 8, - right: 8, + bottom: 16, + right: 16, width: 40, height: 40, child: Stack( diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/unread_indicator_button.dart b/packages/stream_chat_flutter/lib/src/message_list_view/unread_indicator_button.dart index 5d8c0c8c36..b3e76170eb 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/unread_indicator_button.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/unread_indicator_button.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/components/stream_chat_component_builders.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart'; @@ -24,6 +24,32 @@ typedef UnreadIndicatorBuilder = OnUnreadIndicatorDismissTap onDismissTap, ); +/// Properties for configuring an [UnreadIndicatorButton]. +/// +/// This class holds all the configuration options for an unread indicator, +/// allowing them to be passed through the [StreamComponentFactory]. +/// +/// See also: +/// +/// * [UnreadIndicatorButton], which uses these properties. +class UnreadIndicatorProps { + /// Creates properties for an unread indicator. + const UnreadIndicatorProps({ + required this.unreadCount, + required this.onTap, + required this.onDismissTap, + }); + + /// The number of unread messages. + final int unreadCount; + + /// Callback triggered when the indicator is tapped. + final OnUnreadIndicatorTap onTap; + + /// Callback triggered when the dismiss button is tapped. + final OnUnreadIndicatorDismissTap onDismissTap; +} + /// {@template unreadIndicatorButton} /// A button that displays the number of unread messages in a channel. /// @@ -52,7 +78,8 @@ class UnreadIndicatorButton extends StatelessWidget { /// Optional builder for customizing the appearance of the unread indicator. /// - /// If not provided, a default indicator will be built. + /// If not provided, falls back to [StreamComponentFactory], then to the + /// default indicator. final UnreadIndicatorBuilder? unreadIndicatorBuilder; @override @@ -67,46 +94,68 @@ class UnreadIndicatorButton extends StatelessWidget { final unreadCount = currentUserRead.unreadMessages; if (unreadCount <= 0) return const Empty(); + final props = UnreadIndicatorProps( + unreadCount: unreadCount, + onTap: onTap, + onDismissTap: onDismissTap, + ); + if (unreadIndicatorBuilder case final builder?) { return builder(unreadCount, onTap, onDismissTap); } - final theme = StreamChatTheme.of(context); - final textTheme = theme.textTheme; - final colorTheme = theme.colorTheme; + final factoryBuilder = context.chatComponentBuilder(); + if (factoryBuilder != null) return factoryBuilder(context, props); + + final colorTheme = context.streamColorScheme; + final textTheme = context.streamTextTheme; return Material( - elevation: 4, + elevation: 3, clipBehavior: Clip.antiAlias, - color: colorTheme.textLowEmphasis, + color: colorTheme.backgroundElevation1, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(18), + borderRadius: BorderRadiusDirectional.all(context.streamRadius.max), ), - child: InkWell( - onTap: () => onTap(currentUserRead.lastReadMessageId), - child: Padding( - padding: const EdgeInsets.fromLTRB(16, 2, 8, 2), - child: Row( - children: [ - Text( - context.translations.unreadCountIndicatorLabel( - unreadCount: unreadCount, - ), - style: textTheme.body.copyWith(color: colorTheme.barsBg), - ), - const SizedBox(width: 12), - IconButton( - iconSize: 24, - icon: Icon(context.streamIcons.crossMedium), - padding: const EdgeInsets.all(4), - style: IconButton.styleFrom( - foregroundColor: colorTheme.barsBg, - minimumSize: const Size.square(24), - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - onPressed: onDismissTap, + child: SizedBox( + height: 40, + child: InkWell( + onTap: () => onTap(currentUserRead.lastReadMessageId), + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 2, 8, 2), + child: IntrinsicHeight( + child: Row( + children: [ + Icon( + context.streamIcons.arrowUp, + size: 20, + ), + SizedBox(width: context.streamSpacing.xs), + Text( + context.translations.unreadCountIndicatorLabel( + unreadCount: unreadCount, + ), + style: textTheme.bodyEmphasis.copyWith(color: colorTheme.textSecondary), + ), + SizedBox(width: context.streamSpacing.md), + VerticalDivider( + color: colorTheme.borderDefault, + thickness: 1, + ), + IconButton( + iconSize: 20, + icon: Icon(context.streamIcons.crossMedium), + padding: const EdgeInsets.all(5), + style: IconButton.styleFrom( + foregroundColor: colorTheme.textSecondary, + minimumSize: const Size.square(20), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + onPressed: onDismissTap, + ), + ], ), - ], + ), ), ), ), diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index b5b8696cfd..f44bc63231 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -108,6 +108,7 @@ export 'src/message_input/stream_message_send_button.dart'; export 'src/message_input/stream_message_text_field.dart'; export 'src/message_list_view/message_details.dart'; export 'src/message_list_view/message_list_view.dart'; +export 'src/message_list_view/unread_indicator_button.dart'; export 'src/message_modal/message_action_confirmation_modal.dart'; export 'src/message_modal/message_actions_modal.dart'; export 'src/message_modal/message_modal.dart'; From 8000c79d3359335f5df26b45e68e2017ffd58cd8 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Thu, 19 Mar 2026 15:43:41 +0100 Subject: [PATCH 33/33] Add inline add more tile for gallery permissions (#2553) * Add inline add more tile for gallery permissions * Add unit test * minor improvements --- .../lib/src/localization/translations.dart | 2 +- .../options/stream_gallery_picker.dart | 63 ++++++-- .../stream_attachment_picker.dart | 78 +--------- .../photo_gallery/stream_photo_gallery.dart | 7 + .../lib/src/paged_value_scroll_view.dart | 23 ++- .../test/paged_value_grid_view_test.dart | 147 ++++++++++++++++++ .../example/lib/add_new_lang.dart | 2 +- .../lib/src/stream_chat_localizations_ca.dart | 2 +- .../lib/src/stream_chat_localizations_de.dart | 2 +- .../lib/src/stream_chat_localizations_en.dart | 2 +- .../lib/src/stream_chat_localizations_es.dart | 2 +- .../lib/src/stream_chat_localizations_fr.dart | 2 +- .../lib/src/stream_chat_localizations_hi.dart | 2 +- .../lib/src/stream_chat_localizations_it.dart | 2 +- .../lib/src/stream_chat_localizations_ja.dart | 2 +- .../lib/src/stream_chat_localizations_ko.dart | 2 +- .../lib/src/stream_chat_localizations_no.dart | 2 +- .../lib/src/stream_chat_localizations_pt.dart | 2 +- 18 files changed, 240 insertions(+), 104 deletions(-) create mode 100644 packages/stream_chat_flutter_core/test/paged_value_grid_view_test.dart diff --git a/packages/stream_chat_flutter/lib/src/localization/translations.dart b/packages/stream_chat_flutter/lib/src/localization/translations.dart index 2ed97aa956..33203c4439 100644 --- a/packages/stream_chat_flutter/lib/src/localization/translations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/translations.dart @@ -734,7 +734,7 @@ class DefaultTranslations implements Translations { String get somethingWentWrongError => 'Something went wrong'; @override - String get addMoreFilesLabel => 'Add more files'; + String get addMoreFilesLabel => 'Add more'; @override String get enablePhotoAndVideoAccessMessage => diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart index aab1050944..b5a4e3cd48 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart @@ -92,17 +92,6 @@ class _StreamGalleryPickerState extends State { return OptionDrawer( margin: .zero, - actions: [ - if (isLimited) - IconButton( - color: colorScheme.accentPrimary, - icon: const Icon(Icons.add_circle_outline_rounded), - onPressed: () async { - await PhotoManager.presentLimited(); - _controller.doInitialLoad(); - }, - ), - ], child: Builder( builder: (context) { if (!isPermissionGranted) { @@ -143,6 +132,14 @@ class _StreamGalleryPickerState extends State { thumbnailFormat: widget.config.mediaThumbnailFormat, thumbnailQuality: widget.config.mediaThumbnailQuality, thumbnailScale: widget.config.mediaThumbnailScale, + addMoreBuilder: isLimited + ? (context) => _AddMoreTile( + onTap: () async { + await PhotoManager.presentLimited(); + _controller.doInitialLoad(); + }, + ) + : null, itemBuilder: (context, mediaItems, index, defaultWidget) { final media = mediaItems[index]; return defaultWidget.copyWith( @@ -158,6 +155,50 @@ class _StreamGalleryPickerState extends State { } } +class _AddMoreTile extends StatelessWidget { + const _AddMoreTile({required this.onTap}); + + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + final colorScheme = context.streamColorScheme; + final textTheme = context.streamTextTheme; + final spacing = context.streamSpacing; + + return Material( + color: colorScheme.backgroundSurfaceCard, + child: InkWell( + onTap: onTap, + overlayColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.pressed)) return colorScheme.statePressed; + if (states.contains(WidgetState.hovered)) return colorScheme.stateHover; + if (states.contains(WidgetState.focused)) return colorScheme.stateFocused; + return null; + }), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + context.streamIcons.plusLarge, + size: 20, + color: colorScheme.textTertiary, + ), + SizedBox(height: spacing.xs), + Text( + context.translations.addMoreFilesLabel, + style: textTheme.captionEmphasis.copyWith( + color: colorScheme.textTertiary, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + } +} + /// Configuration for the [StreamGalleryPicker]. class GalleryPickerConfig { /// Creates a [GalleryPickerConfig] instance. diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart index aec6681760..365fe48296 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart @@ -267,13 +267,6 @@ class _EndOfFrameCallbackWidgetState extends State { } } -const _kDefaultOptionDrawerShape = RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - ), -); - /// A widget that will be shown in the attachment picker. /// It can be used to show a custom view for each attachment picker option. class OptionDrawer extends StatelessWidget { @@ -281,88 +274,23 @@ class OptionDrawer extends StatelessWidget { const OptionDrawer({ super.key, required this.child, - this.color, - this.elevation = 2, this.margin, - this.clipBehavior = Clip.hardEdge, - this.shape = _kDefaultOptionDrawerShape, - this.title, - this.actions = const [], }); /// The widget below this widget in the tree. final Widget child; - /// The background color of the options card. - /// - /// Defaults to [StreamColorTheme.barsBg]. - final Color? color; - - /// The elevation of the options card. - /// - /// The default value is 2. - final double elevation; - /// The margin of the options card. final EdgeInsetsGeometry? margin; - /// The clip behavior of the options card. - /// - /// The default value is [Clip.hardEdge]. - final Clip clipBehavior; - - /// The shape of the options card. - final ShapeBorder shape; - - /// The title of the options card. - final Widget? title; - - /// The actions available for the options card. - final List actions; - @override Widget build(BuildContext context) { - var height = 0.0; - if (title != null || actions.isNotEmpty) { - height = 40.0; - } - - final leading = title ?? const Empty(); - - Widget trailing; - if (actions.isNotEmpty) { - trailing = Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: actions, - ); - } else { - trailing = const Empty(); - } - final spacing = context.streamSpacing; final effectiveMargin = margin ?? .symmetric(horizontal: spacing.md, vertical: spacing.xxxl); - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - height: height, - child: Row( - children: [ - Expanded(child: leading), - Expanded(child: trailing), - ], - ), - ), - Expanded( - child: Container( - margin: effectiveMargin, - child: child, - ), - ), - ], + return Container( + margin: effectiveMargin, + child: child, ); } } diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart index c8245ee7c7..fdfd164731 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart @@ -56,6 +56,7 @@ class StreamPhotoGallery extends StatelessWidget { this.thumbnailFormat = ThumbnailFormat.jpeg, this.thumbnailQuality = 100, this.thumbnailScale = 1, + this.addMoreBuilder, }); /// The [StreamPhotoGalleryController] used to control the grid of users. @@ -307,6 +308,11 @@ class StreamPhotoGallery extends StatelessWidget { /// Scale of the image. final double thumbnailScale; + /// An optional builder for a leading "Add more" tile shown as the first item + /// in the gallery grid. Useful when the user has limited photo library access + /// and needs a way to expand the selection. + final WidgetBuilder? addMoreBuilder; + @override Widget build(BuildContext context) { return PagedValueGridView( @@ -328,6 +334,7 @@ class StreamPhotoGallery extends StatelessWidget { restorationId: restorationId, clipBehavior: clipBehavior, loadMoreTriggerIndex: loadMoreTriggerIndex, + leadingItemBuilder: addMoreBuilder, gridDelegate: gridDelegate, itemBuilder: (context, mediaList, index) { final media = mediaList[index]; diff --git a/packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart b/packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart index ea5fe13b0f..ce60e68bb0 100644 --- a/packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart @@ -365,6 +365,7 @@ class PagedValueGridView extends StatefulWidget { required this.loadingBuilder, required this.errorBuilder, this.loadMoreTriggerIndex = 3, + this.leadingItemBuilder, this.scrollDirection = Axis.vertical, this.reverse = false, this.scrollController, @@ -413,6 +414,13 @@ class PagedValueGridView extends StatefulWidget { /// The index to take into account when triggering [controller.loadMore]. final int loadMoreTriggerIndex; + /// An optional builder for a single item prepended before the paged items. + /// + /// When provided, [itemBuilder] still receives regular item indices starting + /// at 0 — the leading item is handled separately, similar to + /// [loadMoreIndicatorBuilder]. + final WidgetBuilder? leadingItemBuilder; + /// {@template flutter.widgets.scroll_view.scrollDirection} /// The axis along which the scroll view scrolls. /// @@ -665,13 +673,18 @@ class _PagedValueGridViewState extends State> { keyboardDismissBehavior: widget.keyboardDismissBehavior, restorationId: widget.restorationId, clipBehavior: widget.clipBehavior, - itemCount: value.itemCount, + itemCount: value.itemCount + (widget.leadingItemBuilder != null ? 1 : 0), gridDelegate: widget.gridDelegate, itemBuilder: (context, index) { + var adjustedIndex = index; + if (widget.leadingItemBuilder != null) { + if (index == 0) return widget.leadingItemBuilder!(context); + adjustedIndex = index - 1; + } + if (!_hasRequestedNextPage) { final newPageRequestTriggerIndex = items.length - widget.loadMoreTriggerIndex; - final isBuildingTriggerIndexItem = index == newPageRequestTriggerIndex; - if (nextPageKey != null && isBuildingTriggerIndexItem) { + if (nextPageKey != null && adjustedIndex == newPageRequestTriggerIndex) { // Schedules the request for the end of this frame. WidgetsBinding.instance.addPostFrameCallback((_) async { if (error == null) { @@ -683,14 +696,14 @@ class _PagedValueGridViewState extends State> { } } - if (index == items.length) { + if (adjustedIndex == items.length) { if (error != null) { return widget.loadMoreErrorBuilder(context, error); } return widget.loadMoreIndicatorBuilder(context); } - return widget.itemBuilder(context, items, index); + return widget.itemBuilder(context, items, adjustedIndex); }, ); }, diff --git a/packages/stream_chat_flutter_core/test/paged_value_grid_view_test.dart b/packages/stream_chat_flutter_core/test/paged_value_grid_view_test.dart new file mode 100644 index 0000000000..c2877cdaf5 --- /dev/null +++ b/packages/stream_chat_flutter_core/test/paged_value_grid_view_test.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +class _TestController extends PagedValueNotifier { + _TestController(List items, {int? nextPageKey}) : super(PagedValue(items: items, nextPageKey: nextPageKey)); + + @override + Future doInitialLoad() async {} + + @override + Future loadMore(int nextPageKey) async {} +} + +Widget _wrap(Widget child) => MaterialApp(home: Scaffold(body: child)); + +PagedValueGridView _buildGrid( + _TestController controller, { + WidgetBuilder? leadingItemBuilder, + required List builtIndices, +}) { + return PagedValueGridView( + controller: controller, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), + leadingItemBuilder: leadingItemBuilder, + itemBuilder: (context, items, index) { + builtIndices.add(index); + return Text('item-$index'); + }, + emptyBuilder: (_) => const Text('empty'), + loadMoreErrorBuilder: (_, __) => const Text('load-more-error'), + loadMoreIndicatorBuilder: (_) => const Text('load-more-indicator'), + loadingBuilder: (_) => const Text('loading'), + errorBuilder: (_, __) => const Text('error'), + ); +} + +void main() { + group('PagedValueGridView without leadingItemBuilder', () { + testWidgets('renders items starting at index 0', (tester) async { + final controller = _TestController(['a', 'b', 'c']); + final builtIndices = []; + + await tester.pumpWidget(_wrap(_buildGrid(controller, builtIndices: builtIndices))); + await tester.pump(); + + expect(find.text('item-0'), findsOneWidget); + expect(find.text('item-1'), findsOneWidget); + expect(find.text('item-2'), findsOneWidget); + expect(builtIndices, [0, 1, 2]); + }); + + testWidgets('does not render a leading item', (tester) async { + final controller = _TestController(['a']); + final builtIndices = []; + + await tester.pumpWidget(_wrap(_buildGrid(controller, builtIndices: builtIndices))); + await tester.pump(); + + expect(find.text('leading'), findsNothing); + expect(builtIndices, [0]); + }); + }); + + group('PagedValueGridView with leadingItemBuilder', () { + testWidgets('renders the leading item before the paged items', (tester) async { + final controller = _TestController(['a', 'b', 'c']); + final builtIndices = []; + + await tester.pumpWidget( + _wrap( + _buildGrid( + controller, + leadingItemBuilder: (_) => const Text('leading'), + builtIndices: builtIndices, + ), + ), + ); + await tester.pump(); + + expect(find.text('leading'), findsOneWidget); + expect(find.text('item-0'), findsOneWidget); + expect(find.text('item-1'), findsOneWidget); + expect(find.text('item-2'), findsOneWidget); + }); + + testWidgets('itemBuilder receives item indices starting at 0, not offset', (tester) async { + final controller = _TestController(['a', 'b', 'c']); + final builtIndices = []; + + await tester.pumpWidget( + _wrap( + _buildGrid( + controller, + leadingItemBuilder: (_) => const Text('leading'), + builtIndices: builtIndices, + ), + ), + ); + await tester.pump(); + + expect(builtIndices, [0, 1, 2]); + }); + + testWidgets('renders leading item even with a single paged item', (tester) async { + final controller = _TestController(['a']); + final builtIndices = []; + + await tester.pumpWidget( + _wrap( + _buildGrid( + controller, + leadingItemBuilder: (_) => const Text('leading'), + builtIndices: builtIndices, + ), + ), + ); + await tester.pump(); + + expect(find.text('leading'), findsOneWidget); + expect(find.text('item-0'), findsOneWidget); + expect(builtIndices, [0]); + }); + + testWidgets('renders load-more indicator at correct position with leading item', (tester) async { + final controller = _TestController(['item-0', 'item-1'], nextPageKey: 1); + final builtIndices = []; + + await tester.pumpWidget( + _wrap( + _buildGrid( + controller, + leadingItemBuilder: (_) => const Text('leading'), + builtIndices: builtIndices, + ), + ), + ); + await tester.pump(); + + expect(find.text('leading'), findsOneWidget); + expect(find.text('item-0'), findsOneWidget); + expect(find.text('item-1'), findsOneWidget); + expect(find.text('load-more-indicator'), findsOneWidget); + expect(builtIndices, [0, 1]); + }); + }); +} diff --git a/packages/stream_chat_localizations/example/lib/add_new_lang.dart b/packages/stream_chat_localizations/example/lib/add_new_lang.dart index f627260961..36666ffdf2 100644 --- a/packages/stream_chat_localizations/example/lib/add_new_lang.dart +++ b/packages/stream_chat_localizations/example/lib/add_new_lang.dart @@ -184,7 +184,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'Something went wrong'; @override - String get addMoreFilesLabel => 'Add more files'; + String get addMoreFilesLabel => 'Add more'; @override String get enablePhotoAndVideoAccessMessage => diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart index 683fd22838..d4b139fd34 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart @@ -165,7 +165,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'Alguna cosa ha anat malament'; @override - String get addMoreFilesLabel => 'Afegir més fitxers'; + String get addMoreFilesLabel => 'Afegir més'; @override String get enablePhotoAndVideoAccessMessage => diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart index 9738984398..01d25dd969 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart @@ -159,7 +159,7 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'Etwas ist schief gelaufen'; @override - String get addMoreFilesLabel => 'Weitere Dateien hinzufügen'; + String get addMoreFilesLabel => 'Mehr hinzufügen'; @override String get enablePhotoAndVideoAccessMessage => diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart index 724b2031ca..6d09eedef0 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart @@ -164,7 +164,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'Something went wrong'; @override - String get addMoreFilesLabel => 'Add more files'; + String get addMoreFilesLabel => 'Add more'; @override String get enablePhotoAndVideoAccessMessage => diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart index e2e243db56..8cf7c799b9 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart @@ -165,7 +165,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'Algo ha salido mal'; @override - String get addMoreFilesLabel => 'Añadir más archivos'; + String get addMoreFilesLabel => 'Añadir más'; @override String get enablePhotoAndVideoAccessMessage => diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart index 9b4c0653a8..a91ea1ae8e 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart @@ -165,7 +165,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'Quelque chose a mal tourné'; @override - String get addMoreFilesLabel => "Ajouter d'autres fichiers"; + String get addMoreFilesLabel => 'Ajouter plus'; @override String get enablePhotoAndVideoAccessMessage => diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart index 4ec51bcdab..756045a96d 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart @@ -164,7 +164,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'लोड करने में समस्या'; @override - String get addMoreFilesLabel => 'और फ़ाइलें जोड़ें'; + String get addMoreFilesLabel => 'और जोड़ें'; @override String get enablePhotoAndVideoAccessMessage => diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart index c76b60500c..5bbed46afa 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart @@ -169,7 +169,7 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get somethingWentWrongError => 'Qualcosa è andato storto'; @override - String get addMoreFilesLabel => 'Aggiungi altri file'; + String get addMoreFilesLabel => 'Aggiungi altri'; @override String get enablePhotoAndVideoAccessMessage => diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart index 2d48316deb..2fa9abb65f 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart @@ -158,7 +158,7 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'エラーが発生しました'; @override - String get addMoreFilesLabel => 'ファイルの追加'; + String get addMoreFilesLabel => 'さらに追加'; @override String get enablePhotoAndVideoAccessMessage => 'お友達と共有できるように、写真やビデオへのアクセスを有効にしてください。'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart index d325b3a88f..6269977a04 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart @@ -158,7 +158,7 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { String get somethingWentWrongError => '뭔가 잘못됐습느다'; @override - String get addMoreFilesLabel => '파일을 추가함'; + String get addMoreFilesLabel => '더 추가'; @override String get enablePhotoAndVideoAccessMessage => '친구와 공유할 수 있도록 사진과 동영상에 액세스할 수 있도록 설정하십시오.'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart index b6e22e4a32..a335042b0d 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart @@ -160,7 +160,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'Noe gikk galt'; @override - String get addMoreFilesLabel => 'Legg til flere filer'; + String get addMoreFilesLabel => 'Legg til flere'; @override String get enablePhotoAndVideoAccessMessage => diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart index 27a3e98ea9..72530f80fe 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart @@ -162,7 +162,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'Algo deu errado'; @override - String get addMoreFilesLabel => 'Adicionar mais arquivos'; + String get addMoreFilesLabel => 'Adicionar mais'; @override String get enablePhotoAndVideoAccessMessage =>

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 U!MZyD2m%@0Hn~-K^WpRV0_nDc<^TWy literal 11095 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 bte#UW|Mq_XX}08G diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_light.png index dce33ea2221cc3452d6bee96e5680050392eec48..7fd124e7d98360b4200d4e6afd6fda2c90e712b0 100644 GIT binary patch literal 11117 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 literal 11078 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 wsConnectionStatus).thenReturn(ConnectionStatus.connected); when(() => wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connected)); + when(() => state).thenReturn(MockClientState()); } } diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_dark.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_dark.png index 38f89733ec058cc55cfcc1b653647d96ae8829d0..3ea44c39935fa5286a26d79e03e6a1420938d59a 100644 GIT binary patch literal 2324 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 BrGx+g literal 3817 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 F_#c5oBm@8e diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_dark.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_dark.png index 6fb30a77235284f4e9da7e783c09fd041f1f13eb..3de2c605ae107a597306b22b8bd6a405d48b206c 100644 GIT binary patch literal 2495 zcmdUx`9BkmAIIm4@HqY=Im zIdZNjM{>+HKFr!?e0~3n&-;hh`}KPN^nO1c?;oDeZ#h~Eosm8R004w+Z7d)F09WRT z_ZB#HqP3rWADlE^w7D%*;G|*%y#I5OxzG@6a{zusmI?rz_P4b#gFY3g8lfYR(g2caIl(72!mYei!$86Py zl*^=99)Mzf5@Qejn5hT>xIa(TaO;~Y5V(I7MB({4eq7~sED(x zbEtAo5>H4l~3eJWQn(@}IGAMM~5`Xv9*__9fHy?gk}VG5}C+b->z;dh+Yk zz295|7+fr81<=_d`#D(2_NhL#2b&8DJdShEU~0Epv)1yVE@>fwhSbrbehVxayTp%M z`YXGYN}rRbP8JesoZD|uyW&uw!B;Y~-Y;b&BNe#D`{KU0R#y1Ti=~7!Cp-RCGwN89(Bpelqq) z&6V=*<2YMOKvB~}flLjIF?>c$-mF>CneMh-&O$b;0(ICv!Bz$2QarR({cRM2%_liXkaHDcXEBksb%UJ>BEQMG`q?ckKi z`i#ofr4^8&27eOhA=GFf0J~weW(A81{KSX2RWV$=lR2~mS!i|yqc#q*>$1O)|9`_p z_?fL%HwUW+=cG3C4)|xB-5e0<`ljo0j$c>*UP(0!#Q&8hV#`BXp;(_9T1sP&5Az?j zG0S)QTfVKg9|L6slCS{?XBiy?tXp5C*>?uH=EiGNARR&eVys5PGT>&m&e1;anM6I; zn=-(Ze7NzG^tl2@J3rx~JP$LTG@Y@kYs3a!*DrnP22O*8iN_FcXYGBX~Uj=(KUn``k1(m;2E~mQ)YqG7F z%h~437xCi}Un4ZPpL|;N5ptLsYETMbg0(3hoyww^xAq1bgs8Zp3v+?BVAcX+tv6HA zLEXk(Z}Jiv@}*G)?G(Bc*OL`0y8=VIg!IV1vMo0-=;QBQI3(JR%>OOl@ItDaSk zc^cjgcutad{(G!v9TI;viGn*uM`;m8vvD_X#u?&}dH37zXM-^4x4jtdZXO@0((@*~ z4Wj-2b0ZCW%ES%dmz=|E)CNs^>u0@k71H+6N_V0T8Ldr=i#CJ2oduU9Y$#RSRLF6| z%D$OWa|o^+PupYIb>X8Hbq<4sgKxWocLoS>TLS6I73ZO;X%76@!)>>CR_As&4_9_* z2~RbIUg0+}z9D`zsM+rU8ScsI7&h;j!#VL zRrZ7ZacSy?IYP`$qdzdV&NQ2l&PiZr-kTzubVS4QyY2wbzim|~3e*2V%_>v|V6M;A zF`H;cO`xUwuO~fU+%(Y2fa0+f^WN5!^G=eFZrM|7`of~l2Vky*@8|ryH*BT#I2b*f z=EUEm1v8?~{8*u!3g5lEH-?gIc&u|fex$1~)W46=w2l#v6G`z^nl!=Nzg>M<^lM(q zi)e{i;yt=+zXIS_OW!hhEYwgo93r$ z#R56M*trsRuUOC7ZoXfQpcga~;$f`)vSlws?N&^uma9E=_Op0aVv-Jhdp7KmMtxRb z2-=1I@kh8|t_ZU+UTaPugY@LI{=nT@N$ZDt=_JNf&Xtsisy-e4F1j#z&5`$$;Lm52 zst|SXT0Vt!P)i}pBd-FZO02VjFe-y_78$S={gpMRhTH38Ok0@iJBl>G&A{$xt$#`AR2vc zf}-0r!urfgh=WP956{zX7bCP9$|BqOVb#}EM5ipn)fukZ=s%vO1sJuthMjBk;1Adn zTTS$#hB&`e=*`*Z8&QSWs^;Byqb?f^OC}!lg_cNcc3Q;k6WwVC7tU5-NURN30zO(_ z4xFovwBL|aS2%w8Gd(8=x<~ho3&!y}{^?7S;1wonMU9 z{-A-Dqn4}=*|m`R$A`S`MEw^T%T-pH$Oq&>VW_<&uUMCE?7Y8Vn&8KK%oHovZS3Lx fmm$Il%^Zo`-L#Ry^e1T;#107{8 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 diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_light.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_light.png index 9e26228e052ba0dee5e4a0c9b9ae3d6501bf01fa..6a1b7053306ef85874f2224c59e3bf9d563f54a8 100644 GIT binary patch literal 2645 zcmdT``#;l*8~;!ztG<$#Q!3+>a)?k$a+%~lvLfuT$lQzp{=6ccj>WXRd_wPMo;`DQb|)Ew{sL*87HrV#xE`SDVM) z%->nx3v^zX(B}g{CtO2G88~mB0RRPuPXfT{y&#|y^nVcQgx?lWf5rsMt4NtCH#e%Z z09P+70ka1KN4CEb!PDt5y6y#8k-ZcZaA&YC%e z`4gsxYC3o0nu{1?>OinH_Sj;#j=FB>=<8aud$^s2KGjYc1s9b=uR|(k`nIrs^4<;? zJyK+(p`RFnhZt=X4aHJU9!G!YwHb;a!)gcEN%sRTQp+Usly_OS6NB!2=xzeNx?H15 zAkeB>k(%iG)=Kb3xZHX|d#5OIEC8O6F|2FX;|-&bUJVxfawkolMaiv^rPv|WRG|c( z<<8YT>BVDpHkPe*DhHMf&!AEpBUqVDiWKjmbVm-oQdxtZ70T0K5&d3Le9B#1^|-ZP z18gBVw)VqoVaQnv4F_r7Y8SZg4}Susxn2i3&~0q^N8bC~+OaJkQRs|EnZ!4=`je_( z2^WVp_BG4&2{)grtK3>u?m8Gm4!0ocwtsx#O&=vq$Fx0a7k^uKzSiPG7^^?a_Ozx& zzp9A4Sn=xC_^dK~^qG?k85*A{$hSPIS-L%z}G;$V1w_4@NnxnqgTiv=EGAFP%Grr_fXVl?&3q#OJ zNcn5(C%-nIeld&PXYhHSBh~WN`>qb4MdYk3HlJDUG_~m|A#zPOiB|gl*_J~vQK zl*au0WAuuZMoW+zD`csACP9draHPb*bkD1RDZ>KR2kstcJe_-EUhKYV|N zDEK;4U@JU;(5wxLYX3fUEWLC8FGPqH936~TB63;mY zN*Zsdn1z}v#@zp<hxpqJHb~DCXqhw-!E0ynLU*vD*A0c5nk9?h zdJnEM^;o^68;`MJI|m`ys26y1Niq{t9G8l3R~@Ln>!}&1)kZX3?Em`uz2u*~mwNAN z2a==1oz!kL<_Mg#AD*J8^brWjPMk$7n<6>n9q}z-UB)n>J^!?M==wQgvjPTk1;5tw zUu=GqCD^s@xA}<{f}%aTd3kujR&B?6RqI@=$ykwWJYwDf-F<7;@$llt6MzPcDD=(-Y6F=`$}prlJegv!&zdU#rF(r z*Ru}Y$4=P+fHUYYkfE-; i7YND*l>WyTia literal 3861 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 VpdRWgz`Yh^r1$9FXC23={{ls)Ay5DS diff --git a/sample_app/android/app/src/main/AndroidManifest.xml b/sample_app/android/app/src/main/AndroidManifest.xml index 6fb866b633..28a3985094 100644 --- a/sample_app/android/app/src/main/AndroidManifest.xml +++ b/sample_app/android/app/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ + @@ -14,6 +15,10 @@ + + + + builder: (context, child) => StreamChat( client: _initNotifier.initData!.client, streamChatConfigData: StreamChatConfigurationData( - draftMessagesEnabled: true, + draftMessagesEnabled: false, ), child: child, ), From a49c7911c3db20cad5b5fdd07dc38993c32af302 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Tue, 3 Mar 2026 17:05:49 +0100 Subject: [PATCH 09/33] add initial claude file (#2523) * add initial claude file * update format check Co-authored-by: Sahil Kumar * improve note on local dependency --------- Co-authored-by: Sahil Kumar --- CLAUDE.md | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..7d2892bd2b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,148 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +This is a Dart/Flutter monorepo for Stream Chat's official Flutter SDK, managed with [Melos](https://pub.dev/packages/melos). All packages live under `packages/`. + +## Common Commands + +### Setup +```bash +melos bootstrap # Fetch and link all dependencies (equivalent to pub get for all packages) +``` + +### Testing +```bash +melos run test:all # Run all Dart & Flutter tests +melos run test:dart # Run Dart-only tests +melos run test:flutter # Run Flutter tests +# Run tests in a specific package: +cd packages/stream_chat_flutter && flutter test +cd packages/stream_chat_flutter && flutter test test/src/path/to/test_file.dart +``` + +### Golden Tests +```bash +melos run update:goldens # Regenerate all golden image files +# In CI, CI goldens are used; locally, platform goldens are used (configured via alchemist) +``` + +### Linting & Formatting +```bash +melos run lint:all # Run analyze + format +melos run analyze # Run dart analyze --fatal-infos on all packages +melos run format # Check formatting (page width: 120) +``` + +### Code Generation +```bash +melos run generate:all # Run build_runner for all packages +melos run generate:flutter # Run build_runner for Flutter packages only +melos run generate:dart # Run build_runner for Dart packages only +``` + +### Versioning +```bash +melos run version:update # Regenerate version.dart from pubspec.yaml (runs automatically after bootstrap) +``` + +## Package Architecture + +The SDK is layered — each package builds on top of the previous: + +``` +stream_chat # Pure Dart, no Flutter dependency + └── stream_chat_persistence # Local disk cache using Drift (optional) + └── stream_chat_flutter_core # Flutter business logic, no UI + └── stream_chat_flutter # Full UI component library + └── stream_chat_localizations # i18n for UI components +``` + +### `stream_chat` +Low-level Dart client. Key types: +- `StreamChatClient` — central API client, manages WebSocket, REST, and state +- `Channel` — represents a chat channel, has its own state and streaming APIs +- Models in `lib/src/core/models/`: `Message`, `User`, `Member`, `Reaction`, `Poll`, `Event`, `Attachment`, `Draft`, etc. +- Generated code (`.g.dart`, `.freezed.dart`) for JSON serialization/immutable models — do not edit manually + +### `stream_chat_flutter_core` +Business logic layer. Key types: +- `StreamChatCore` — root widget, manages app lifecycle, WebSocket reconnection, and connectivity +- `StreamChannel` — provides a `Channel` to the widget tree via `StreamChannel.of(context)` +- `PagedValueNotifier` — base class for all list controllers +- Controllers: `StreamChannelListController`, `StreamMessageListController` (via `MessageListCore`), `StreamUserListController`, `StreamMemberListController`, `StreamThreadListController`, `StreamDraftListController`, `StreamMessageReminderListController`, `StreamPollController` +- `BetterStreamBuilder` — efficient StreamBuilder that only rebuilds when data changes + +### `stream_chat_flutter` +Full UI package. Key architectural points: + +**Root widget hierarchy:** +`StreamChat` → `StreamChatTheme` → `StreamChatConfiguration` → `StreamChatCore` → app content + +**Theming:** `StreamChatThemeData` (accessed via `StreamChatTheme.of(context)`) contains per-component theme data objects. Components read their theme from context. `StreamChatConfigurationData` holds non-theme UI config. + +**Widget tree integration pattern:** +- `StreamChat.of(context)` — get the chat state (current user, client) +- `StreamChannel.of(context)` — get the current channel state +- `StreamChatTheme.of(context)` — get current theme data + +**Key UI components:** +- `StreamChannelListView` + `StreamChannelListTile` — channel list using `StreamChannelListController` +- `StreamMessageListView` — message list with floating date dividers, unread indicators, thread separators +- `StreamMessageInput` (legacy) / `StreamChatMessageComposer` (new design system) — message composition +- `StreamMessageWidget` — renders individual messages with attachments, reactions, threads +- Scroll views in `lib/src/scroll_view/` — generic paged scroll views for channels, threads, members, users, drafts, polls + +**New design system components** (`lib/src/components/`): +- `StreamUserAvatar`, `StreamChannelAvatar`, `StreamUserAvatarGroup` — avatar components; these are chat-domain wrappers around the base components in `stream_core_flutter` +- `StreamChatMessageComposer` — new composer using `MessageComposerFactory` for custom layouts + +**Golden tests:** Use `alchemist` package. Platform goldens used locally, CI goldens used in CI (detected via `CI`/`GITHUB_ACTIONS` env vars). Goldens stored alongside tests in `goldens/` subdirectories. + +### `stream_chat_localizations` +Provides `StreamChatLocalizations` — Flutter localizations delegate with translations for all UI strings. + +### `stream_chat_persistence` +Optional local persistence using Drift (SQLite). Implements `ChatPersistenceClient` from `stream_chat`. + +## Code Style + +- Line length: **120 characters** (configured in `analysis_options.yaml`) +- Imports: always use package imports (`always_use_package_imports`), not relative imports +- All public APIs **must** have doc comments (`public_member_api_docs`) +- Sort constructors first, unnamed constructors before named +- Prefer `const` constructors, `final` locals, single quotes +- Trailing commas: `preserve` (formatter setting) +- Generated files (`.g.dart`, `.freezed.dart`) are excluded from analysis + +## PR & Commit Conventions + +PR titles follow [Conventional Commits](https://www.conventionalcommits.org/): +- `fix(scope): description` — bug fix +- `feat(scope): description` — new feature +- `refactor(scope)!: description` — breaking change +- `chore(scope): description`, `docs:`, `test:`, etc. + +After modifying any package, update its `CHANGELOG.md`. + +## Figma Designs + +UI designs for this SDK are in the **Chat SDK Design system** Figma project. Use the Figma MCP server to look up designs when implementing or updating UI components. + +## `stream_core_flutter` (external sibling repo) + +Basic UI components that can be shared across Stream products live in the `stream_core_flutter` package in the **stream-core-flutter** repository (a sibling repo, not inside this monorepo). These components: +- Never depend on chat domain models (`Channel`, `Message`, `User`, etc.) +- Provide primitive building blocks: avatars, layout primitives, theming tokens, etc. + +Components in this repo can be wrappers around those base components, adding chat domain models and extra logic on top. For example, `StreamChannelAvatar` wraps the base `StreamAvatarGroup` from `stream_core_flutter` and adds channel-specific member resolution logic. + +`stream_core_flutter` types used here are re-exported via a `show` allowlist in `lib/stream_chat_flutter.dart`. When adding a new type from `stream_core_flutter`, add it to that allowlist. + +## Dependency Management + +Dependencies are centrally managed in `melos.yaml` under `command.bootstrap.dependencies`. Do **not** edit version constraints directly in individual `pubspec.yaml` files — update `melos.yaml` and run `melos bootstrap`. + +> Note: `stream_chat_flutter` uses a local path dependency to `stream_core_flutter` (pointing to the sibling repo) when making changes to both repos together. Use a git dependency when no local changes to `stream_core_flutter` are needed. From b4a328c8fb5e3b861d1866a9a5bce94b66557884 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Tue, 3 Mar 2026 17:11:24 +0100 Subject: [PATCH 10/33] feat(ui): audio attachments in composer (#2518) * Improved voice recording UI * separate voice recordings in default attachments * Simplify theming * Improve structure of message composer * remove default background color * add stream theme with brightness in test * chore: Update Goldens * fix 2 outdated tests * update theme properties in tests --------- Co-authored-by: renefloor <15101411+renefloor@users.noreply.github.com> --- melos.yaml | 2 +- .../voice_recording_attachment.dart | 139 ++--- .../voice_recording_attachment_playlist.dart | 16 + .../message_composer_input_header.dart | 19 +- .../message_composer_recording_locked.dart | 240 ++++++++- .../stream_chat_message_composer.dart | 342 ++++++------ .../audio_recorder/stream_audio_recorder.dart | 1 - .../stream_message_input_attachment_list.dart | 34 +- .../lib/src/misc/audio_waveform.dart | 486 ------------------ .../theme/audio_waveform_slider_theme.dart | 129 ----- .../lib/src/theme/audio_waveform_theme.dart | 150 ------ .../lib/src/theme/stream_chat_theme.dart | 75 +-- .../lib/src/theme/themes.dart | 2 - .../voice_recording_attachment_theme.dart | 61 +-- .../lib/src/utils/extensions.dart | 9 + .../lib/stream_chat_flutter.dart | 2 + packages/stream_chat_flutter/pubspec.yaml | 2 +- ...m_voice_recording_attachment_idle_dark.png | Bin 5971 -> 4702 bytes ..._voice_recording_attachment_idle_light.png | Bin 6733 -> 4354 bytes ...oice_recording_attachment_playing_dark.png | Bin 5462 -> 5381 bytes ...ice_recording_attachment_playing_light.png | Bin 6374 -> 4963 bytes ...ice_recording_attachment_playlist_dark.png | Bin 14035 -> 10776 bytes ...ce_recording_attachment_playlist_light.png | Bin 14530 -> 10405 bytes ...ce_recording_attachment_playlist_test.dart | 9 + .../voice_recording_attachment_test.dart | 5 +- ...io_recorder_button_recording_hold_dark.png | Bin 4719 -> 4570 bytes ..._recorder_button_recording_locked_dark.png | Bin 6755 -> 6313 bytes ...recorder_button_recording_locked_light.png | Bin 6584 -> 5919 bytes ...recorder_button_recording_stopped_dark.png | Bin 5927 -> 5747 bytes ...ecorder_button_recording_stopped_light.png | Bin 5795 -> 5339 bytes .../stream_audio_recorder_test.dart | 2 +- .../test/src/misc/audio_waveform_test.dart | 2 +- ...ream_audio_waveform_slider_custom_dark.png | Bin 3489 -> 3725 bytes ...eam_audio_waveform_slider_custom_light.png | Bin 3439 -> 3631 bytes .../ci/stream_audio_waveform_slider_dark.png | Bin 3168 -> 2918 bytes ...tream_audio_waveform_slider_empty_dark.png | Bin 988 -> 1123 bytes ...ream_audio_waveform_slider_empty_light.png | Bin 976 -> 944 bytes ...am_audio_waveform_slider_inverted_dark.png | Bin 3237 -> 2924 bytes ...m_audio_waveform_slider_inverted_light.png | Bin 3272 -> 2758 bytes ...m_audio_waveform_slider_less_data_dark.png | Bin 2999 -> 2853 bytes ..._audio_waveform_slider_less_data_light.png | Bin 3055 -> 2663 bytes .../ci/stream_audio_waveform_slider_light.png | Bin 3187 -> 2738 bytes ...am_audio_waveform_slider_progress_dark.png | Bin 3476 -> 3573 bytes ...m_audio_waveform_slider_progress_light.png | Bin 3236 -> 3215 bytes sample_app/lib/app.dart | 20 +- 45 files changed, 600 insertions(+), 1147 deletions(-) delete mode 100644 packages/stream_chat_flutter/lib/src/misc/audio_waveform.dart delete mode 100644 packages/stream_chat_flutter/lib/src/theme/audio_waveform_slider_theme.dart delete mode 100644 packages/stream_chat_flutter/lib/src/theme/audio_waveform_theme.dart diff --git a/melos.yaml b/melos.yaml index a3e47fa6be..551fb4f1a3 100644 --- a/melos.yaml +++ b/melos.yaml @@ -95,7 +95,7 @@ command: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: da18aa04ad48d4d5bb429c9e90d9f0253c418fae + ref: 4e7e22b4b61e9b9569e2933407d49fff370e6bec path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 diff --git a/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment.dart index 2a1e6e6d68..f4805549c2 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/audio/audio_playlist_state.dart'; import 'package:stream_chat_flutter/src/audio/audio_sampling.dart' as sampling; -import 'package:stream_chat_flutter/src/misc/audio_waveform.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; const _kDefaultWaveformLimit = 35; -const _kDefaultWaveformHeight = 28.0; +const _kDefaultWaveformHeight = 20.0; /// Signature for building trailing widgets in voice recording attachments. /// @@ -47,7 +47,9 @@ class StreamVoiceRecordingAttachment extends StatelessWidget { this.shape, this.constraints = const BoxConstraints(), this.showTitle = false, + this.title, this.trailingBuilder = _defaultTrailingBuilder, + this.onRemovePressed, }); /// The audio track to display. @@ -85,6 +87,10 @@ class StreamVoiceRecordingAttachment extends StatelessWidget { /// The constraints to use when displaying the voice recording. final BoxConstraints constraints; + /// The title of the audio message to display when [showTitle] is `true`. + /// If not provided, the [track.title] will be used. + final String? title; + /// Whether to show the title of the audio message. /// /// Defaults to `false`. @@ -93,49 +99,36 @@ class StreamVoiceRecordingAttachment extends StatelessWidget { /// The builder to use for the trailing widget. final StreamVoiceRecordingAttachmentTrailingWidgetBuilder trailingBuilder; + /// Callback called when the remove button is pressed. + /// If not provided, the remove button will not be shown. + final VoidCallback? onRemovePressed; + static Widget _defaultTrailingBuilder( BuildContext context, PlaylistTrack track, PlaybackSpeed speed, ValueChanged? onChangeSpeed, ) { - return AnimatedSwitcher( - duration: const Duration(milliseconds: 200), - child: switch (track.state.isPlaying) { - true => SpeedControlButton( - speed: speed, - onChangeSpeed: onChangeSpeed, - ), - false => getFileTypeImage(track.title?.mediaType?.mimeType), - }, + return SpeedControlButton( + speed: speed, + onChangeSpeed: onChangeSpeed, ); } @override Widget build(BuildContext context) { + final colorScheme = context.streamColorScheme; final theme = StreamVoiceRecordingAttachmentTheme.of(context); - final waveformSliderTheme = theme.audioWaveformSliderTheme; - final waveformTheme = waveformSliderTheme?.audioWaveformTheme; - - final shape = - this.shape ?? - RoundedRectangleBorder( - side: BorderSide( - color: StreamChatTheme.of(context).colorTheme.borders, - strokeAlign: BorderSide.strokeAlignOutside, - ), - borderRadius: BorderRadius.circular(14), - ); - - return Container( - constraints: constraints, - clipBehavior: Clip.hardEdge, - padding: const EdgeInsets.all(8), - decoration: ShapeDecoration( - shape: shape, - color: theme.backgroundColor, - ), + final textTheme = context.streamTextTheme; + final spacing = context.streamSpacing; + + return StreamMessageComposerAttachmentContainer( + borderColor: colorScheme.borderDefault, + onRemovePressed: onRemovePressed, + backgroundColor: theme.backgroundColor, + padding: EdgeInsets.all(spacing.sm), child: Row( + crossAxisAlignment: .center, children: [ AudioControlButton( state: track.state, @@ -149,21 +142,22 @@ class StreamVoiceRecordingAttachment extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (track.title case final title? when showTitle) ...[ + if (title ?? track.title case final title? when showTitle) AudioTitleText( title: title, - style: theme.titleTextStyle, + style: theme.titleTextStyle ?? textTheme.metadataEmphasis, ), - const SizedBox(height: 6), - ], + Row( children: [ AudioDurationText( duration: track.duration, position: track.position, - style: theme.durationTextStyle, + style: + theme.durationTextStyle ?? + textTheme.metadataEmphasis.copyWith(color: colorScheme.textSecondary), ), - const SizedBox(width: 8), + SizedBox(width: spacing.xs), Expanded( child: SizedBox( height: _kDefaultWaveformHeight, @@ -177,13 +171,7 @@ class StreamVoiceRecordingAttachment extends StatelessWidget { onChangeStart: onTrackSeekStart, onChanged: onTrackSeekChanged, onChangeEnd: onTrackSeekEnd, - color: waveformTheme?.color, - progressColor: waveformTheme?.progressColor, - minBarHeight: waveformTheme?.minBarHeight, - spacingRatio: waveformTheme?.spacingRatio, - heightScale: waveformTheme?.heightScale, - thumbColor: waveformSliderTheme?.thumbColor, - thumbBorderColor: waveformSliderTheme?.thumbBorderColor, + isActive: track.state != TrackState.idle, ), ), ), @@ -285,6 +273,9 @@ class AudioControlButton extends StatelessWidget { this.onPlay, this.onPause, this.onReplay, + this.style = .secondary, + this.type = .outline, + this.size = .medium, }); /// The current state of the audio track. @@ -299,24 +290,35 @@ class AudioControlButton extends StatelessWidget { /// Callback when the track is replayed. final VoidCallback? onReplay; + /// The style of the button. + final StreamButtonStyle style; + + /// The type of the button. + final StreamButtonType type; + + /// The size of the button. + final StreamButtonSize size; + @override Widget build(BuildContext context) { - final theme = StreamVoiceRecordingAttachmentTheme.of(context); + final icons = context.streamIcons; - return ElevatedButton( - style: theme.audioControlButtonStyle, - onPressed: switch (state) { + return StreamButton.icon( + style: style, + type: type, + size: size, + icon: switch (state) { + TrackState.loading => icons.playSolid, + TrackState.idle => icons.playSolid, + TrackState.playing => icons.pause, + TrackState.paused => icons.playSolid, + }, + onTap: switch (state) { TrackState.loading => null, TrackState.idle => onPlay, TrackState.playing => onPause, TrackState.paused => onPlay, }, - child: switch (state) { - TrackState.loading => theme.loadingIndicator, - TrackState.idle => theme.playIcon, - TrackState.playing => theme.pauseIcon, - TrackState.paused => theme.playIcon, - }, ); } } @@ -343,14 +345,35 @@ class SpeedControlButton extends StatelessWidget { @override Widget build(BuildContext context) { final theme = StreamVoiceRecordingAttachmentTheme.of(context); + final colorScheme = context.streamColorScheme; + final textTheme = context.streamTextTheme; + + final buttonStyle = + theme.speedControlButtonStyle ?? + ElevatedButton.styleFrom( + elevation: 2, + textStyle: textTheme.metadataEmphasis, + foregroundColor: colorScheme.textPrimary, + padding: const EdgeInsets.symmetric(horizontal: 8), + shape: StadiumBorder(side: BorderSide(color: colorScheme.borderDefault)), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + minimumSize: const Size(40, 28), + ); - return ElevatedButton( - style: theme.speedControlButtonStyle, + return TextButton( + style: buttonStyle, onPressed: switch (onChangeSpeed) { final it? => () => it(speed.next), _ => null, }, - child: Text('x${speed.speed}'), + child: ConstrainedBox( + constraints: const BoxConstraints(minWidth: 28), + child: Text( + 'x${speed.speed.toString().replaceFirst('.0', '')}', + style: textTheme.metadataEmphasis.copyWith(height: 1), + textAlign: TextAlign.center, + ), + ), ); } } diff --git a/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment_playlist.dart b/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment_playlist.dart index 53f77a07f6..9fede75283 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment_playlist.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment_playlist.dart @@ -19,6 +19,8 @@ class StreamVoiceRecordingAttachmentPlaylist extends StatefulWidget { this.itemBuilder, this.separatorBuilder = _defaultVoiceRecordingPlaylistSeparatorBuilder, this.constraints = const BoxConstraints(), + this.onRemovePressed, + this.voiceRecordingTitle, }); /// The shape of the attachment. @@ -47,6 +49,12 @@ class StreamVoiceRecordingAttachmentPlaylist extends StatefulWidget { /// The separator to use between the voice recordings. final IndexedWidgetBuilder separatorBuilder; + /// Callback called when the remove button is pressed. + final ValueSetter? onRemovePressed; + + /// The title to use for the voice recording. + final String? voiceRecordingTitle; + // Default separator builder for the voice recording playlist. static Widget _defaultVoiceRecordingPlaylistSeparatorBuilder( BuildContext context, @@ -111,10 +119,18 @@ class _StreamVoiceRecordingAttachmentPlaylistState extends State widget.onRemovePressed?.call(attachment!) + : null, shape: widget.shape, constraints: widget.constraints, onTrackPause: _controller.pause, diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart index 8b249ba10f..2ab5ad67eb 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart @@ -34,12 +34,17 @@ class _DefaultStreamMessageComposerInputHeader extends StatelessWidget { final ogAttachment = props.controller.ogAttachment; final nonOGAttachments = controller.attachments .where((it) { - return it.titleLink == null; + return it.titleLink == null && it.type != AttachmentType.voiceRecording; + }) + .toList(growable: false); + final voiceRecordings = controller.attachments + .where((it) { + return it.type == AttachmentType.voiceRecording; }) .toList(growable: false); final hasAttachments = nonOGAttachments.isNotEmpty; - final hasContent = quotedMessage != null || hasAttachments || ogAttachment != null; + final hasContent = quotedMessage != null || hasAttachments || ogAttachment != null || voiceRecordings.isNotEmpty; final spacing = context.streamSpacing; final contentPadding = EdgeInsets.only( @@ -66,6 +71,16 @@ class _DefaultStreamMessageComposerInputHeader extends StatelessWidget { currentUserId: props.currentUserId, ), ), + if (voiceRecordings.isNotEmpty) + Padding( + padding: contentPadding, + child: StreamVoiceRecordingAttachmentPlaylist( + voiceRecordings: voiceRecordings, + onRemovePressed: _onAttachmentRemovePressed, + voiceRecordingTitle: 'Voice Message', + message: props.controller.message, + ), + ), if (hasAttachments) StreamMessageInputAttachmentList( attachments: nonOGAttachments, diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_recording_locked.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_recording_locked.dart index 194c668411..220f80a289 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_recording_locked.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_recording_locked.dart @@ -1,7 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/audio/audio_playlist_controller.dart'; +import 'package:stream_chat_flutter/src/audio/audio_playlist_state.dart'; +import 'package:stream_chat_flutter/src/audio/audio_sampling.dart' as sampling; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart'; +const _kDefaultWaveformHeight = 20.0; +const _kDefaultWaveformLimit = 35; + /// Widget to display the recording locked state. /// This widget can be used inside of the [StreamBaseMessageComposer] instead of the default `inputBody`. class MessageComposerRecordingLocked extends StatelessWidget { @@ -16,6 +22,7 @@ class MessageComposerRecordingLocked extends StatelessWidget { required this.feedback, required this.messageInputController, required this.sendMessageCallback, + required this.state, }); /// The controller for the audio recorder. @@ -31,11 +38,15 @@ class MessageComposerRecordingLocked extends StatelessWidget { /// This callback should be null when the message is not supposed to be sent automatically. final VoidCallback? sendMessageCallback; + /// The state of the recording. + final RecordStateRecording state; + @override Widget build(BuildContext context) { final icons = context.streamIcons; final textTheme = context.streamTextTheme; final colorScheme = context.streamColorScheme; + final spacing = context.streamSpacing; return Column( mainAxisSize: MainAxisSize.min, @@ -53,23 +64,19 @@ class MessageComposerRecordingLocked extends StatelessWidget { size: 20, ), ), - ValueListenableBuilder( - valueListenable: audioRecorderController, - builder: (context, state, child) { - final duration = switch (state) { - RecordStateRecording() => state.duration, - RecordStateStopped() => state.audioRecording.duration, - RecordStateIdle() => Duration.zero, - }; - - return Text( - duration.toMinutesAndSeconds(), - style: textTheme.captionEmphasis.copyWith( - color: colorScheme.textPrimary, - fontFeatures: [const FontFeature.tabularFigures()], - ), - ); - }, + Text( + state.duration.toMinutesAndSeconds(), + style: textTheme.captionEmphasis.copyWith( + color: colorScheme.textPrimary, + fontFeatures: [const FontFeature.tabularFigures()], + ), + ), + Expanded( + child: Container( + height: _kDefaultWaveformHeight, + padding: EdgeInsets.symmetric(horizontal: spacing.md), + child: StreamAudioWaveform(waveform: state.waveform, limit: 50), + ), ), ], ), @@ -120,3 +127,202 @@ class MessageComposerRecordingLocked extends StatelessWidget { ); } } + +/// Widget to display the recording stopped state. +/// This widget can be used inside of the [StreamBaseMessageComposer] instead of the default `inputBody`. +class MessageComposerRecordingStopped extends StatefulWidget { + /// Creates a new instance of [MessageComposerRecordingStopped]. + /// [audioRecorderController] is the controller for the audio recorder. + /// [feedback] is the feedback for the audio recorder. + /// [messageInputController] is the controller for the message input. + /// [sendMessageCallback] is the callback for when the message is sent automatically. + const MessageComposerRecordingStopped({ + super.key, + required this.audioRecorderController, + required this.feedback, + required this.messageInputController, + required this.sendMessageCallback, + required this.recordingState, + }); + + /// The controller for the audio recorder. + final StreamAudioRecorderController audioRecorderController; + + /// The feedback for the audio recorder. + final AudioRecorderFeedback feedback; + + /// The controller for the message input. + final StreamMessageInputController messageInputController; + + /// The callback for when the message is sent automatically. + /// This callback should be null when the message is not supposed to be sent automatically. + final VoidCallback? sendMessageCallback; + + /// The state of the recording. + final RecordStateStopped recordingState; + + Attachment get _audioRecording => recordingState.audioRecording; + + @override + State createState() => _MessageComposerRecordingStoppedState(); +} + +class _MessageComposerRecordingStoppedState extends State { + late final _controller = StreamAudioPlaylistController( + widget._audioRecording.toPlaylist(), + ); + + @override + void initState() { + super.initState(); + _controller.initialize(); + } + + @override + void didUpdateWidget( + covariant MessageComposerRecordingStopped oldWidget, + ) { + super.didUpdateWidget(oldWidget); + if (widget._audioRecording != widget._audioRecording) { + // If the playlist have changed, update the playlist. + _controller.updatePlaylist(widget._audioRecording.toPlaylist()); + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final icons = context.streamIcons; + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; + const index = 0; + + final spacing = context.streamSpacing; + + return ValueListenableBuilder( + valueListenable: _controller, + builder: (context, state, child) { + final track = state.tracks.firstOrNull; + if (track == null) return const SizedBox.shrink(); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + AudioControlButton( + state: track.state, + style: StreamButtonStyle.secondary, + type: StreamButtonType.ghost, + size: StreamButtonSize.small, + onPlay: () { + if (state.currentIndex == index) { + // Play the track directly if it is already loaded. + _controller.play(); + } else { + // Otherwise, load the track first and then play it. + _controller.skipToItem(index); + } + }, + onPause: _controller.pause, + ), + AudioDurationText( + duration: track.duration, + position: track.position, + style: textTheme.captionEmphasis.copyWith( + color: colorScheme.textPrimary, + fontFeatures: [const FontFeature.tabularFigures()], + ), + ), + + Expanded( + child: Container( + padding: EdgeInsets.symmetric(horizontal: spacing.md), + height: _kDefaultWaveformHeight, + child: StreamAudioWaveformSlider( + limit: _kDefaultWaveformLimit, + waveform: sampling.resampleWaveformData( + track.waveform, + _kDefaultWaveformLimit, + ), + progress: track.progress, + // Only allow seeking if the current track is the one being + // interacted with. + onChangeStart: (_) async { + if (state.currentIndex != index) return; + return _controller.pause(); + }, + onChangeEnd: (_) async { + if (state.currentIndex != index) return; + return _controller.play(); + }, + onChanged: (progress) async { + if (state.currentIndex != index) return; + + final duration = track.duration.inMicroseconds; + final seekPosition = (duration * progress).toInt(); + final seekDuration = Duration(microseconds: seekPosition); + + return _controller.seek(seekDuration); + }, + isActive: track.state != TrackState.idle, + ), + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + StreamButton.icon( + key: const ValueKey('cancel-record-button'), + style: StreamButtonStyle.secondary, + type: StreamButtonType.outline, + size: StreamButtonSize.small, + icon: icons.trashBin, + onTap: widget.audioRecorderController.cancelRecord, + ), + if (widget.audioRecorderController.value is RecordStateRecording) + StreamButton.icon( + key: const ValueKey('stop-record-button'), + style: StreamButtonStyle.destructive, + type: StreamButtonType.outline, + size: StreamButtonSize.small, + icon: icons.stop, + onTap: widget.audioRecorderController.stopRecord, + ), + StreamButton.icon( + key: const ValueKey('finish-record-button'), + style: StreamButtonStyle.primary, + type: StreamButtonType.solid, + size: StreamButtonSize.small, + icon: icons.checkmark2Small, + onTap: () async { + await widget.feedback.onRecordFinish(context); + final audio = await widget.audioRecorderController.finishRecord(); + if (audio != null) { + widget.messageInputController.addAttachment(audio); + } + + // Once the recording is finished, cancel the recorder. + widget.audioRecorderController.cancelRecord(discardTrack: false); + + // Send the message if the user has enabled the option to + // send the voice recording automatically. + widget.sendMessageCallback?.call(); + }, + ), + ], + ), + ], + ); + }, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart index b2390058ea..117d25bbcf 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart @@ -31,7 +31,8 @@ class StreamChatMessageComposer extends StatefulWidget { String? currentUserId, String placeholder = '', StreamAudioRecorderController? audioRecorderController, - this.sendVoiceRecordingAutomatically = false, + bool sendVoiceRecordingAutomatically = false, + AudioRecorderFeedback feedback = const AudioRecorderFeedback(), }) : props = MessageComposerProps( isFloating: false, message: null, @@ -41,6 +42,8 @@ class StreamChatMessageComposer extends StatefulWidget { currentUserId: currentUserId, placeholder: placeholder, audioRecorderController: audioRecorderController, + sendVoiceRecordingAutomatically: sendVoiceRecordingAutomatically, + feedback: feedback, ); /// The controller for the message composer. @@ -49,25 +52,12 @@ class StreamChatMessageComposer extends StatefulWidget { /// The properties for the message composer. final MessageComposerProps props; - /// Whether the voice recording should be sent automatically. - /// If enabled, the voice recording will be sent automatically when the recording is finished. - /// If disabled, the voice recording will be added as an attachment to the message - /// and the user will need to send the message manually. - final bool sendVoiceRecordingAutomatically; - @override State createState() => _StreamChatMessageComposerState(); } class _StreamChatMessageComposerState extends State { late StreamMessageInputController _controller; - final AudioRecorderFeedback feedback = const AudioRecorderFeedback(); - - /// The threshold to lock the recording. - final double lockRecordThreshold = 50; - - /// The threshold to cancel the recording. - final double cancelRecordThreshold = 75; @override void initState() { @@ -102,141 +92,61 @@ class _StreamChatMessageComposerState extends State { @override Widget build(BuildContext context) { - final voiceRecordingCallback = createVoiceRecordingCallback(); - - MessageComposerComponentProps componentProps({required AudioRecorderState audioRecorderState}) => - MessageComposerComponentProps( - controller: _controller, - isFloating: widget.props.isFloating, - message: widget.props.message, - currentUserId: widget.props.currentUserId, - onSendPressed: widget.props.onSendPressed, - voiceRecordingCallback: voiceRecordingCallback, - onAttachmentButtonPressed: widget.props.onAttachmentButtonPressed, - audioRecorderState: audioRecorderState, - focusNode: widget.props.focusNode, - ); - - Widget createComposer({ - Widget? body, - AudioRecorderState audioRecorderState = const RecordStateIdle(), - }) => - StreamMessageComposerFactory.maybeOf(context)?.messageComposer?.call(context, widget.props) ?? - core.StreamCoreMessageComposer( - placeholder: widget.props.placeholder, - controller: _controller.textFieldController, - isFloating: widget.props.isFloating, - focusNode: widget.props.focusNode, - composerLeading: StreamMessageComposerLeading(props: componentProps(audioRecorderState: audioRecorderState)), - composerTrailing: StreamMessageComposerTrailing( - props: componentProps(audioRecorderState: audioRecorderState), - ), - inputHeader: StreamMessageComposerInputHeader(props: componentProps(audioRecorderState: audioRecorderState)), - inputTrailing: StreamMessageComposerInputTrailing( - props: componentProps(audioRecorderState: audioRecorderState), - ), - inputLeading: StreamMessageComposerInputLeading( - props: componentProps(audioRecorderState: audioRecorderState), - ), - inputBody: body, - ); + if (StreamMessageComposerFactory.maybeOf(context)?.messageComposer case final messageComposerFactory?) { + return messageComposerFactory(context, widget.props); + } - if (widget.props.audioRecorderController case final controller?) { - return ValueListenableBuilder( - valueListenable: controller, - builder: (context, state, _) { - final body = switch (state) { - RecordStateRecordingLocked() || RecordStateStopped() => MessageComposerRecordingLocked( - audioRecorderController: controller, - feedback: feedback, - messageInputController: _controller, - sendMessageCallback: widget.sendVoiceRecordingAutomatically ? widget.props.onSendPressed : null, - ), - RecordStateRecording() => StreamMessageComposerRecordingOngoing(audioRecorderController: controller), - _ => null, - }; - - final streamSpacing = context.streamSpacing; - - return PortalTarget( - anchor: Aligned( - offset: Offset(-streamSpacing.md, -streamSpacing.md), - target: Alignment.topRight, - follower: Alignment.bottomRight, - ), - visible: state is RecordStateRecording, - portalFollower: SwipeToLockButton(isLocked: state is RecordStateRecordingLocked), - child: createComposer(body: body, audioRecorderState: state), - ); - }, + final audioRecorderController = widget.props.audioRecorderController; + if (audioRecorderController == null) { + return DefaultStreamChatMessageComposer( + props: widget.props, + inputController: _controller, ); } - return createComposer(); - } - - core.VoiceRecordingCallback? createVoiceRecordingCallback() { - if (widget.props.audioRecorderController case final controller?) { - return core.VoiceRecordingCallback( - onLongPressStart: () async { - // Return if the recording is already started. - if (controller.isRecording) return; - - await feedback.onRecordStart(context); - return controller.startRecord(); - }, - onLongPressEnd: (_) async { - // Return if the recording not yet started or already locked. - if (!controller.isRecording || controller.isLocked) return; - - await feedback.onRecordFinish(context); - final audio = await controller.finishRecord(); - if (audio != null) { - _controller.addAttachment(audio); - } - - // Once the recording is finished, cancel the recorder. - controller.cancelRecord(discardTrack: false); - - // Send the message if the user has enabled the option to - // send the voice recording automatically. - if (widget.sendVoiceRecordingAutomatically) { - return widget.props.onSendPressed.call(); - } - }, - onLongPressCancel: () async { - // Return if the recording is already started. - if (controller.isRecording) return; - - // Notify the parent that the recorder is canceled before it starts. - await feedback.onRecordStartCancel(context); - // Show a message to the user to hold to record. - controller.showInfo( - context.translations.holdToRecordLabel, - ); - }, - onLongPressMoveUpdate: (details) async { - // Return if the recording not yet started or already locked. - if (!controller.isRecording || controller.isLocked) return; - final dragOffset = details.offsetFromOrigin; + return ValueListenableBuilder( + valueListenable: audioRecorderController, + builder: (context, state, _) { + final body = switch (state) { + RecordStateRecordingLocked() => MessageComposerRecordingLocked( + audioRecorderController: audioRecorderController, + feedback: widget.props.feedback, + messageInputController: _controller, + sendMessageCallback: widget.props.sendVoiceRecordingAutomatically ? widget.props.onSendPressed : null, + state: state, + ), + RecordStateStopped() => MessageComposerRecordingStopped( + audioRecorderController: audioRecorderController, + feedback: widget.props.feedback, + messageInputController: _controller, + sendMessageCallback: widget.props.sendVoiceRecordingAutomatically ? widget.props.onSendPressed : null, + recordingState: state, + ), + RecordStateRecording() => StreamMessageComposerRecordingOngoing( + audioRecorderController: audioRecorderController, + ), + _ => null, + }; - // Lock recording if the drag offset is greater than the threshold. - if (dragOffset.dy <= -lockRecordThreshold) { - await feedback.onRecordLock(context); - return controller.lockRecord(); - } - // Cancel recording if the drag offset is greater than the threshold. - if (dragOffset.dx <= -cancelRecordThreshold) { - await feedback.onRecordCancel(context); - return controller.cancelRecord(); - } + final streamSpacing = context.streamSpacing; - // Update the drag offset. - return controller.dragRecord(dragOffset); - }, - ); - } - return null; + return PortalTarget( + anchor: Aligned( + offset: Offset(-streamSpacing.md, -streamSpacing.md), + target: Alignment.topRight, + follower: Alignment.bottomRight, + ), + visible: state is RecordStateRecording, + portalFollower: SwipeToLockButton(isLocked: state is RecordStateRecordingLocked), + child: DefaultStreamChatMessageComposer( + props: widget.props, + inputController: _controller, + audioRecorderState: state, + body: body, + ), + ); + }, + ); } } @@ -260,6 +170,8 @@ class MessageComposerProps { this.focusNode, this.currentUserId, this.audioRecorderController, + this.sendVoiceRecordingAutomatically = false, + this.feedback = const AudioRecorderFeedback(), }); /// Whether the message composer is floating. @@ -285,6 +197,15 @@ class MessageComposerProps { /// The audio recorder controller. final StreamAudioRecorderController? audioRecorderController; + + /// Whether the voice recording should be sent automatically. + /// If enabled, the voice recording will be sent automatically when the recording is finished. + /// If disabled, the voice recording will be added as an attachment to the message + /// and the user will need to send the message manually. + final bool sendVoiceRecordingAutomatically; + + /// The feedback for the audio recorder. + final AudioRecorderFeedback feedback; } /// Properties to build any of the sub-components. @@ -353,3 +274,138 @@ extension on StreamAudioRecorderController { bool get isRecording => value is RecordStateRecording; bool get isLocked => isRecording && value is! RecordStateRecordingHold; } + +/// Default implementation of the message composer. +/// Shows the message composer with the default components. +/// Does not include the audio recording flow in the body. +class DefaultStreamChatMessageComposer extends StatelessWidget { + /// Creates a new instance of [DefaultStreamChatMessageComposer]. + /// [props] contains the properties for the message composer. + /// [inputController] is the controller for the message input. + /// [audioRecorderState] is the state of the audio recorder. + /// [body] is the body of the message composer. + const DefaultStreamChatMessageComposer({ + super.key, + required this.props, + required this.inputController, + this.audioRecorderState = const RecordStateIdle(), + this.body, + }); + + /// The properties for the message composer. + final MessageComposerProps props; + + /// The controller for the message input. + final StreamMessageInputController inputController; + + /// The state of the audio recorder. + /// Used for the microphone button state. + final AudioRecorderState audioRecorderState; + + /// The body of the message composer. + final Widget? body; + + /// The threshold to lock the recording. + static const double _lockRecordThreshold = 50; + + /// The threshold to cancel the recording. + static const double _cancelRecordThreshold = 75; + + @override + Widget build(BuildContext context) { + final componentProps = MessageComposerComponentProps( + controller: inputController, + isFloating: props.isFloating, + message: props.message, + currentUserId: props.currentUserId, + onSendPressed: props.onSendPressed, + voiceRecordingCallback: _createVoiceRecordingCallback(context), + onAttachmentButtonPressed: props.onAttachmentButtonPressed, + audioRecorderState: audioRecorderState, + focusNode: props.focusNode, + ); + + return core.StreamCoreMessageComposer( + placeholder: props.placeholder, + controller: inputController.textFieldController, + isFloating: props.isFloating, + focusNode: props.focusNode, + composerLeading: StreamMessageComposerLeading(props: componentProps), + composerTrailing: StreamMessageComposerTrailing( + props: componentProps, + ), + inputHeader: StreamMessageComposerInputHeader(props: componentProps), + inputTrailing: StreamMessageComposerInputTrailing( + props: componentProps, + ), + inputLeading: StreamMessageComposerInputLeading( + props: componentProps, + ), + inputBody: body, + ); + } + + core.VoiceRecordingCallback? _createVoiceRecordingCallback(BuildContext context) { + if (props.audioRecorderController case final audioRecorderController?) { + return core.VoiceRecordingCallback( + onLongPressStart: () async { + // Return if the recording is already started. + if (audioRecorderController.isRecording) return; + + await props.feedback.onRecordStart(context); + return audioRecorderController.startRecord(); + }, + onLongPressEnd: (_) async { + // Return if the recording not yet started or already locked. + if (!audioRecorderController.isRecording || audioRecorderController.isLocked) return; + + await props.feedback.onRecordFinish(context); + final audio = await audioRecorderController.finishRecord(); + if (audio != null) { + inputController.addAttachment(audio); + } + + // Once the recording is finished, cancel the recorder. + audioRecorderController.cancelRecord(discardTrack: false); + + // Send the message if the user has enabled the option to + // send the voice recording automatically. + if (props.sendVoiceRecordingAutomatically) { + return props.onSendPressed.call(); + } + }, + onLongPressCancel: () async { + // Return if the recording is already started. + if (audioRecorderController.isRecording) return; + + // Notify the parent that the recorder is canceled before it starts. + await props.feedback.onRecordStartCancel(context); + // Show a message to the user to hold to record. + audioRecorderController.showInfo( + context.translations.holdToRecordLabel, + ); + }, + onLongPressMoveUpdate: (details) async { + // Return if the recording not yet started or already locked. + if (!audioRecorderController.isRecording || audioRecorderController.isLocked) return; + final dragOffset = details.offsetFromOrigin; + + // Lock recording if the drag offset is greater than the threshold. + if (dragOffset.dy <= -_lockRecordThreshold) { + await props.feedback.onRecordLock(context); + return audioRecorderController.lockRecord(); + } + // Cancel recording if the drag offset is greater than the threshold. + if (dragOffset.dx <= -_cancelRecordThreshold) { + await props.feedback.onRecordCancel(context); + return audioRecorderController.cancelRecord(); + } + + // Update the drag offset. + return audioRecorderController.dragRecord(dragOffset); + }, + ); + } + return null; + } +} diff --git a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart index e77b8e811b..34d01c1745 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart @@ -9,7 +9,6 @@ import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/message_input/audio_recorder/audio_recorder_feedback.dart'; import 'package:stream_chat_flutter/src/message_input/audio_recorder/audio_recorder_state.dart'; import 'package:stream_chat_flutter/src/message_input/stream_message_input_icon_button.dart'; -import 'package:stream_chat_flutter/src/misc/audio_waveform.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart index a8949bed4b..f67ffb2ca7 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart @@ -89,13 +89,13 @@ class StreamMessageInputAttachmentList extends StatefulWidget { class _StreamMessageInputAttachmentListState extends State { late List _audioAttachments = widget._audioAttachments; - late final _controller = StreamAudioPlaylistController(_audioAttachments.toPlaylist()); + StreamAudioPlaylistController? _controller; late final _scrollController = ScrollController(); @override void initState() { super.initState(); - _controller.initialize(); + _updateController(); } @override @@ -108,7 +108,7 @@ class _StreamMessageInputAttachmentListState extends State it.key == attachment).first; return StreamVoiceRecordingAttachment( + title: 'Voice Message', + showTitle: true, track: track, speed: state.speed, - trailingBuilder: (_, __, ___, ____) { - return RemoveAttachmentButton( - onPressed: switch (onRemovePressed) { - final callback? => () => callback(attachment), - _ => null, - }, - ); + onRemovePressed: switch (onRemovePressed) { + final callback? => () => callback(attachment), + _ => null, }, onTrackPause: controller.pause, onChangeSpeed: controller.setSpeed, diff --git a/packages/stream_chat_flutter/lib/src/misc/audio_waveform.dart b/packages/stream_chat_flutter/lib/src/misc/audio_waveform.dart deleted file mode 100644 index 05bcea0085..0000000000 --- a/packages/stream_chat_flutter/lib/src/misc/audio_waveform.dart +++ /dev/null @@ -1,486 +0,0 @@ -import 'dart:math' as math; - -import 'package:collection/collection.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/theme/audio_waveform_slider_theme.dart'; -import 'package:stream_chat_flutter/src/theme/audio_waveform_theme.dart'; - -const _kAudioWaveformSliderThumbWidth = 4.0; -const _kAudioWaveformSliderThumbHeight = 28.0; - -/// {@template streamAudioWaveformSlider} -/// A widget that displays an audio waveform and allows the user to interact -/// with it using a slider. -/// {@endtemplate} -class StreamAudioWaveformSlider extends StatefulWidget { - /// {@macro streamAudioWaveformSlider} - const StreamAudioWaveformSlider({ - super.key, - required this.waveform, - this.onChangeStart, - required this.onChanged, - this.onChangeEnd, - this.limit = 100, - this.color, - this.progress = 0, - this.progressColor, - this.minBarHeight, - this.spacingRatio, - this.heightScale, - this.inverse = true, - this.thumbColor, - this.thumbBorderColor, - }); - - /// The waveform data to be drawn. - /// - /// Note: The values should be between 0 and 1. - final List waveform; - - /// Called when the thumb starts being dragged. - final ValueChanged? onChangeStart; - - /// Called while the thumb is being dragged. - final ValueChanged? onChanged; - - /// Called when the thumb stops being dragged. - final ValueChanged? onChangeEnd; - - /// The color of the wave bars. - /// - /// Defaults to [StreamAudioWaveformSliderThemeData.color]. - final Color? color; - - /// The number of wave bars that will be draw in the screen. When the length - /// of [waveform] is bigger than [limit] only the X last bars will be shown. - /// - /// Defaults to 100. - final int limit; - - /// The progress of the audio track. Used to show the progress of the audio. - /// - /// Defaults to 0. - final double progress; - - /// The color of the progressed wave bars. - /// - /// Defaults to [StreamAudioWaveformSliderThemeData.progressColor]. - final Color? progressColor; - - /// The minimum height of the bars. - /// - /// Defaults to [StreamAudioWaveformSliderThemeData.minBarHeight]. - final double? minBarHeight; - - /// The ratio of the spacing between the bars. - /// - /// Defaults to [StreamAudioWaveformSliderThemeData.spacingRatio]. - final double? spacingRatio; - - /// The scale of the height of the bars. - /// - /// Defaults to [StreamAudioWaveformSliderThemeData.heightScale]. - final double? heightScale; - - /// If true, the bars grow from right to left otherwise they grow from left - /// to right. - /// - /// Defaults to true. - final bool inverse; - - /// The color of the slider thumb. - /// - /// Defaults to [StreamAudioWaveformSliderThemeData.thumbColor]. - final Color? thumbColor; - - /// The color of the slider thumb border. - /// - /// Defaults to [StreamAudioWaveformSliderThemeData.thumbBorderColor]. - final Color? thumbBorderColor; - - @override - State createState() => _StreamAudioWaveformSliderState(); -} - -class _StreamAudioWaveformSliderState extends State { - @override - Widget build(BuildContext context) { - final theme = StreamAudioWaveformSliderTheme.of(context); - final waveformTheme = theme.audioWaveformTheme; - - final color = widget.color ?? waveformTheme!.color!; - final progressColor = widget.progressColor ?? waveformTheme!.progressColor!; - final minBarHeight = widget.minBarHeight ?? waveformTheme!.minBarHeight!; - final spacingRatio = widget.spacingRatio ?? waveformTheme!.spacingRatio!; - final heightScale = widget.heightScale ?? waveformTheme!.heightScale!; - final thumbColor = widget.thumbColor ?? theme.thumbColor!; - final thumbBorderColor = widget.thumbBorderColor ?? theme.thumbBorderColor!; - - return HorizontalSlider( - onChangeStart: widget.onChangeStart, - onChanged: widget.onChanged, - onChangeEnd: widget.onChangeEnd, - child: LayoutBuilder( - builder: (context, constraints) => Stack( - fit: StackFit.expand, - clipBehavior: Clip.none, - alignment: Alignment.center, - children: [ - StreamAudioWaveform( - waveform: widget.waveform, - limit: widget.limit, - color: color, - progress: widget.progress, - progressColor: progressColor, - minBarHeight: minBarHeight, - spacingRatio: spacingRatio, - heightScale: heightScale, - inverse: widget.inverse, - ), - Builder( - // Just using it for the calculation of the thumb position. - builder: (context) { - final progressWidth = constraints.maxWidth * widget.progress; - return AnimatedPositioned( - curve: const ElasticOutCurve(1.05), - duration: const Duration(milliseconds: 300), - left: progressWidth - _kAudioWaveformSliderThumbWidth / 2, - child: StreamAudioWaveformSliderThumb( - color: thumbColor, - borderColor: thumbBorderColor, - height: constraints.maxHeight, - ), - ); - }, - ), - ], - ), - ), - ); - } -} - -/// {@template streamAudioWaveformSliderThumb} -/// A widget that represents the thumb of the [StreamAudioWaveformSlider]. -/// {@endtemplate} -class StreamAudioWaveformSliderThumb extends StatelessWidget { - /// {@macro streamAudioWaveformSliderThumb} - const StreamAudioWaveformSliderThumb({ - super.key, - this.width = _kAudioWaveformSliderThumbWidth, - this.height = _kAudioWaveformSliderThumbHeight, - this.color = Colors.white, - this.borderColor = const Color(0xffecebeb), - }); - - /// The width of the thumb. - final double width; - - /// The height of the thumb. - final double height; - - /// The color of the thumb. - final Color color; - - /// The border color of the thumb. - final Color borderColor; - - @override - Widget build(BuildContext context) { - return Container( - width: width, - height: height, - decoration: BoxDecoration( - color: color, - border: Border.all( - color: borderColor, - strokeAlign: BorderSide.strokeAlignOutside, - ), - borderRadius: BorderRadius.circular(2), - ), - ); - } -} - -/// {@template streamAudioWaveform} -/// A widget that displays an audio waveform. -/// -/// The waveform is drawn using the [waveform] data. The waveform is drawn -/// horizontally and the bars grow from right to left. -/// {@endtemplate} -class StreamAudioWaveform extends StatelessWidget { - /// {@macro streamAudioWaveform} - const StreamAudioWaveform({ - super.key, - required this.waveform, - this.limit = 100, - this.color, - this.progress = 0, - this.progressColor, - this.minBarHeight, - this.spacingRatio, - this.heightScale, - this.inverse = true, - }); - - /// The waveform data to be drawn. - /// - /// Note: The values should be between 0 and 1. - final List waveform; - - /// The color of the wave bars. - /// - /// Defaults to [StreamAudioWaveformThemeData.color]. - final Color? color; - - /// The number of wave bars that will be draw in the screen. When the length - /// of [waveform] is bigger than [limit] only the X last bars will be shown. - /// - /// Defaults to 100. - final int limit; - - /// The progress of the audio track. Used to show the progress of the audio. - /// - /// Defaults to 0. - final double progress; - - /// The color of the progressed wave bars. - /// - /// Defaults to [StreamAudioWaveformThemeData.progressColor]. - final Color? progressColor; - - /// The minimum height of the bars. - /// - /// Defaults to [StreamAudioWaveformThemeData.minBarHeight]. - final double? minBarHeight; - - /// The ratio of the spacing between the bars. - /// - /// Defaults to [StreamAudioWaveformThemeData.spacingRatio]. - final double? spacingRatio; - - /// The scale of the height of the bars. - /// - /// Defaults to [StreamAudioWaveformThemeData.heightScale]. - final double? heightScale; - - /// If true, the bars grow from right to left otherwise they grow from left - /// to right. - /// - /// Defaults to true. - final bool inverse; - - @override - Widget build(BuildContext context) { - final theme = StreamAudioWaveformTheme.of(context); - - final color = this.color ?? theme.color!; - final progressColor = this.progressColor ?? theme.progressColor!; - final minBarHeight = this.minBarHeight ?? theme.minBarHeight!; - final spacingRatio = this.spacingRatio ?? theme.spacingRatio!; - final heightScale = this.heightScale ?? theme.heightScale!; - - return CustomPaint( - willChange: true, - painter: _WaveformPainter( - waveform: waveform.reversed, - limit: limit, - color: color, - progress: progress, - progressColor: progressColor, - minBarHeight: minBarHeight, - spacingRatio: spacingRatio, - heightScale: heightScale, - inverse: inverse, - ), - ); - } -} - -class _WaveformPainter extends CustomPainter { - _WaveformPainter({ - required Iterable waveform, - this.limit = 100, - this.color = const Color(0xff7E828B), - this.progress = 0, - this.progressColor = const Color(0xff005FFF), - this.minBarHeight = 2, - double spacingRatio = 0.3, - this.heightScale = 1, - this.inverse = true, - }) : waveform = [ - ...waveform.take(limit), - if (waveform.length < limit) - // Fill the remaining bars with 0 value - ...List.filled(limit - waveform.length, 0), - ], - spacingRatio = spacingRatio.clamp(0, 1); - - final List waveform; - final Color color; - final int limit; - final double progress; - final Color progressColor; - final double minBarHeight; - final double spacingRatio; - final bool inverse; - final double heightScale; - - @override - void paint(Canvas canvas, Size size) { - final canvasWidth = size.width; - final canvasHeight = size.height; - - // The total spacing between the bars in the canvas. - final spacingWidth = canvasWidth * spacingRatio; - final barsWidth = canvasWidth - spacingWidth; - final barWidth = barsWidth / limit; - final barSpacing = spacingWidth / (limit - 1); - final progressWidth = progress * canvasWidth; - - void _paintBar(int index, double barValue) { - var dx = index * (barWidth + barSpacing) + barWidth / 2; - if (inverse) dx = canvasWidth - dx; - final dy = canvasHeight / 2; - - final barHeight = math.max(barValue * canvasHeight, minBarHeight); - - final rect = RRect.fromRectAndRadius( - Rect.fromCenter( - center: Offset(dx, dy), - width: barWidth, - height: barHeight, - ), - const Radius.circular(2), - ); - - final waveColor = switch (dx <= progressWidth) { - true => progressColor, - false => color, - }; - - final wavePaint = Paint() - ..color = waveColor - ..strokeCap = StrokeCap.round; - - canvas.drawRRect(rect, wavePaint); - } - - // Paint all the bars - waveform.forEachIndexed(_paintBar); - } - - @override - bool shouldRepaint(covariant _WaveformPainter oldDelegate) => - !const ListEquality().equals(waveform, oldDelegate.waveform) || - color != oldDelegate.color || - limit != oldDelegate.limit || - progress != oldDelegate.progress || - progressColor != oldDelegate.progressColor || - minBarHeight != oldDelegate.minBarHeight || - spacingRatio != oldDelegate.spacingRatio || - heightScale != oldDelegate.heightScale || - inverse != oldDelegate.inverse; -} - -/// {@template horizontalSlider} -/// A widget that allows interactive horizontal sliding gestures. -/// -/// The `HorizontalSlider` widget wraps a child widget and allows users to -/// perform sliding gestures horizontally. It can be configured with callbacks -/// to notify the parent widget about the changes in the horizontal value. -/// {@endtemplate} -class HorizontalSlider extends StatefulWidget { - /// Creates a horizontal slider. - const HorizontalSlider({ - super.key, - required this.child, - required this.onChanged, - this.onChangeStart, - this.onChangeEnd, - }); - - /// The child widget wrapped by the slider. - final Widget child; - - /// Called when the horizontal value starts changing. - final ValueChanged? onChangeStart; - - /// Called when the horizontal value changes. - final ValueChanged? onChanged; - - /// Called when the horizontal value stops changing. - final ValueChanged? onChangeEnd; - - @override - State createState() => _HorizontalSliderState(); -} - -class _HorizontalSliderState extends State { - bool _active = false; - - /// Returns true if the slider is interactive. - bool get isInteractive => widget.onChanged != null; - - /// Converts the visual position to a value based on the text direction. - double _getValueFromVisualPosition(double visualPosition) { - final textDirection = Directionality.of(context); - final value = switch (textDirection) { - TextDirection.rtl => 1.0 - visualPosition, - TextDirection.ltr => visualPosition, - }; - - return clampDouble(value, 0, 1); - } - - /// Converts the local position to a horizontal value. - double _getValueFromLocalPosition(Offset globalPosition) { - final box = context.findRenderObject()! as RenderBox; - final localPosition = box.globalToLocal(globalPosition); - final visualPosition = localPosition.dx / box.size.width; - return _getValueFromVisualPosition(visualPosition); - } - - void _handleDragStart(DragStartDetails details) { - if (!_active && isInteractive) { - _active = true; - final value = _getValueFromLocalPosition(details.globalPosition); - widget.onChangeStart?.call(value); - } - } - - void _handleDragUpdate(DragUpdateDetails details) { - _handleHorizontalDrag(details.globalPosition); - } - - void _handleDragEnd(DragEndDetails details) { - if (!mounted) return; - - if (_active && mounted) { - final value = _getValueFromLocalPosition(details.globalPosition); - widget.onChangeEnd?.call(value); - _active = false; - } - } - - /// Handles the sliding gesture. - void _handleHorizontalDrag(Offset globalPosition) { - if (!mounted) return; - - if (isInteractive) { - final value = _getValueFromLocalPosition(globalPosition); - widget.onChanged?.call(value); - } - } - - @override - Widget build(BuildContext context) { - return GestureDetector( - onHorizontalDragStart: _handleDragStart, - onHorizontalDragUpdate: _handleDragUpdate, - onHorizontalDragEnd: _handleDragEnd, - child: widget.child, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/theme/audio_waveform_slider_theme.dart b/packages/stream_chat_flutter/lib/src/theme/audio_waveform_slider_theme.dart deleted file mode 100644 index 3e2b1062cc..0000000000 --- a/packages/stream_chat_flutter/lib/src/theme/audio_waveform_slider_theme.dart +++ /dev/null @@ -1,129 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/theme/audio_waveform_theme.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; - -/// {@template streamAudioWaveformSliderTheme} -/// Overrides the default style of [StreamAudioWaveformSlider] descendants. -/// -/// See also: -/// -/// * [StreamVoiceRecordingAttachmentThemeData], which is used to configure -/// this theme. -/// {@endtemplate} -class StreamAudioWaveformSliderTheme extends InheritedTheme { - /// Creates a [StreamAudioWaveformSliderTheme]. - /// - /// The [data] parameter must not be null. - const StreamAudioWaveformSliderTheme({ - super.key, - required this.data, - required super.child, - }); - - /// The configuration of this theme. - final StreamAudioWaveformSliderThemeData data; - - /// The closest instance of this class that encloses the given context. - /// - /// If there is no enclosing [StreamAudioWaveformSliderTheme] widget, - /// then [StreamAudioWaveformSliderTheme.audioWaveformSliderTheme] is used. - /// - /// Typical usage is as follows: - /// - /// ```dart - /// StreamAudioWaveformSliderTheme theme = - /// StreamAudioWaveformSliderTheme.of(context); - /// ``` - static StreamAudioWaveformSliderThemeData of(BuildContext context) { - final audioWaveformSliderTheme = context.dependOnInheritedWidgetOfExactType(); - return audioWaveformSliderTheme?.data ?? StreamChatTheme.of(context).audioWaveformSliderTheme; - } - - @override - Widget wrap(BuildContext context, Widget child) => StreamAudioWaveformSliderTheme(data: data, child: child); - - @override - bool updateShouldNotify(StreamAudioWaveformSliderTheme oldWidget) => data != oldWidget.data; -} - -/// {@template streamAudioWaveformSliderThemeData} -/// A style that overrides the default appearance of -/// [StreamAudioWaveformSlider] widgets when used with -/// [StreamAudioWaveformSliderTheme] or with the overall -/// [StreamChatTheme]'s [StreamChatThemeData.audioWaveformSliderTheme]. -/// {@endtemplate} -class StreamAudioWaveformSliderThemeData with Diagnosticable { - /// {@macro streamVoiceRecordingAttachmentThemeData} - const StreamAudioWaveformSliderThemeData({ - this.audioWaveformTheme, - this.thumbColor, - this.thumbBorderColor, - }); - - /// The theme of the audio waveform. - final StreamAudioWaveformThemeData? audioWaveformTheme; - - /// The color of the thumb. - final Color? thumbColor; - - /// The color of the thumb border. - final Color? thumbBorderColor; - - /// A copy of [StreamAudioWaveformSliderThemeData] with specified attributes - /// overridden. - StreamAudioWaveformSliderThemeData copyWith({ - StreamAudioWaveformThemeData? audioWaveformTheme, - Color? thumbColor, - Color? thumbBorderColor, - }) { - return StreamAudioWaveformSliderThemeData( - audioWaveformTheme: audioWaveformTheme ?? this.audioWaveformTheme, - thumbColor: thumbColor ?? this.thumbColor, - thumbBorderColor: thumbBorderColor ?? this.thumbBorderColor, - ); - } - - /// Merges this [StreamPollOptionsDialogThemeData] with the [other]. - StreamAudioWaveformSliderThemeData merge( - StreamAudioWaveformSliderThemeData? other, - ) { - if (other == null) return this; - return copyWith( - audioWaveformTheme: other.audioWaveformTheme, - thumbColor: other.thumbColor, - thumbBorderColor: other.thumbBorderColor, - ); - } - - /// Linearly interpolate between two [StreamPollOptionsDialogThemeData]. - static StreamAudioWaveformSliderThemeData lerp( - StreamAudioWaveformSliderThemeData a, - StreamAudioWaveformSliderThemeData b, - double t, - ) => StreamAudioWaveformSliderThemeData( - audioWaveformTheme: StreamAudioWaveformThemeData.lerp(a.audioWaveformTheme!, b.audioWaveformTheme!, t), - thumbColor: Color.lerp(a.thumbColor, b.thumbColor, t), - thumbBorderColor: Color.lerp(a.thumbBorderColor, b.thumbBorderColor, t), - ); - - @override - bool operator ==(Object other) => - identical(this, other) || - other is StreamAudioWaveformSliderThemeData && - other.audioWaveformTheme == audioWaveformTheme && - other.thumbColor == thumbColor && - other.thumbBorderColor == thumbBorderColor; - - @override - int get hashCode => audioWaveformTheme.hashCode ^ thumbColor.hashCode ^ thumbBorderColor.hashCode; - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty('audioWaveformTheme', audioWaveformTheme)) - ..add(ColorProperty('thumbColor', thumbColor)) - ..add(ColorProperty('thumbBorderColor', thumbBorderColor)); - } -} diff --git a/packages/stream_chat_flutter/lib/src/theme/audio_waveform_theme.dart b/packages/stream_chat_flutter/lib/src/theme/audio_waveform_theme.dart deleted file mode 100644 index d7f81148df..0000000000 --- a/packages/stream_chat_flutter/lib/src/theme/audio_waveform_theme.dart +++ /dev/null @@ -1,150 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; - -/// {@template streamAudioWaveformTheme} -/// Overrides the default style of [StreamAudioWaveform] descendants. -/// -/// See also: -/// -/// * [StreamVoiceRecordingAttachmentThemeData], which is used to configure -/// this theme. -/// {@endtemplate} -class StreamAudioWaveformTheme extends InheritedTheme { - /// Creates a [StreamAudioWaveformTheme]. - /// - /// The [data] parameter must not be null. - const StreamAudioWaveformTheme({ - super.key, - required this.data, - required super.child, - }); - - /// The configuration of this theme. - final StreamAudioWaveformThemeData data; - - /// The closest instance of this class that encloses the given context. - /// - /// If there is no enclosing [StreamAudioWaveformTheme] widget, - /// then [StreamAudioWaveformTheme.audioWaveformSliderTheme] is used. - /// - /// Typical usage is as follows: - /// - /// ```dart - /// StreamAudioWaveformTheme theme = StreamAudioWaveformTheme.of(context); - /// ``` - static StreamAudioWaveformThemeData of(BuildContext context) { - final audioWaveformTheme = context.dependOnInheritedWidgetOfExactType(); - return audioWaveformTheme?.data ?? StreamChatTheme.of(context).audioWaveformTheme; - } - - @override - Widget wrap(BuildContext context, Widget child) => StreamAudioWaveformTheme(data: data, child: child); - - @override - bool updateShouldNotify(StreamAudioWaveformTheme oldWidget) => data != oldWidget.data; -} - -/// {@template streamVoiceRecordingAttachmentThemeData} -/// A style that overrides the default appearance of -/// [StreamAudioWaveformSlider] widgets when used with -/// [StreamAudioWaveformTheme] or with the overall -/// [StreamChatTheme]'s [StreamChatThemeData.audioWaveformSliderTheme]. -/// {@endtemplate} -class StreamAudioWaveformThemeData with Diagnosticable { - /// {@macro streamAudioWaveformThemeData} - const StreamAudioWaveformThemeData({ - this.color, - this.progressColor, - this.minBarHeight, - this.spacingRatio, - this.heightScale, - }); - - /// The color of the wave bars. - final Color? color; - - /// The color of the progressed wave bars. - final Color? progressColor; - - /// The minimum height of the bars. - final double? minBarHeight; - - /// The ratio of the spacing between the bars. - final double? spacingRatio; - - /// The scale of the height of the bars. - final double? heightScale; - - /// A copy of [StreamAudioWaveformThemeData] with specified attributes - /// overridden. - StreamAudioWaveformThemeData copyWith({ - Color? color, - Color? progressColor, - double? minBarHeight, - double? spacingRatio, - double? heightScale, - }) { - return StreamAudioWaveformThemeData( - color: color ?? this.color, - progressColor: progressColor ?? this.progressColor, - minBarHeight: minBarHeight ?? this.minBarHeight, - spacingRatio: spacingRatio ?? this.spacingRatio, - heightScale: heightScale ?? this.heightScale, - ); - } - - /// Merges this [StreamPollOptionsDialogThemeData] with the [other]. - StreamAudioWaveformThemeData merge( - StreamAudioWaveformThemeData? other, - ) { - if (other == null) return this; - return copyWith( - color: other.color, - progressColor: other.progressColor, - minBarHeight: other.minBarHeight, - spacingRatio: other.spacingRatio, - heightScale: other.heightScale, - ); - } - - /// Linearly interpolate between two [StreamPollOptionsDialogThemeData]. - static StreamAudioWaveformThemeData lerp( - StreamAudioWaveformThemeData a, - StreamAudioWaveformThemeData b, - double t, - ) => StreamAudioWaveformThemeData( - color: Color.lerp(a.color, b.color, t), - progressColor: Color.lerp(a.progressColor, b.progressColor, t), - minBarHeight: lerpDouble(a.minBarHeight, b.minBarHeight, t), - spacingRatio: lerpDouble(a.spacingRatio, b.spacingRatio, t), - heightScale: lerpDouble(a.heightScale, b.heightScale, t), - ); - - @override - bool operator ==(Object other) => - identical(this, other) || - other is StreamAudioWaveformThemeData && - other.color == color && - other.progressColor == progressColor && - other.minBarHeight == minBarHeight && - other.spacingRatio == spacingRatio && - other.heightScale == heightScale; - - @override - int get hashCode => - color.hashCode ^ progressColor.hashCode ^ minBarHeight.hashCode ^ spacingRatio.hashCode ^ heightScale.hashCode; - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(ColorProperty('color', color)) - ..add(ColorProperty('progressColor', progressColor)) - ..add(DoubleProperty('minBarHeight', minBarHeight)) - ..add(DoubleProperty('spacingRatio', spacingRatio)) - ..add(DoubleProperty('heightScale', heightScale)); - } -} diff --git a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart index 4cb81111f6..572dd3c3f2 100644 --- a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart @@ -1,7 +1,6 @@ // ignore_for_file: deprecated_member_use_from_same_package import 'package:flutter/material.dart' hide TextTheme; -import 'package:stream_chat_flutter/src/misc/audio_waveform.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@template streamChatTheme} @@ -63,8 +62,6 @@ class StreamChatThemeData { StreamPollOptionVotesDialogThemeData? pollOptionVotesDialogTheme, StreamThreadListTileThemeData? threadListTileTheme, StreamDraftListTileThemeData? draftListTileTheme, - StreamAudioWaveformThemeData? audioWaveformTheme, - StreamAudioWaveformSliderThemeData? audioWaveformSliderTheme, StreamVoiceRecordingAttachmentThemeData? voiceRecordingAttachmentTheme, }) { brightness ??= colorTheme?.brightness ?? Brightness.light; @@ -97,8 +94,6 @@ class StreamChatThemeData { pollOptionVotesDialogTheme: pollOptionVotesDialogTheme, threadListTileTheme: threadListTileTheme, draftListTileTheme: draftListTileTheme, - audioWaveformTheme: audioWaveformTheme, - audioWaveformSliderTheme: audioWaveformSliderTheme, voiceRecordingAttachmentTheme: voiceRecordingAttachmentTheme, ); @@ -133,8 +128,6 @@ class StreamChatThemeData { required this.pollOptionVotesDialogTheme, required this.threadListTileTheme, required this.draftListTileTheme, - required this.audioWaveformTheme, - required this.audioWaveformSliderTheme, required this.voiceRecordingAttachmentTheme, }); @@ -191,20 +184,6 @@ class StreamChatThemeData { indicatorIconSize: 16, ); - final audioWaveformTheme = StreamAudioWaveformThemeData( - color: colorTheme.textLowEmphasis, - progressColor: colorTheme.accentPrimary, - minBarHeight: 2, - spacingRatio: 0.3, - heightScale: 1, - ); - - final audioWaveformSliderTheme = StreamAudioWaveformSliderThemeData( - audioWaveformTheme: audioWaveformTheme, - thumbColor: Colors.white, - thumbBorderColor: colorTheme.borders, - ); - return StreamChatThemeData.raw( textTheme: textTheme, colorTheme: colorTheme, @@ -545,47 +524,7 @@ class StreamChatThemeData { color: colorTheme.textLowEmphasis, ), ), - audioWaveformTheme: audioWaveformTheme, - audioWaveformSliderTheme: audioWaveformSliderTheme, - voiceRecordingAttachmentTheme: StreamVoiceRecordingAttachmentThemeData( - backgroundColor: colorTheme.barsBg, - playIcon: const StreamSvgIcon(icon: StreamSvgIcons.play), - pauseIcon: const StreamSvgIcon(icon: StreamSvgIcons.pause), - loadingIndicator: SizedBox.fromSize( - size: const Size.square(24 - /* Padding */ 2), - child: Center( - child: CircularProgressIndicator.adaptive( - valueColor: AlwaysStoppedAnimation(colorTheme.accentPrimary), - ), - ), - ), - audioControlButtonStyle: ElevatedButton.styleFrom( - elevation: 2, - iconColor: Colors.black, - padding: const EdgeInsets.symmetric(horizontal: 6), - backgroundColor: Colors.white, - shape: const CircleBorder(), - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - minimumSize: const Size(36, 36), - ), - titleTextStyle: textTheme.bodyBold.copyWith( - color: colorTheme.textHighEmphasis, - ), - durationTextStyle: textTheme.footnote.copyWith( - color: colorTheme.textLowEmphasis, - ), - speedControlButtonStyle: ElevatedButton.styleFrom( - elevation: 2, - textStyle: textTheme.footnote, - foregroundColor: Colors.black, - padding: const EdgeInsets.symmetric(horizontal: 8), - backgroundColor: Colors.white, - shape: const StadiumBorder(), - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - minimumSize: const Size(40, 28), - ), - audioWaveformSliderTheme: audioWaveformSliderTheme, - ), + voiceRecordingAttachmentTheme: const StreamVoiceRecordingAttachmentThemeData(), ); } @@ -648,12 +587,6 @@ class StreamChatThemeData { /// Theme configuration for the [StreamThreadListTile] widget. final StreamThreadListTileThemeData threadListTileTheme; - /// Theme configuration for the [StreamAudioWaveform] widget. - final StreamAudioWaveformThemeData audioWaveformTheme; - - /// Theme configuration for the [StreamAudioWaveformSlider] widget. - final StreamAudioWaveformSliderThemeData audioWaveformSliderTheme; - /// Theme configuration for the [StreamVoiceRecordingAttachment] widget. final StreamVoiceRecordingAttachmentThemeData voiceRecordingAttachmentTheme; @@ -694,8 +627,6 @@ class StreamChatThemeData { StreamPollOptionVotesDialogThemeData? pollOptionVotesDialogTheme, StreamThreadListTileThemeData? threadListTileTheme, StreamDraftListTileThemeData? draftListTileTheme, - StreamAudioWaveformThemeData? audioWaveformTheme, - StreamAudioWaveformSliderThemeData? audioWaveformSliderTheme, StreamVoiceRecordingAttachmentThemeData? voiceRecordingAttachmentTheme, }) => StreamChatThemeData.raw( channelListHeaderTheme: this.channelListHeaderTheme.merge(channelListHeaderTheme), @@ -718,8 +649,6 @@ class StreamChatThemeData { pollOptionVotesDialogTheme: pollOptionVotesDialogTheme ?? this.pollOptionVotesDialogTheme, threadListTileTheme: threadListTileTheme ?? this.threadListTileTheme, draftListTileTheme: draftListTileTheme ?? this.draftListTileTheme, - audioWaveformTheme: audioWaveformTheme ?? this.audioWaveformTheme, - audioWaveformSliderTheme: audioWaveformSliderTheme ?? this.audioWaveformSliderTheme, voiceRecordingAttachmentTheme: voiceRecordingAttachmentTheme ?? this.voiceRecordingAttachmentTheme, ); @@ -747,8 +676,6 @@ class StreamChatThemeData { pollOptionVotesDialogTheme: pollOptionVotesDialogTheme.merge(other.pollOptionVotesDialogTheme), threadListTileTheme: threadListTileTheme.merge(other.threadListTileTheme), draftListTileTheme: draftListTileTheme.merge(other.draftListTileTheme), - audioWaveformTheme: audioWaveformTheme.merge(other.audioWaveformTheme), - audioWaveformSliderTheme: audioWaveformSliderTheme.merge(other.audioWaveformSliderTheme), voiceRecordingAttachmentTheme: voiceRecordingAttachmentTheme.merge(other.voiceRecordingAttachmentTheme), ); } diff --git a/packages/stream_chat_flutter/lib/src/theme/themes.dart b/packages/stream_chat_flutter/lib/src/theme/themes.dart index d24070676d..8f1558bdc3 100644 --- a/packages/stream_chat_flutter/lib/src/theme/themes.dart +++ b/packages/stream_chat_flutter/lib/src/theme/themes.dart @@ -1,5 +1,3 @@ -export 'audio_waveform_slider_theme.dart'; -export 'audio_waveform_theme.dart'; export 'avatar_theme.dart'; export 'channel_header_theme.dart'; export 'channel_list_header_theme.dart'; diff --git a/packages/stream_chat_flutter/lib/src/theme/voice_recording_attachment_theme.dart b/packages/stream_chat_flutter/lib/src/theme/voice_recording_attachment_theme.dart index d7cfbf2949..64867c902f 100644 --- a/packages/stream_chat_flutter/lib/src/theme/voice_recording_attachment_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/voice_recording_attachment_theme.dart @@ -1,6 +1,5 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/theme/audio_waveform_slider_theme.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; /// {@template streamVoiceRecordingAttachmentTheme} @@ -57,31 +56,14 @@ class StreamVoiceRecordingAttachmentThemeData with Diagnosticable { /// {@macro streamVoiceRecordingAttachmentThemeData} const StreamVoiceRecordingAttachmentThemeData({ this.backgroundColor, - this.playIcon, - this.pauseIcon, - this.loadingIndicator, - this.audioControlButtonStyle, this.titleTextStyle, this.durationTextStyle, this.speedControlButtonStyle, - this.audioWaveformSliderTheme, }); /// The background color of the attachment. final Color? backgroundColor; - /// The icon widget to show when the recording is playing. - final Widget? playIcon; - - /// The icon widget to show when the recording is paused. - final Widget? pauseIcon; - - /// The widget to show when the recording is loading. - final Widget? loadingIndicator; - - /// The style for the audio control button. - final ButtonStyle? audioControlButtonStyle; - /// The text style for the title. final TextStyle? titleTextStyle; @@ -91,31 +73,18 @@ class StreamVoiceRecordingAttachmentThemeData with Diagnosticable { /// The style for the speed control button. final ButtonStyle? speedControlButtonStyle; - /// The theme for the audio waveform slider. - final StreamAudioWaveformSliderThemeData? audioWaveformSliderTheme; - /// A copy of [StreamVoiceRecordingAttachmentThemeData] with specified /// attributes overridden. StreamVoiceRecordingAttachmentThemeData copyWith({ Color? backgroundColor, - Widget? playIcon, - Widget? pauseIcon, - Widget? loadingIndicator, - ButtonStyle? audioControlButtonStyle, TextStyle? titleTextStyle, TextStyle? durationTextStyle, ButtonStyle? speedControlButtonStyle, - StreamAudioWaveformSliderThemeData? audioWaveformSliderTheme, }) => StreamVoiceRecordingAttachmentThemeData( backgroundColor: backgroundColor ?? this.backgroundColor, - playIcon: playIcon ?? this.playIcon, - pauseIcon: pauseIcon ?? this.pauseIcon, - loadingIndicator: loadingIndicator ?? this.loadingIndicator, - audioControlButtonStyle: audioControlButtonStyle ?? this.audioControlButtonStyle, titleTextStyle: titleTextStyle ?? this.titleTextStyle, durationTextStyle: durationTextStyle ?? this.durationTextStyle, speedControlButtonStyle: speedControlButtonStyle ?? this.speedControlButtonStyle, - audioWaveformSliderTheme: audioWaveformSliderTheme ?? this.audioWaveformSliderTheme, ); /// Merges this [StreamVoiceRecordingAttachmentThemeData] with the [other]. @@ -125,16 +94,9 @@ class StreamVoiceRecordingAttachmentThemeData with Diagnosticable { if (other == null) return this; return copyWith( backgroundColor: other.backgroundColor, - playIcon: other.playIcon, - pauseIcon: other.pauseIcon, - loadingIndicator: other.loadingIndicator, - audioControlButtonStyle: other.audioControlButtonStyle, titleTextStyle: other.titleTextStyle, durationTextStyle: other.durationTextStyle, speedControlButtonStyle: other.speedControlButtonStyle, - audioWaveformSliderTheme: audioWaveformSliderTheme?.merge( - other.audioWaveformSliderTheme, - ), ); } @@ -147,18 +109,9 @@ class StreamVoiceRecordingAttachmentThemeData with Diagnosticable { ) { return StreamVoiceRecordingAttachmentThemeData( backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), - playIcon: t < 0.5 ? a.playIcon : b.playIcon, - pauseIcon: t < 0.5 ? a.pauseIcon : b.pauseIcon, - loadingIndicator: t < 0.5 ? a.loadingIndicator : b.loadingIndicator, - audioControlButtonStyle: ButtonStyle.lerp(a.audioControlButtonStyle, b.audioControlButtonStyle, t), titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t), durationTextStyle: TextStyle.lerp(a.durationTextStyle, b.durationTextStyle, t), speedControlButtonStyle: ButtonStyle.lerp(a.speedControlButtonStyle, b.speedControlButtonStyle, t), - audioWaveformSliderTheme: StreamAudioWaveformSliderThemeData.lerp( - a.audioWaveformSliderTheme!, - b.audioWaveformSliderTheme!, - t, - ), ); } @@ -167,24 +120,14 @@ class StreamVoiceRecordingAttachmentThemeData with Diagnosticable { identical(this, other) || other is StreamVoiceRecordingAttachmentThemeData && other.backgroundColor == backgroundColor && - other.playIcon == playIcon && - other.pauseIcon == pauseIcon && - other.loadingIndicator == loadingIndicator && - other.audioControlButtonStyle == audioControlButtonStyle && other.titleTextStyle == titleTextStyle && other.durationTextStyle == durationTextStyle && - other.speedControlButtonStyle == speedControlButtonStyle && - other.audioWaveformSliderTheme == audioWaveformSliderTheme; + other.speedControlButtonStyle == speedControlButtonStyle; @override int get hashCode => backgroundColor.hashCode ^ - playIcon.hashCode ^ - pauseIcon.hashCode ^ - loadingIndicator.hashCode ^ - audioControlButtonStyle.hashCode ^ titleTextStyle.hashCode ^ durationTextStyle.hashCode ^ - speedControlButtonStyle.hashCode ^ - audioWaveformSliderTheme.hashCode; + speedControlButtonStyle.hashCode; } diff --git a/packages/stream_chat_flutter/lib/src/utils/extensions.dart b/packages/stream_chat_flutter/lib/src/utils/extensions.dart index 7d048adeea..3723535511 100644 --- a/packages/stream_chat_flutter/lib/src/utils/extensions.dart +++ b/packages/stream_chat_flutter/lib/src/utils/extensions.dart @@ -631,6 +631,15 @@ extension VoiceRecordingAttachmentExtension on Attachment { } } +/// {@template singleAttachmentPlaylistExtension} +/// Extension on [Attachment] to provide the playlist specific +/// properties. +/// {@endtemplate} +extension SingleAttachmentPlaylistExtension on Attachment { + /// Converts the attachment to a list of [PlaylistTrack]. + List toPlaylist() => [this].toPlaylist(); +} + /// {@template attachmentPlaylistExtension} /// Extension on [Iterable] to provide the playlist specific /// properties. diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index edfb37280a..4df35aad63 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -5,6 +5,8 @@ export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; export 'package:stream_core_flutter/stream_core_flutter.dart' show StreamAvatarSize, + StreamAudioWaveformSlider, + StreamAudioWaveform, StreamTheme, StreamIcons, StreamThemeExtension, diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 0b2afe5d99..fe4ccfe430 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: stream_core_flutter: git: url: https://github.com/GetStream/stream-core-flutter.git - ref: da18aa04ad48d4d5bb429c9e90d9f0253c418fae + ref: 4e7e22b4b61e9b9569e2933407d49fff370e6bec path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_dark.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_dark.png index abfbdbe3e9e08ec3db4dbca85000fcf60ff776cb..f657f9c2a858bee97fc55264706d47417e218dbf 100644 GIT binary patch literal 4702 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 literal 5971 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 F@;_(45(WSO literal 6733 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 Tem(*I4+3eb>A~Nt*hKyhXnu_X diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_dark.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_dark.png index d7164ba4d26a727ceea34ef3b3497149b295e0e6..7ba9af2dd8d19a1d4438a8ff3b5794630d845573 100644 GIT binary patch literal 5381 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 ArvLx| literal 5462 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 O0KIbka=Fp%$NvP@{zs1h literal 6374 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 g|1usuGb-q)QFR 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 V6NjFfb3x!o>zeM>g3Grb{Ri+p#v%X! literal 14035 zcmeIZXH-*B*YCUO(gmbfsfq|l4ZWivASg}gJxU8b2na|A=_1mbf`Wp8(tC?^5HVCq zM4I$oLOF})J!gz_&%Gbc=R4kvFc^frch;V3t~KZU{pXI+*Skwe#zF=`5T({V^@k9I zmkoZBt`LJyGQ)nUf?tGQs#=Cuz$@^IeKhzR&+Fk`Rj7Q3Z3Tk3AT4zjLuB^)YyiR# z9m>D8d4N0nK^GIHYli(&as0(9 z=fQuI-@^srXnvsQPuG}j=r5&ht;SFMDjo{;kCv9YcSYgJ?7xiw_tuSBrIS#Ow_HAl z$%JzivH0Ika=kVbbc2I%xd91;$|N*2x@94w(HCpJcC|wD-rRRq);FuBD<@ofD!6Gr zoD2k-N#h^kR@?|_M%VR5ea!FiZ!@Qp)97~U%PM?a7b$s&V4;|>t_5d?8umVs^`T(n zE@b{=OwCSAMKenAa9~6xONUz~ti{p}bCqULw3{p^fQ>3USEZn{?K*d6Xe^EF0W*W_ zf6n@6O#WYI`QD14B8ZBHM>ZO%Mun=WOOVfy{nzn)jseS}i0Ej?Ng2c)}r}Mo>ZryWH+=aJd9Li(_WUZj?@!U(-;`| z`>^NTff_Y65rQ0fdk3uMZbH@5+pS@0(Rw>ND~@Trh#3w}!LlXQVIZM&zz7;fR?ECBc`&KI1JD08Kbp5uPMSVI{E{$BPINz43AZTL#XnJK=YZUOe9aT zD91JuBf&!M_MqJf7Fwmf+%SZPudXhxZ!qL7!JfB+L_9PiZ;<(lu^A<26w!c57~Z3) z?igtsUvcYrrEhE?IjhT6c=HDG``7*mxmTK}dH3x5q@x)2(lQ4(l)1|p9um^fkm@g^ zodXIDvbA}){A*dMI)4@x<+phR1m5E(Z%tBHSATB;UtW83&71M6Y(x%rf^9@wf(W06 zhTf{6VAl<=;E_J&?SSJ@oTj=&zmCcBH3k^JxOe=eOHE%B_m-DtmL(!eXt)0U%8Q4) z@bO*m<+sHXLn&H(snMwMwTHwk-WE}B*k3w{GI43q^7cZhlZ0{I#Ho|=v%37;nJMl; zyEk3V%kCP0*H6#jRxg2;9kf;R$0bMAyC^4l4r?;C;yEL+)y=@)>pd4S>Y|=u*T=+d z=_Pc>xKM>xh)C|xa%h?xC28{|>y_-Zk4{NGt5Uj0J5!hnc<&t?*rLlVkht~Q(b?IYNgAz}hW0YlsQty3( z*ogIr38Fs0goBU;_6|$pFj@tA{IDT__V@RZ zEAP`0JAbuYroNIkl=CpKhq5h*k*cff>h^ENu}{079M@N%H#`_GRwWBUi&meXv&47X zwv&rAh2+Tj6&4f}V5%++GCg@u84J^40oYfyP@h*Z4x4JIxK>r z|J^FCZiI#fHkvH@u$Q;5ukTC1p_}~q(Y!{*{5Fd7?%lg2;KL+sej0XC#%3z+!b!)f z0y?5CvwL;5(FHt02-{H_-c(WvDXGHhYP!{5pKm?3V3l@LcXAS7VP#F$;lDbMc2-w^ zM3zIk{nl(64X-D$!-#=|jCkqSq7kfpV?JiP0<%0UvcHJ@2Za$H$ zuYTU<^=5c1pv2_tz5H4p@ztBfQ^+f_(sf$ulT~ZWPou@z;ES_~8dp|GIYA@;X*T?I zv|A8W)O=*C`?DufA93EE1-lu_%>RlJ)j`7E+8nSdHUj9Mx&SVf?Hl2 z&5n2tD?}wEP<71l&O$^4SmNhuZ!CZ%5 z;9HQ^Fv3uS7?MAjursZ3d3>|1p*F%U~`1wmcauH4+2pUW+cw*RI^Z6{tJRgtqi znUT`m$v1OWUhh!|WPG1_|0EVWoVx$>(F5#0+UhPhO%#E&-q|Xa{Xkt})|>rkK|} zm^icedYU%(wv%uUe7$|=_&78=TK)N2<>434g`v~D z;UG881UAz4&05SYtH;P$*O%>tFLqMJVV=Ca@8VM_b7*t1_O!_Lsc(;jd1zy}m?-{W zqd0r)5)~qolapUg{hXT0be;Yy>`g-x<9m8^#ax(fYWnP0DHvDlhnc%Lb2}Zkx(!uv zzQe=BOAF7ZnF!lrRRvgBW|o(i>3$9MhU@i~W8u#o-;4m)D|~%8KNxG{@dNHf{` z_#o(+w!`144u{GZxD*_Yu)`e~(Q%l(9#Dv=ujdxAd<71D5x8>S2kw4YR4snvYmRh) z?xkfLjAz>LLUMCCW&$>wk9wqA@QjQEnuFXT!_1k698-AP7Y+(PHIXgQk8dFfL|L3> z@XOVC+d3lj_Vz!H_~bWCMdosBi!ZNy#1M^YD$Qy5K&{$^9{*U zuYyNaiCrnYDrldH*ruyf@Tx%E??-l(rrT*32Uc5oiicMrjFZwCDF<>3d+H%jIJvpE za;3&)W#FR8MvaKW-`?$fN_S)PmWkolZw`uxTsg+>nR@Bmsp8jed;3pT@KM$wxq3wv z71ZPmLN_eyQ^Eyuk@!kS?bQ72jzK{gF8Y@^@FM>F!IM{}ie77*7=L=TC7;MxMeo;9#5I3D6*=;Wq@AsRzYwmRL&(mi< zJ*Wu1-rxR|4~v&)vI6h?{PoLrX($J50n@LCvOQgs-9p>07n)5Uw$8m$i`p2xI39HW zWp{Ss8Q!VYbn5Bo|8KiCn}V`?^w(mi)%n8T;!igtR)d<28`|z%lSW9n|9(X$pr_*> z&H|Y?Aw6`aLHZTvzsojJ^O^Eu@4@E}pT7}6Up;u-mIl2r5sXvVeZ3c+gAbWFW%&Y@ zBs0b4eyz{72KPDC&AJid<24;GW+C^-O|aWYvS*J>OiXHA#f!wFqxn};^FFm@xtDkT z`n+#iWxq4#9U~>{t?f&RvaM82wnMIduH70CAJ%zV6UuWpp>4slps=tIbFkcyfVK)& z7?axlYf_G@u*z*Y#+>gnD&^HQ#K0FTO(`ZCd`7*c=ZhCqOYB4KBr>c;nGpYe-fX%! z$JOl)$q88WbVQ)@T5JS=2VVs3?eDi+i~b#Mag&s4WOqipwuK%ZaA)XrxJcQ}&K_Ey zpAs=~aA+=to-H^2Ab{XcTJZbRYgNVW!N!B|qo~;-=yjTj_4-t!fw5)it>*Mf_sw9g zHx_r{odW0h*`Q$68?Wo3hLa(mI=gLJ-bY$&3Z7R=8pNxA59QQC>+8;#1LQWgdv~I7DRD%C zQYwpbb+d&;^4soJHkK~j=6ZLVvSV)Zn_s>{7By@Q#q&qoMX&>vMV*?`XH>>>^;9o{ zp=O2jFMh+E1&$`UgY4d)mBxk3Hf=JMtY z9!IpaSguXuIHNVi$jQsiE=YyUzcN(`CCUiRSbq7#ejamxsXu7Oz3pDzmDeTw3Y$N}P2#6BUKk!U!f>gQ+8lNg&AemWiF#yx4MP zf*q;>aXMiDDeq@L`She&Y7Vt|`c#@W=7`b?SFb3=5>M26u(B8rfATb4K-BCL6q0ul zc%oErafDJLu5z-h71T-*4L-*o$Vq3Whxz@+7cw`cE*cHr*&&mt_^z{VCzUliRm0*N zMIe>Me*5$Kmud0N8|lIp39)^GOccaFhjJ1%_A^FB#=V~Dx7OkDr;=J$P=nDet-4x$ zV{oe_ajYhakJr6I*oO!CNk|kHI|@?}6O@)N<=bdE#*kH@qkuKPn0`jcTEJZ!eFc2( zJIkGhioMn0|0&|umYw+oYJ1tB@m9K(O0$TDKF!Fqrilo$uAQ#7af+J7w)GuIiaLqt zOG(z|H#OmdLv|O@HY&4)q^NGr8){znS(BP;&@FN2M!wiH4>Nm_yW1dV`hk((VuiF+?bmkB8)2jHj*f`^V?~Z2^S=IDBR^+SHs{mUoy(_D{cmbt zt;|QniScAE{`|R!n*igw?~DsqtTFuzfA$2gLRy@#)~2`W4r(DGQWH|<(og8=;eR{G zP&do*wW8r5wEDddeUs%}_~o_1IKOzW?5rMmI8XlzkjN6_n z>AksfHz;d1p{^7&ZerD4+p>RKLRvb#sv*&5ZKCoN{nQw@7yp2Z+CU9b_&_1k>p2MX8IA|ViKuU@^Cz&w>RGo|H_oWe>&PIx{yz4S?Fyd8S-iOJSat3f zIp`E>g*w7SzqRuvOM%}WI5(**tarmzGRgB{*Oj6->66G6R!qGAt@atgnr{hJ*R z7wi&(3XIEV9>4Z;cGCrQ}R-yxf`me4LzX zhz3xay*OL7vO^`Xzb|)t2?_=pc6Rn8UQ*wZ`^YnO_16YFZOOc(dM1vrU3BZY&)RHb zEy$9aaT|@uon8SvNUzqZZ*>RM!Rk_3G>yKqaUw4&kp!Q#wZ0>6$BmPj_2C&MZ;z-^ z+YIL_z~S&4Pjj4xvfIO`6{GFNGA_>FM#F3T|0c_RttHWaWQW3bcEX)^=i{XnJKSG* zkv@>GB-FHXJnuT*%{ht+hIeD~W1aF863)+ihd)?7>u{_0gIS1X3^Fk>wKe#xesM;Z zr;P7MDV^f^&H6ABlaSa*nH?Fi&JYNCH-m^f@1!IbAH|WG`2Kz~+;!h> zX=p(7NXBaohOoX86CQb&Gbh7IUUdlxN@dZE0pJE5#kyJAV&>0*mFhNIaH0|tgsGDh z=(=Pjb@ggpv5zWm#`IrH*vk;bb%zXQF%}edL_P>^AkLB&QT3|kG%}LvnrC^{li`1? z49DC{Axs>mE*p&WmW{6nT6eAku@uyh7Kll3_0HzQ-*LCiYPfRrI-gZ{&hPIwP}0n% zoNsJW*^L!nM9rO*GgdVFa$photWt)y-DFB^2^s!gxUXOPR(1fiQAyAk9UTn?HH6D# z717+;hM$3zmD0zmp&YrWrQ852k>DThxxwe^=X37O`!o<{^zK}* z(!qc@=iiI7GOM5)bv~)$u6N+oa@IAbSK~B6!B!cB^L3ePVULfGM`A)rzIe=Q{`~oq z>*^J*tKVmk^c2K26vU&`({UD!zS#BJISwyhdokd+_4Pl+Xav`P*BDKb6@xiq5hBufReRJtZ|sbue|j zzx=g6d0|xHas98J3tr}J0@Tqi@O{t7(<|QM)-7=XGYQ(z;-|-r|0H$$Yj0m;;qW?q;b!I8 z$~kBGI6)UC%roh#T3};nG+b|NTyu^cnFu0{QoC0&b{w48a!bMFfhg>Co*wa}-HT>l zzErL6ll4ui1ze?N6`wskU9D8nv+4JE<*{|@o+v&x7ICjm)V{}tlZJuY2Ca|BUm;u<&0lBA;T3AYX(&DJ#)f^zmDlIN8sSo#xdzHfCL?-7&I z^4?34aO|}YYfs7u9D~1{w2heE2p?My9ke`QCe$20g~4v{9xunEiWY%S?qSfmB7 zEr~Q!u*)_)2!wRUz~qBe2_&6YDX0oo5dwvy1FT<*-_vZRU(eCQ9#t0Az0P@ehaYuy zE4|&#FTZw)Hi1JX++HjOE`eV=O^}F+yJt@b61?vhQLj)&M+6R{OuX4LWAWl8;!Zi0 z@N1TNoO&hXXC^qryE3}cQf>|tn;KKh?n!=+IGr4oqrjg5U3WVvGX(bZaW?E6&)edz z5KAa}HLQQ3X9NQ^P;rK2pC!Fq+vDw3cjO*vxz!9oG7Mo#`ov}9k-7bx*s!9gWbOAo zZ0QI%lSPy)P0TB0v=48p))4`oGTLM3?;Z2+dOw)rG_Z$6V3+q;(56mclZjU6#sgOY zOb5NHA;H4zWo~HHI~45LR$pDRL-)F`()5MZNyR}y*(#SI@Gp+2Ui+8BOHnK)qdrGm z#Kh%}32{O^S8s#?iW0?X*|u$WcPA+O)-5JBRdj(~m#qOj6YKz#tx6%@vJrNiZ0!sE zoDQpr9h=l3+!_kTm`aeP_frDXfiXu>`z@-8qEWOV8;=AK=QA13sBWt_O2T&=sjhP* zsu|dWsYW&{e)psRMphQZ6{r}|6XGw3X6VqJOf2noE_m7@@;NOma`f$)LB$S=$E97T zlcVM9d;1QWj=fbBc}k+CiH%8mB}xaEfmrgk#g=pH)n0pPh?3uql2>cZl@%=B$NsEU zJDaIfLo42PaO;20*;>$E7V@{x?Z>}jL(hNTG*QeN__{#a*H^YxhPG0>-yeHwcRy=N z6o#bF?BOep`EVpC=#2QMj(%M$0)L%Z{1n3^%3U}eR{WI6)oMgC+gp(69&P@EW8SB~ zFx+r;Z?n{QrzR7Rj7FIC1atld39GBu;t{+X(9%?KxW-j3$o5i4-J4Z3QwNaZd@(Yz zY`8t4an>1su2y&PQCeY8Cj(245bm<0#oD|c%*a*4_g@}xsjJUxC1DOuM$iHEZJuOI43rSP zk>r;ru1O1U70QC%-p}Ip-=;ti1)C3`R`13lf>7G(61ih5k)_|6mRld{%Mg(OM%q;< z_r?bKf%CbI1`QzlJX*v@1Pid>MfLX1Lh{_(PsV=K{qUmMFnEN#N;CSoeSW*(@JIK{ zPU7w-xm*nKTx?_=E8dJnFo1IKvh}1@JJmEA?H_*KM?^*uCq<*M2!xL=z-U8QZyuUN zI~Yv=s~CApq_}1NCt6D@2%C(Zwk5^~m@bA_YYTaLCFsQC6)TQAp9w2p9r&?u7>vMnkW15@ftw8#m?EZ zKxSZBcoT9bbKuLAOA^<03Hj))y8Yl-vyzZ8>Y`r4^nQY@S-i4w;<)jin;y~8#agmC zs`BOB0LjraSHji5D+H?IM}aSe7yXQmj&=(Na9LgTQlq$c1xO$?r0qcG;J&V{C!Yqdrh=Ay_weSshgw}m^qxX5O@;7p=LkP-=3`}W6=l-5lp#4+Cc4HKS@(Rs z=Yg=DvbsdG=G;+6mW?$M4uuhz#EOaU?XL={VCobU4ULB*xbKo%5@Z3s^j*i*&JX6X z-}nAydTPrH%I@5z>Lx*Pe<#@YUb&cPM`o=Q%r@~=7LKkh{cXos%}Rojs^`K&47coM z9)9AE0fvF)xB-xNf_olo>^NFJL((T`^KFtz3V1PqDfqxB!H+dY`-HSH;6P*`URPoF z#ZV%!(On^i&|wqek}_DC3?m!tf2j%YydOOQa>yPNGjmV&6p==K+ItM`X=v=gb2J;L zvbuLapgUD;VXdEce^1Uab5*mdwa*5Y~f`Jl%pRP zjS2ZPh=yh`a66nVYt}%Y8G!xic{Q<&8z1o0)g^ZM=sfCXf4}Uzu||Pfym34u2R=NL z(9QZS&Xzhk;6Dncj-a6&s6>p0nl_lsE)HDEp6U!#wQ%Ec~UrovoN6GJH6LVi(!lGq=u;j~e1&4-) z#&&TcUdY_$Oiab;zDe8@$2$YjMk5lsVQg{S*Vwx5A(?>CB$%cyo5N8p3^o)Hr9n!e zPfx^ywF!S2mSi`Y@_9~K-H>d~|D<(3>%p1pXeTlBMp_cPz%D}HJfo_+%AD{&+5g{k zF}RCM7Xh_=NIa`_A)uI{$`E)%SO3ra<}zO~)Bqxc^|wizS~)IeNA(0+nxIc2FR;6O zw}XMy@S@UAuH_VMru}5t{@Pdh_7sQDOv{@&KWa)#m18stoWd5%tj+{mc5^oKTUuT` zT*#L5qZ(Gk3DvmIQQ4zC5wKaXupT`_qp&&z%-K&v{ulB;Jg^L~Qhaf$KB`($SVdf- z=dJ6Hdn!X8t-+I2L<6u682O){A0HvNyBP)8r67GPtHgB6=cED;a=Sn-o4+axcB}6O z!l}yYKYxCuXq~7GYRJNired=|@1o&C4CIzbEYaFbJsl+_B~kk9#6ew=(pK3pQvQHC(99h>f!C}oew_zXiqzu zj2KCPvl_~G3nBiz%Hi`h}Vwn>c4+mMVx0>9IKfcb z?){SMC^07{fI2%*_E+y)IQ%|4oJ(|_uEB%;ni6LA#AwDW|5`%KAX5g5iyg*1^Da+% zV+V!kW=Yuqx#6-9`fOHfJ65a7hKEzR0nuiml0FE zru@a}?)y*wB69;aWWWt`f#k&tW+Q}9-Q2Tjgs!mtvn*PGd z!T%+ox7fVTC)cU7(OiAZr!=4Co#*w)>tJepg9#<2C;edaBG%UU0KuZ(j zxot~fb-sfqk8_c>tsDq=E8*t<76FSfv8;zFP&ScXl6iSA-`&aK7Ysj+iab-FJb41@ z_=lG`lBo7>3a|VTCM=`IVYms<%~BLc0QD`Cje(2fnWY2l_vEXmFUVVV(jw5a{7gC+ z0VSC*p1Z0Y(*fez2Wd|>glzvOnD3=1d2xh|s`TG=Jl!i%ia^b0c=~_-{CTp5uf&{~ zCiw~~F|lny%%(TRIUU+)IiTESfFK5s%U*y+w!Di|`46R$=~gXC7orzEtm!my@3-EI zj29HN$0sBxe*Jn>T)B@nscIYD8#s{|A0OAax%mxk*|{;>2n2`<%%i-MpRfDAe~ak1 zr5kk_8Nu`%te5f2m*2<;AoKFluxw%ihn^k%NdGtTd*iN?lRnbSU9-34;n=r7#8lAU z-X7+6z-G5}%QoeQQrVStFLAcN9SEsZUQ*#&Mu!EX8x#AAELp)*f`;0 zU+IG6e0@B)n>}t7doa-S2+l3oj5H4$e-_S*S7}P-iyLS{f3fF`;nM=5`xkou>t9Fh zj}MwldTcBI+`3qA#V7@BT!-3*U!2g%Z(%x*=k?U9=T^pOTqb{HT=;iJlF9FUpE>=O zH8W>Y{sHdXABd6n6OOI-a+m)$=zY@y!$dJM_7s3#oA#GP-M36qwz}vZU`PO+f7kg5 zAp0Y6y6Hg%5VHI#TBYU>{+v9&2Lb8VVkz50y?Qq2&}ja!b+fZQtn(NJ0DVvEX+M$t z>6W?mPswceV7cnzw?ZY`Lvmq9S*?%dbw9UMrVu5lh$*Xi5R}x^)a0uW)VKZbJbp8{ zRpw&%{e3a8_O2aluy^%~IK3(12=4D4(PwjYRXkm=ip{B7bWB^zk@jOq4oDUGxj2~R z@uw^1W)$WBEsxJE`IshcT;*q3bkl3+Yw*SS&2+49Bh3}Ut;H<&KLAqQUgltfmjC_h zt8sPoC8in8+wFZ|j2V1oT`kByuJXmzl9@MUat>t4miIe*|_7fQTHmmb6Nfir-B0DqVf0Bi~Kci z4e*QW96?eqSg{$kFvQ?B}(0XvAIb-+4R#u=WcFAi$ki z1m?898tFHe2SIuzK!*;d^?J5wn|~pAusIio#axA~$NznU!%buD1!Jfad<$@Y??B3~ z>+%3&GjLQeat7^PKUhEJN$LIjc3w=LtqZ6j*ni`Z|De-eF6`eJJJ=7OtB}5d zL5!qd`S9U(uHX3;jbs~9rZ49k0blanXt;T7*yN8Cg{@8yI-F*=jk9F}$Vh%qlR#U? zr*+P=0y`)Npniyo(Go+SENb!g_x+TOjM!FWqpO|t@F0etx4Zfd8z>07L8n7`zC6j%b6el?R_%&LO}Cu$ z_)Fd~iVrZ{f-p-;dFKz7mCm;lL)S9UpS4qX2l84WuO$cKrvpG(A!q#63704UJbUn# z*dyHALC3E(pCf0nA%l*00UcjE7!d6Px^7Wn6&ctl0R6Qy2y)!Jzn{$lka-BGqS32r z(|zN|&!^Q7JO~hBmx)OmKiHS5Aj*No z0h40g!P_@PDndr#T}{V6z|Y|w9@`{+BQrmU?JL&5RrEE{Jo#iSV%WN`pUo*N&?ME9FZts8^$jvICjAV82%aqs4RfXg zIL^g7(w}9vi+Ue4o^!k!KX}`9i=@w5BJ&HvCa5LxE@%3scp+K)7l(AfWv(7Nsf)4@|u z?GuAJ$6C-gaVa%!e|8Td2T6)_rO`IKoUhP@Pv*FyII4s(b&j)*2zn7b`4$xf(w8=@ z_rgV|B`$|Emb-K5FwtNBu;IoUSKtO1JDY1 z7k;Pvw~gx`@j3?8%r-9N=JdkBD`rAlQ6|hb@{WYW+u$Jp=D!7b2jI0~5qdCTjtXiX z9&C(*L+}Bvrv|;c-wu~hMehd&6|FKax5i##e2(4ib+N2L1S}_7ll!i3b{ayaz_7$#x>5qm{p{TQo9re(&Y5!t?v3Z5-`Nk$EcsmR`78?N04%CPMi%l z>ov_E%(_T*u6SR+#*#ZC5~|EBoUMeb#HAct+vje{X(U(dJ|EoZN3r2lKbzNA-&h#n z=Tqps!=n3TY7NvPN52nSO!`XQmRCyhh#^p4mmg@UOXzAVDo$X%RZ@6QPtdKG39h!q zETKrt5x1B_)xan z@N4t}Xb6k}-7dcC4UAT&w%f#}X(>tACk$ouHLnRux#tU_I686_0!Btg^nb_>XUl|< za0UXxBX)6ihi24e_d5^hK%mYq?n&s3iUL|Tiq$yD^sh|lUT8_*TSSGGzWd_=I(uqsSJMb+U_ zvsEU+o%wAMDBJ@XS7c?M8<;txhF|!yP%B7q-cNr%I`t1Vl#^kz<+V8BfoZ!+OG=rj zC@VgU3?JJ5b8uuWmI2%#93}(12rwn?fTl z&JK$j8yhiIiR^Nbpx=Vgtj0w}HYXDFbfH$4d|0^~_|9hM*n5g~FQ{8_79y4b>+&5~ zLMfLIU9t2pU~J;Miw94irj0Fxj>2EE#T6D65dalsqRN511vk4{cv;#xfHo?sVTD6B z%jYZaPwl8m$ZMuCFTj=e6)sL=PFv@mukt``MTsq`!D|^#ir+7ACaov>h*f^4Dccc8 zzc!c3OAwT%V~aajzibBsolyU0cUn^YHqbP=nJ5ZtYZ)7T*E^=&TNM`9zS{%G_4NAa zz@51*;YvuXWjRK8yK!%9l*l;`Dic)G?@zmiLU!xKj zc+>l!&;J$+2bJRm2g@$<#%q7`1dUy<&+!))a3s^hLfSE^6Fc%&%*%7vfsdB-Mr?K- zhy9*cXMv6N`8U2EwF3Q`gi42-u`iM4gYISq1~R3e1{D7;vNReSJL=WIdGU3}cKpVj zGvBzI-H?!9+&G(Q`TIhtrMZeN3t_HVH65+>gC9$fF`rO$R$T&m2WzV#yW~Dx6d7L*whNS9`BuHcJ{j{52&1n!el8 zWQqq%|I6e$5Rib_&U-J3_vt_iXbrtCeq20CSlvz5I7?bGO_0^RCw}Es?&kfk<()Zn z99}SUT2b+xN)fjbr za1|hnR-ohH5@L&WA4*elE(SupoE|I)+(@&lJZ;&tT30hct9$_dB~O*wOPDuIEm>Z; zx}`!vVLr&l_*j!mpE$1jCACsh$UG|HFAip6ED8%RSIdd+E(9eo*a1Kr$BH)@N)h(5 zKV7Ns7iYvAC({@TaB`qb2&c0G(}6Klac&GmS(hjBL9-=#ATJb0C&aZ11)C@Mva162 zU-%tN_15#Cqy;8m_EvFP-$+)J65mI8~nYDda` zVNT#%&e%%NNZU^|EEQUo-fmibQuT?R;H>#kd7xCr(|$V8M}sw~XpMaHhW;4!M(Udl zVoOjW{=N>K!M2g%FU-%F@|*5fBR3><$J*{#15ihzJ4QCulJ5*#F(gakRnFVd`thk< zV+3@2^J^Vfd?_i-7tc17?Zug2f6&&H#Y;ld_)NW*x0&|N9Mz2N!>ZJRvh=;)qQuJ-l)f{H`W(1mNFLM)JA=`(9U z-TOyhvZ$t}nmK)c_6-D4URu(wLY{jiiXt;dI7^PA2osr=8`JQA{Y+>!u8h%2Z zTlz|S9kJY?%U&~u0;~d$^MRO`Q5uZ$WQfLYF=&geH>8~W$ZV3 z6(JTC*tPJn6=6*ez-R~nunmuliuLV%2VccfP*ZZzBz9sk|1yCXoYT3Y$<2X6AoAf3 z41ttRP0Ek?np&U^p3d5C3PNCN+DmSHL_0XL ztDsF@>&qnAJJ^>uS5+ivTVh=K&D2Q8+E&TimPDe;SEEt%?;hP;ZlKad!}!310up_< z@j!1Dpvzn;s&M30tjFrbwOT#A5>CxjQsHzGsYU{@3K`1cAN{#Xqnir!CZxHBxlz8L znC)nEQovC-Awca0qnaOSXlQ1yfM*Ay!>z&d2FX{!g9Y5QFaN)p{3YP!CH(J$|4X6x rZ)^PTdd+`ZZr=)ydgvP9_N#}V*U8l diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_light.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_light.png index f5b00306dac388c2b7e2d2beb44bcfe3f2d25f1f..94b4e1b26ba73c64ca71de8e477da72059748ed9 100644 GIT binary patch literal 10405 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 literal 14530 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%0A2x&yJ!s3>T;H>1&xj#WP1)vTQ1 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 S4R{F=A}J~>QXr)B>wf?hRg&TW diff --git a/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_playlist_test.dart b/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_playlist_test.dart index b6a4fbfc52..748f383028 100644 --- a/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_playlist_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_playlist_test.dart @@ -253,6 +253,15 @@ Widget _wrapWithStreamChatApp( Brightness? brightness, }) { return MaterialApp( + theme: ThemeData( + brightness: .light, + extensions: [StreamTheme.light()], + ), + darkTheme: ThemeData( + brightness: .dark, + extensions: [StreamTheme.dark()], + ), + themeMode: brightness == Brightness.light ? ThemeMode.light : ThemeMode.dark, home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), child: Builder( diff --git a/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart b/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart index d0becb3e9b..820aab8fe6 100644 --- a/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/src/audio/audio_playlist_state.dart'; -import 'package:stream_chat_flutter/src/misc/audio_waveform.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import '../mocks.dart'; @@ -35,7 +34,6 @@ void main() { // Verify key components are present expect(find.byType(AudioControlButton), findsOneWidget); expect(find.byType(StreamAudioWaveformSlider), findsOneWidget); - expect(find.bySvgIcon(StreamSvgIcons.filetypeAudioM4a), findsOneWidget); }, ); @@ -89,7 +87,7 @@ void main() { ), ); - expect(find.text('x1.0'), findsOneWidget); + expect(find.text('x1'), findsOneWidget); expect(find.byType(SpeedControlButton), findsOneWidget); }, ); @@ -281,6 +279,7 @@ Widget _wrapWithStreamChatApp( Brightness? brightness, }) { return MaterialApp( + theme: ThemeData(brightness: brightness), home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), child: Builder( diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_dark.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_dark.png index 236732440b767c548992722b7a2c1ac137e6cfc0..7a796a17d3fbf9f10341f6d81c85e5eac607a931 100644 GIT binary patch literal 4570 zcmb_gc|276`#+IxX)ITzvMVGzHR&>zWN8dpW{|bSko`tuY-6e$Q6XcCLYBc0N{lV0 z%as_6Y-3j<>&#?l#_~Jvz5n0W_x1Xn*O||md7XKld7k(4yx*VaOswT~6Fy!MUH}02 z;MWYT0pP%M@crwS1 z@`^ErdnSg(wJy;*x;k`6$HrIwpm|PGhhmQy{>3lA^L?F5tDPN8V8Y@HIbp7?2X;ItU0J zIlu+PJOT~?h6eY+jHLAeAW={XxPR@iAfPYv|NAxD{ogjE_w?juWl2<#$;Gv`_UUHE z;7O>gtjs48WtCM_y#4*JBaui4C#N?fReW&QF$ZqIw~op5=i}oe6&9Mr8X5!z1+~wt zCCrC(<@uW!*~4sZ5h$X7_k@MXmZypHAP%>V4ps+bA3AjC9&_|J zb2GC(VrOHcuHQtXvzwb+*y6j^V%==u!2MG}VkW=vi*+wAR~Va^EQ~jLWemS_drWA{ zmx$bS4b8G!ss&N}3xz}`@k_vpmX_{zc69j8SJlakvW6$fCnc@`R5a zJI32=dG+d5J6JrJ#f!H}hG zw5g|uprx%%_dTZ%y#BLiuF{F_cki@lptBtb<=q*)Z?BYMYE$)$UG*b0t7mTYUvsU@ z_-TNxK*yw*$^B~#X^!dNPB^)Y!;kfc9{C?A;ZrEg1(2V4kKc6(Sp82@HNgad!H1td zee%|#NUWI5KKP%g`1;R?3A;0icfcG?WrV<3|H<)g?s38))7x&x0x6q=TvX^0Z5V+xX_pa3pEjz&`W_4|CZnQQClmI6zMf&`Q zA2tVofonu-t%%LH@sfIt%np{%`NV5?z^2p+oQiqUGFjK!%?&oO{Bd5b@bx!klwG!Q zT5e8`w9ZP$1<~lo`@y7T?`_Xi_|@lBSFkx8w0&w;$YiXdgvmZHl1*jBwS&zX8XDR? z-qW|)rKP3wOy!jwug= z{V@v&B0(Li9t(acckUciN2h^A2VK|(1eC{%Ym=Exu?wK2NPQBDeKh_vT2uZoS6qC& z0%U=c=yxhQm^pgBS#9<6*#y+u>Pb+L9Y22k`Z|~~OmtEybW*VkdVVT82UI{zaPT~c zpjOZivo$|Zq~AgZ$)%3|wMIKTJ6ibN4}rB#Ms6q4g2ZNe7{#E_WL~~&kTqje{1_y- zazRN6B7A+(3XEuZ>w|}wQ3+^!uzf%TyaNL4K)C{Ca&;=n z!aF#aT%ZL)IM@UNw^otXU^e7yOZKCZ83@%vkW^VSZ7Z-&8qLSo*KDRMvt*>|Z=!T? z|JSAx+yAA%cUA-d90hg$lUg_8Q~{Ck(n+RV15i3PvG6h0M;1#m)D}tXl5DFZ12|J#~*$(Y_;z z=%K_BHFIRa+-I_QifI;%R*HBIVk>xc?Qs0sjZ|f7#e$FwB$rzs;%6M<)K%d-L`FJ_ zVoDAHmKU1UlFbUhc+4rO=pj%aTW(8RTeu^+rY!61CFf?p@LjhitR_45o%1}mmNdQX z;lvNPTrB1jDBO$l9_L)KFRhn>6SoShHYwRD+DpApx->f<5!#Y@qo#{}pjvfd^Xv$^ zw!`o$G3x*3{-RsYY~1>$XZx)q@yUon3ar-u z-Q;&0milws3ptdzVL5o16y>#}37)wB%G(0>OWM6PMYP%?7ob|HRpWfVxlMsnmjEX` z@k~&_PDoEPweu$j1!UpM<(!&lhTgqp(@e06s5mbdv9c6gMaKLDv!@P)8ddg6u z|EV`a)s+>c(;_f?u+gGiUnEM