diff --git a/docs/docs_screenshots/test/channel/channel_preview_test.dart b/docs/docs_screenshots/test/channel/channel_preview_test.dart index 2d3c02be7..a67108ce6 100644 --- a/docs/docs_screenshots/test/channel/channel_preview_test.dart +++ b/docs/docs_screenshots/test/channel/channel_preview_test.dart @@ -2,6 +2,7 @@ import 'package:alchemist/alchemist.dart'; import 'package:device_preview/device_preview.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 '../src/golden_client_stubs.dart'; @@ -200,4 +201,164 @@ void main() { ); }, ); + + goldenTest( + 'slidable channel list with header', + fileName: 'slidable_channel_list', + constraints: const BoxConstraints.tightFor(width: 430, height: 932), + builder: () { + final client = MockClient(); + final clientState = MockClientState(); + when(() => client.state).thenReturn(clientState); + when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id', name: 'Alice')); + + final channels = [ + fakeChannel( + client: client, + id: 'general', + name: 'General', + messages: [ + Message( + id: 'msg-1', + text: 'Hey, how is everyone doing?', + user: User(id: 'user-2', name: 'Bob'), + createdAt: DateTime(2024, 6, 1, 10, 30), + ), + ], + unreadCount: 2, + ), + fakeChannel( + client: client, + id: 'design', + name: 'Design', + messages: [ + Message( + id: 'msg-2', + text: 'New mockups are ready!', + user: User(id: 'user-3', name: 'Carol'), + createdAt: DateTime(2024, 6, 1, 9, 15), + ), + ], + ), + fakeChannel( + client: client, + id: 'random', + name: 'Random', + messages: [ + Message( + id: 'msg-3', + text: 'Anyone up for lunch?', + user: User(id: 'user-4', name: 'Dave'), + createdAt: DateTime(2024, 5, 31, 12, 0), + ), + ], + ), + fakeChannel( + client: client, + id: 'engineering', + name: 'Engineering', + messages: [ + Message( + id: 'msg-4', + text: 'PR #42 is ready for review', + user: User(id: 'user-5', name: 'Eve'), + createdAt: DateTime(2024, 5, 30, 15, 45), + ), + ], + ), + ]; + + final controller = StreamChannelListController.fromValue( + PagedValue(items: channels), + client: client, + ); + + stubQueryChannelsForGoldens(client, channels); + + return DeviceFrame( + device: Devices.ios.iPhone13, + isFrameVisible: true, + screen: MaterialApp( + theme: docsScreenshotsTheme(), + debugShowCheckedModeBanner: false, + home: StreamChat( + client: client, + streamChatThemeData: docsStreamChatThemeData(), + connectivityStream: Stream.value([ConnectivityResult.mobile]), + child: Builder( + builder: (context) { + final chatTheme = StreamChatTheme.of(context); + final backgroundColor = chatTheme.colorTheme.inputBg; + return Scaffold( + appBar: const StreamChannelListHeader(), + body: Column( + children: [ + // First channel shown swiped to reveal slidable actions + SizedBox( + height: 80, + child: Stack( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + width: 80, + height: 80, + child: ColoredBox( + color: backgroundColor, + child: const Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.more_horiz), + ], + ), + ), + ), + SizedBox( + width: 80, + height: 80, + child: ColoredBox( + color: backgroundColor, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.delete_outline, + color: chatTheme.colorTheme.accentError, + ), + ], + ), + ), + ), + ], + ), + Transform.translate( + offset: const Offset(-160, 0), + child: ColoredBox( + color: Colors.white, + child: StreamChannelListItem(channel: channels[0]), + ), + ), + ], + ), + ), + Expanded( + child: StreamChannelListView( + controller: StreamChannelListController.fromValue( + PagedValue(items: channels.sublist(1)), + client: client, + ), + shrinkWrap: true, + ), + ), + ], + ), + ); + }, + ), + ), + ), + ); + }, + ); } diff --git a/docs/docs_screenshots/test/channel/goldens/ci/slidable_channel_list.png b/docs/docs_screenshots/test/channel/goldens/ci/slidable_channel_list.png new file mode 100644 index 000000000..a0e5ce894 Binary files /dev/null and b/docs/docs_screenshots/test/channel/goldens/ci/slidable_channel_list.png differ diff --git a/docs/docs_screenshots/test/channel/goldens/macos/channel_header.png b/docs/docs_screenshots/test/channel/goldens/macos/channel_header.png index b627f06a3..1219003fb 100644 Binary files a/docs/docs_screenshots/test/channel/goldens/macos/channel_header.png and b/docs/docs_screenshots/test/channel/goldens/macos/channel_header.png differ diff --git a/docs/docs_screenshots/test/channel/goldens/macos/channel_header_custom_title.png b/docs/docs_screenshots/test/channel/goldens/macos/channel_header_custom_title.png index 35fc9ee48..423dce7b4 100644 Binary files a/docs/docs_screenshots/test/channel/goldens/macos/channel_header_custom_title.png and b/docs/docs_screenshots/test/channel/goldens/macos/channel_header_custom_title.png differ diff --git a/docs/docs_screenshots/test/channel/goldens/macos/channel_list_header.png b/docs/docs_screenshots/test/channel/goldens/macos/channel_list_header.png index ba08cf919..51201d3a2 100644 Binary files a/docs/docs_screenshots/test/channel/goldens/macos/channel_list_header.png and b/docs/docs_screenshots/test/channel/goldens/macos/channel_list_header.png differ diff --git a/docs/docs_screenshots/test/channel/goldens/macos/channel_list_header_custom_subtitle.png b/docs/docs_screenshots/test/channel/goldens/macos/channel_list_header_custom_subtitle.png index 963479866..60a9dba96 100644 Binary files a/docs/docs_screenshots/test/channel/goldens/macos/channel_list_header_custom_subtitle.png and b/docs/docs_screenshots/test/channel/goldens/macos/channel_list_header_custom_subtitle.png differ diff --git a/docs/docs_screenshots/test/channel/goldens/macos/channel_list_view.png b/docs/docs_screenshots/test/channel/goldens/macos/channel_list_view.png index fc52b2fde..65be76b51 100644 Binary files a/docs/docs_screenshots/test/channel/goldens/macos/channel_list_view.png and b/docs/docs_screenshots/test/channel/goldens/macos/channel_list_view.png differ diff --git a/docs/docs_screenshots/test/channel/goldens/macos/channel_preview.png b/docs/docs_screenshots/test/channel/goldens/macos/channel_preview.png index 7ee2a600f..b94a208e5 100644 Binary files a/docs/docs_screenshots/test/channel/goldens/macos/channel_preview.png and b/docs/docs_screenshots/test/channel/goldens/macos/channel_preview.png differ diff --git a/docs/docs_screenshots/test/channel/goldens/macos/slidable_channel_list.png b/docs/docs_screenshots/test/channel/goldens/macos/slidable_channel_list.png new file mode 100644 index 000000000..583017c11 Binary files /dev/null and b/docs/docs_screenshots/test/channel/goldens/macos/slidable_channel_list.png differ diff --git a/docs/docs_screenshots/test/channel/goldens/macos/swipe_channel.png b/docs/docs_screenshots/test/channel/goldens/macos/swipe_channel.png index 6e1da412b..1b2df5b5b 100644 Binary files a/docs/docs_screenshots/test/channel/goldens/macos/swipe_channel.png and b/docs/docs_screenshots/test/channel/goldens/macos/swipe_channel.png differ diff --git a/docs/docs_screenshots/test/draft_list/goldens/macos/channel_draft_message.png b/docs/docs_screenshots/test/draft_list/goldens/macos/channel_draft_message.png index 3be5123fd..a2725c032 100644 Binary files a/docs/docs_screenshots/test/draft_list/goldens/macos/channel_draft_message.png and b/docs/docs_screenshots/test/draft_list/goldens/macos/channel_draft_message.png differ diff --git a/docs/docs_screenshots/test/draft_list/goldens/macos/draft_list_view.png b/docs/docs_screenshots/test/draft_list/goldens/macos/draft_list_view.png index 42d54e526..9e06a0ca9 100644 Binary files a/docs/docs_screenshots/test/draft_list/goldens/macos/draft_list_view.png and b/docs/docs_screenshots/test/draft_list/goldens/macos/draft_list_view.png differ diff --git a/docs/docs_screenshots/test/draft_list/goldens/macos/thread_draft_message.png b/docs/docs_screenshots/test/draft_list/goldens/macos/thread_draft_message.png index 29cb755c3..e5b58fe87 100644 Binary files a/docs/docs_screenshots/test/draft_list/goldens/macos/thread_draft_message.png and b/docs/docs_screenshots/test/draft_list/goldens/macos/thread_draft_message.png differ diff --git a/docs/docs_screenshots/test/message_input/goldens/ci/message_input_custom_send_icon.png b/docs/docs_screenshots/test/message_input/goldens/ci/message_input_custom_send_icon.png new file mode 100644 index 000000000..67704e309 Binary files /dev/null and b/docs/docs_screenshots/test/message_input/goldens/ci/message_input_custom_send_icon.png differ diff --git a/docs/docs_screenshots/test/message_input/goldens/macos/message_input.png b/docs/docs_screenshots/test/message_input/goldens/macos/message_input.png index 6f809d9c8..0d9fc034e 100644 Binary files a/docs/docs_screenshots/test/message_input/goldens/macos/message_input.png and b/docs/docs_screenshots/test/message_input/goldens/macos/message_input.png differ diff --git a/docs/docs_screenshots/test/message_input/goldens/macos/message_input_change_position.png b/docs/docs_screenshots/test/message_input/goldens/macos/message_input_change_position.png index b257c5a08..0d48b4a61 100644 Binary files a/docs/docs_screenshots/test/message_input/goldens/macos/message_input_change_position.png and b/docs/docs_screenshots/test/message_input/goldens/macos/message_input_change_position.png differ diff --git a/docs/docs_screenshots/test/message_input/goldens/macos/message_input_custom_send_icon.png b/docs/docs_screenshots/test/message_input/goldens/macos/message_input_custom_send_icon.png new file mode 100644 index 000000000..64f0a52dd Binary files /dev/null and b/docs/docs_screenshots/test/message_input/goldens/macos/message_input_custom_send_icon.png differ diff --git a/docs/docs_screenshots/test/message_input/goldens/macos/message_input_quoted_message.png b/docs/docs_screenshots/test/message_input/goldens/macos/message_input_quoted_message.png index ff3e61496..80505c2a4 100644 Binary files a/docs/docs_screenshots/test/message_input/goldens/macos/message_input_quoted_message.png and b/docs/docs_screenshots/test/message_input/goldens/macos/message_input_quoted_message.png differ diff --git a/docs/docs_screenshots/test/message_input/goldens/macos/stream_message_input_default.png b/docs/docs_screenshots/test/message_input/goldens/macos/stream_message_input_default.png index 6f809d9c8..0d9fc034e 100644 Binary files a/docs/docs_screenshots/test/message_input/goldens/macos/stream_message_input_default.png and b/docs/docs_screenshots/test/message_input/goldens/macos/stream_message_input_default.png differ diff --git a/docs/docs_screenshots/test/message_input/stream_message_input_test.dart b/docs/docs_screenshots/test/message_input/stream_message_input_test.dart index e28821cda..875a3a743 100644 --- a/docs/docs_screenshots/test/message_input/stream_message_input_test.dart +++ b/docs/docs_screenshots/test/message_input/stream_message_input_test.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:record/record.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; import '../src/fakes.dart'; import '../src/golden_theme.dart'; @@ -134,6 +135,62 @@ void main() { }, ); + goldenTest( + 'custom send icon via StreamIcons', + fileName: 'message_input_custom_send_icon', + constraints: const BoxConstraints.tightFor(width: 375, height: 100), + builder: () { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + + setupMockChannel( + client: client, + clientState: clientState, + channel: channel, + channelState: channelState, + ); + + final streamTextTheme = core.StreamTextTheme().apply( + color: core.StreamColorScheme.light().systemText, + fontFamily: 'Roboto', + ); + + return MaterialApp( + debugShowCheckedModeBanner: false, + theme: ThemeData( + useMaterial3: true, + brightness: Brightness.light, + extensions: [ + StreamTheme( + brightness: Brightness.light, + textTheme: streamTextTheme, + icons: const StreamIcons(send: Icons.reply_rounded), + ), + ], + ), + home: StreamChat( + client: client, + streamChatThemeData: docsStreamChatThemeData(), + connectivityStream: Stream.value([ConnectivityResult.mobile]), + child: StreamChannel( + showLoading: false, + channel: channel, + child: Scaffold( + body: Column( + children: [ + Expanded(child: Container()), + const StreamMessageInput(), + ], + ), + ), + ), + ), + ); + }, + ); + goldenTest( 'message input with quoted message', fileName: 'message_input_quoted_message', diff --git a/docs/docs_screenshots/test/message_list/goldens/macos/message_list_view.png b/docs/docs_screenshots/test/message_list/goldens/macos/message_list_view.png index 0a9d97a1a..e84de0a7f 100644 Binary files a/docs/docs_screenshots/test/message_list/goldens/macos/message_list_view.png and b/docs/docs_screenshots/test/message_list/goldens/macos/message_list_view.png differ diff --git a/docs/docs_screenshots/test/message_list/goldens/macos/message_list_view_pin.png b/docs/docs_screenshots/test/message_list/goldens/macos/message_list_view_pin.png index 9cca5a0eb..0b28d7e3d 100644 Binary files a/docs/docs_screenshots/test/message_list/goldens/macos/message_list_view_pin.png and b/docs/docs_screenshots/test/message_list/goldens/macos/message_list_view_pin.png differ diff --git a/docs/docs_screenshots/test/message_list/goldens/macos/message_list_view_threads.png b/docs/docs_screenshots/test/message_list/goldens/macos/message_list_view_threads.png index 48ef4e2af..608199037 100644 Binary files a/docs/docs_screenshots/test/message_list/goldens/macos/message_list_view_threads.png and b/docs/docs_screenshots/test/message_list/goldens/macos/message_list_view_threads.png differ diff --git a/docs/docs_screenshots/test/message_list/goldens/macos/message_reaction_theming.png b/docs/docs_screenshots/test/message_list/goldens/macos/message_reaction_theming.png index 95d094078..59b6d1d75 100644 Binary files a/docs/docs_screenshots/test/message_list/goldens/macos/message_reaction_theming.png and b/docs/docs_screenshots/test/message_list/goldens/macos/message_reaction_theming.png differ diff --git a/docs/docs_screenshots/test/message_list/goldens/macos/message_rounded_avatar.png b/docs/docs_screenshots/test/message_list/goldens/macos/message_rounded_avatar.png index 8baa6db8d..d4dffa7fa 100644 Binary files a/docs/docs_screenshots/test/message_list/goldens/macos/message_rounded_avatar.png and b/docs/docs_screenshots/test/message_list/goldens/macos/message_rounded_avatar.png differ diff --git a/docs/docs_screenshots/test/message_list/goldens/macos/message_styles.png b/docs/docs_screenshots/test/message_list/goldens/macos/message_styles.png index d60faf835..e228b9d97 100644 Binary files a/docs/docs_screenshots/test/message_list/goldens/macos/message_styles.png and b/docs/docs_screenshots/test/message_list/goldens/macos/message_styles.png differ diff --git a/docs/docs_screenshots/test/message_list/goldens/macos/message_theming.png b/docs/docs_screenshots/test/message_list/goldens/macos/message_theming.png index b7b799431..38c4bf71f 100644 Binary files a/docs/docs_screenshots/test/message_list/goldens/macos/message_theming.png and b/docs/docs_screenshots/test/message_list/goldens/macos/message_theming.png differ diff --git a/docs/docs_screenshots/test/message_list/goldens/macos/message_widget_actions.png b/docs/docs_screenshots/test/message_list/goldens/macos/message_widget_actions.png index ea65f7aac..bcd237389 100644 Binary files a/docs/docs_screenshots/test/message_list/goldens/macos/message_widget_actions.png and b/docs/docs_screenshots/test/message_list/goldens/macos/message_widget_actions.png differ diff --git a/docs/docs_screenshots/test/message_search/goldens/macos/message_search_list_view.png b/docs/docs_screenshots/test/message_search/goldens/macos/message_search_list_view.png index 9b0383044..4124dae4c 100644 Binary files a/docs/docs_screenshots/test/message_search/goldens/macos/message_search_list_view.png and b/docs/docs_screenshots/test/message_search/goldens/macos/message_search_list_view.png differ diff --git a/docs/docs_screenshots/test/polls/goldens/macos/poll_creator.png b/docs/docs_screenshots/test/polls/goldens/macos/poll_creator.png index d5941fc46..c7ed46b6b 100644 Binary files a/docs/docs_screenshots/test/polls/goldens/macos/poll_creator.png and b/docs/docs_screenshots/test/polls/goldens/macos/poll_creator.png differ diff --git a/docs/docs_screenshots/test/polls/goldens/macos/poll_interactor.png b/docs/docs_screenshots/test/polls/goldens/macos/poll_interactor.png index 371083824..86bd009a3 100644 Binary files a/docs/docs_screenshots/test/polls/goldens/macos/poll_interactor.png and b/docs/docs_screenshots/test/polls/goldens/macos/poll_interactor.png differ diff --git a/docs/docs_screenshots/test/polls/goldens/macos/polls_composer.png b/docs/docs_screenshots/test/polls/goldens/macos/polls_composer.png index 44cfebb35..0e6a9d411 100644 Binary files a/docs/docs_screenshots/test/polls/goldens/macos/polls_composer.png and b/docs/docs_screenshots/test/polls/goldens/macos/polls_composer.png differ diff --git a/docs/docs_screenshots/test/thread_list/goldens/ci/thread_list_unread_banner.png b/docs/docs_screenshots/test/thread_list/goldens/ci/thread_list_unread_banner.png index 01bc57f97..e41f99724 100644 Binary files a/docs/docs_screenshots/test/thread_list/goldens/ci/thread_list_unread_banner.png and b/docs/docs_screenshots/test/thread_list/goldens/ci/thread_list_unread_banner.png differ diff --git a/docs/docs_screenshots/test/thread_list/goldens/macos/thread_list_tile_custom.png b/docs/docs_screenshots/test/thread_list/goldens/macos/thread_list_tile_custom.png index f9da03524..3ff755f0f 100644 Binary files a/docs/docs_screenshots/test/thread_list/goldens/macos/thread_list_tile_custom.png and b/docs/docs_screenshots/test/thread_list/goldens/macos/thread_list_tile_custom.png differ diff --git a/docs/docs_screenshots/test/thread_list/goldens/macos/thread_list_unread_banner.png b/docs/docs_screenshots/test/thread_list/goldens/macos/thread_list_unread_banner.png index 50296ef39..8786affc2 100644 Binary files a/docs/docs_screenshots/test/thread_list/goldens/macos/thread_list_unread_banner.png and b/docs/docs_screenshots/test/thread_list/goldens/macos/thread_list_unread_banner.png differ diff --git a/docs/docs_screenshots/test/thread_list/goldens/macos/thread_list_view.png b/docs/docs_screenshots/test/thread_list/goldens/macos/thread_list_view.png index 50296ef39..c51bc2ddb 100644 Binary files a/docs/docs_screenshots/test/thread_list/goldens/macos/thread_list_view.png and b/docs/docs_screenshots/test/thread_list/goldens/macos/thread_list_view.png differ diff --git a/docs/docs_screenshots/test/thread_list/goldens/macos/thread_list_view_empty.png b/docs/docs_screenshots/test/thread_list/goldens/macos/thread_list_view_empty.png index 8b616655b..c27e887a9 100644 Binary files a/docs/docs_screenshots/test/thread_list/goldens/macos/thread_list_view_empty.png and b/docs/docs_screenshots/test/thread_list/goldens/macos/thread_list_view_empty.png differ diff --git a/docs/docs_screenshots/test/thread_list/thread_list_view_test.dart b/docs/docs_screenshots/test/thread_list/thread_list_view_test.dart index 87a186712..763b197f5 100644 --- a/docs/docs_screenshots/test/thread_list/thread_list_view_test.dart +++ b/docs/docs_screenshots/test/thread_list/thread_list_view_test.dart @@ -280,6 +280,7 @@ void main() { client: client, controller: controller, banner: const StreamUnreadThreadsBanner( + enabled: true, unreadThreads: {'thread-1', 'thread-2', 'thread-3'}, ), ); diff --git a/docs/docs_screenshots/test/user_list/goldens/macos/user_list_view.png b/docs/docs_screenshots/test/user_list/goldens/macos/user_list_view.png index 2422dfeb5..5b1c3dec6 100644 Binary files a/docs/docs_screenshots/test/user_list/goldens/macos/user_list_view.png and b/docs/docs_screenshots/test/user_list/goldens/macos/user_list_view.png differ diff --git a/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_attachment_custom.png b/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_attachment_custom.png new file mode 100644 index 000000000..b31d807bf Binary files /dev/null and b/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_attachment_custom.png differ diff --git a/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_finished.png b/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_finished.png index a08ce8523..dffb3657d 100644 Binary files a/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_finished.png and b/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_finished.png differ diff --git a/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_hold_recording.png b/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_hold_recording.png index 68288c138..f706148d9 100644 Binary files a/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_hold_recording.png and b/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_hold_recording.png differ diff --git a/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_idle_tooltip.png b/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_idle_tooltip.png new file mode 100644 index 000000000..f6651cce6 Binary files /dev/null and b/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_idle_tooltip.png differ diff --git a/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_locked_recording.png b/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_locked_recording.png index 0f317b55d..9063648cc 100644 Binary files a/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_locked_recording.png and b/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_locked_recording.png differ diff --git a/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_stopped.png b/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_stopped.png new file mode 100644 index 000000000..fb8ff97bb Binary files /dev/null and b/docs/docs_screenshots/test/voice_recording/goldens/ci/voice_recording_stopped.png differ diff --git a/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_attachment.png b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_attachment.png index 223f63568..6c5329020 100644 Binary files a/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_attachment.png and b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_attachment.png differ diff --git a/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_attachment_custom.png b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_attachment_custom.png new file mode 100644 index 000000000..62d14412d Binary files /dev/null and b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_attachment_custom.png differ diff --git a/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_attachment_playing.png b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_attachment_playing.png index 223f63568..6c5329020 100644 Binary files a/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_attachment_playing.png and b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_attachment_playing.png differ diff --git a/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_enabled.png b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_enabled.png index e43c9a3b0..b413c61f6 100644 Binary files a/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_enabled.png and b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_enabled.png differ diff --git a/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_finished.png b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_finished.png index 6f5ec0995..740cdfdf7 100644 Binary files a/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_finished.png and b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_finished.png differ diff --git a/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_hold_recording.png b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_hold_recording.png index 29042080f..fa3f2bead 100644 Binary files a/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_hold_recording.png and b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_hold_recording.png differ diff --git a/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_idle.png b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_idle.png index e43c9a3b0..b413c61f6 100644 Binary files a/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_idle.png and b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_idle.png differ diff --git a/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_idle_tooltip.png b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_idle_tooltip.png new file mode 100644 index 000000000..380087ce3 Binary files /dev/null and b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_idle_tooltip.png differ diff --git a/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_locked_recording.png b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_locked_recording.png index b09eb4473..c8e7e1aaa 100644 Binary files a/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_locked_recording.png and b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_locked_recording.png differ diff --git a/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_stopped.png b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_stopped.png new file mode 100644 index 000000000..264c060ed Binary files /dev/null and b/docs/docs_screenshots/test/voice_recording/goldens/macos/voice_recording_stopped.png differ diff --git a/docs/docs_screenshots/test/voice_recording/voice_recording_test.dart b/docs/docs_screenshots/test/voice_recording/voice_recording_test.dart index b02d2666d..7e7b69314 100644 --- a/docs/docs_screenshots/test/voice_recording/voice_recording_test.dart +++ b/docs/docs_screenshots/test/voice_recording/voice_recording_test.dart @@ -3,8 +3,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:record/record.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/stream_chat_flutter.dart'; import '../src/fakes.dart'; @@ -27,6 +25,7 @@ StreamAudioRecorderController _makeRecorderController(AudioRecorderState initial Widget _buildVoiceRecordingMessageInputScaffold({ required MockClient client, required MockChannel channel, + StreamMessageInputController? messageInputController, }) { return MaterialApp( theme: docsScreenshotsTheme(), @@ -42,7 +41,10 @@ Widget _buildVoiceRecordingMessageInputScaffold({ body: Column( children: [ Expanded(child: Container()), - const StreamMessageInput(enableVoiceRecording: true), + StreamMessageInput( + enableVoiceRecording: true, + messageInputController: messageInputController, + ), ], ), ), @@ -51,40 +53,20 @@ Widget _buildVoiceRecordingMessageInputScaffold({ ); } -Widget _buildVoiceRecorderScaffold({ - required MockClient client, - required Widget child, -}) { - return MaterialApp( - theme: docsScreenshotsTheme(), - debugShowCheckedModeBanner: false, - home: StreamChat( - client: client, - streamChatThemeData: docsStreamChatThemeData(), - connectivityStream: Stream.value([ConnectivityResult.mobile]), - child: Scaffold( - body: Padding( - padding: const EdgeInsets.all(8), - child: Center(child: child), - ), - ), - ), - ); -} - /// Scaffold that shows a message bubble + the voice widget + an input bar, /// giving context to how voice recording looks in a real conversation. Widget _buildVoiceRecordingContextScaffold({ required MockClient client, required MockChannel channel, required Widget voiceWidget, + StreamChatThemeData? streamChatThemeData, }) { return MaterialApp( theme: docsScreenshotsTheme(), debugShowCheckedModeBanner: false, home: StreamChat( client: client, - streamChatThemeData: docsStreamChatThemeData(), + streamChatThemeData: streamChatThemeData ?? docsStreamChatThemeData(), connectivityStream: Stream.value([ConnectivityResult.mobile]), child: StreamChannel( showLoading: false, @@ -120,6 +102,59 @@ Widget _buildVoiceRecordingContextScaffold({ ); } +/// Scaffold that shows a full message input bar (with attachment button and +/// placeholder) using [StreamChatMessageComposer] so we can inject a custom +/// [audioRecorderController] to control the recording state. +/// +/// The outer [Material] + bottom padding mirrors what [StreamMessageInput] +/// wraps around [StreamChatMessageComposer] internally. +Widget _buildVoiceRecordingComposerScaffold({ + required MockClient client, + required MockChannel channel, + required StreamAudioRecorderController audioRecorderController, +}) { + return MaterialApp( + theme: docsScreenshotsTheme(), + debugShowCheckedModeBanner: false, + home: StreamChat( + client: client, + streamChatThemeData: docsStreamChatThemeData(), + connectivityStream: Stream.value([ConnectivityResult.mobile]), + child: StreamChannel( + showLoading: false, + channel: channel, + child: Scaffold( + body: Column( + children: [ + Expanded(child: Container()), + Builder( + builder: (context) { + return Material( + child: DecoratedBox( + decoration: BoxDecoration( + color: context.streamColorScheme.backgroundElevation1, + ), + child: Padding( + padding: EdgeInsets.only(bottom: context.streamSpacing.md), + child: StreamChatMessageComposer( + onSendPressed: () {}, + onAttachmentButtonPressed: () {}, + placeholder: 'Send a message', + audioRecorderController: audioRecorderController, + ), + ), + ), + ); + }, + ), + ], + ), + ), + ), + ), + ); +} + void _setupChannel(MockClient client, MockClientState clientState, MockChannel channel, MockChannelState channelState) { setupMockChannel( client: client, @@ -179,20 +214,23 @@ void main() { goldenTest( 'voice recording hold recording state', fileName: 'voice_recording_hold_recording', - constraints: const BoxConstraints.tightFor(width: 375, height: 56), + constraints: const BoxConstraints.tightFor(width: 375, height: 200), builder: () { final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + _setupChannel(client, clientState, channel, channelState); final holdState = RecordStateRecordingHold( duration: const Duration(seconds: 5), waveform: List.generate(20, (i) => (i % 5) / 5.0), ); - return _buildVoiceRecorderScaffold( + return _buildVoiceRecordingComposerScaffold( client: client, - child: StreamMessageComposerRecordingOngoing( - audioRecorderController: _makeRecorderController(holdState), - ), + channel: channel, + audioRecorderController: _makeRecorderController(holdState), ); }, ); @@ -200,34 +238,37 @@ void main() { goldenTest( 'voice recording locked recording state', fileName: 'voice_recording_locked_recording', - constraints: const BoxConstraints.tightFor(width: 375, height: 120), + constraints: const BoxConstraints.tightFor(width: 375, height: 200), builder: () { final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + _setupChannel(client, clientState, channel, channelState); final lockedState = RecordStateRecordingLocked( duration: const Duration(seconds: 12), waveform: List.generate(20, (i) => (i % 5) / 5.0), ); - return _buildVoiceRecorderScaffold( + return _buildVoiceRecordingComposerScaffold( client: client, - child: MessageComposerRecordingLocked( - audioRecorderController: _makeRecorderController(lockedState), - feedback: const AudioRecorderFeedback(), - messageInputController: StreamMessageInputController(), - sendMessageCallback: null, - state: lockedState, - ), + channel: channel, + audioRecorderController: _makeRecorderController(lockedState), ); }, ); goldenTest( - 'voice recording finished state', - fileName: 'voice_recording_finished', - constraints: const BoxConstraints.tightFor(width: 375, height: 120), + 'voice recording stopped state', + fileName: 'voice_recording_stopped', + constraints: const BoxConstraints.tightFor(width: 375, height: 150), builder: () { final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + _setupChannel(client, clientState, channel, channelState); final stoppedState = RecordStateStopped( audioRecording: Attachment( @@ -241,15 +282,42 @@ void main() { ), ); - return _buildVoiceRecorderScaffold( + return _buildVoiceRecordingComposerScaffold( client: client, - child: MessageComposerRecordingStopped( - audioRecorderController: _makeRecorderController(stoppedState), - feedback: const AudioRecorderFeedback(), - messageInputController: StreamMessageInputController(), - sendMessageCallback: null, - recordingState: stoppedState, - ), + channel: channel, + audioRecorderController: _makeRecorderController(stoppedState), + ); + }, + ); + + goldenTest( + 'voice recording finished state', + fileName: 'voice_recording_finished', + constraints: const BoxConstraints.tightFor(width: 375, height: 200), + builder: () { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + _setupChannel(client, clientState, channel, channelState); + + final messageInputController = StreamMessageInputController() + ..addAttachment( + Attachment( + type: 'voiceRecording', + assetUrl: 'https://example.com/recording.m4a', + uploadState: const UploadState.success(), + extraData: const { + 'duration': 15.0, + 'waveform_data': [0.1, 0.5, 0.9, 0.4, 0.2], + }, + ), + ); + + return _buildVoiceRecordingMessageInputScaffold( + client: client, + channel: channel, + messageInputController: messageInputController, ); }, ); @@ -326,6 +394,29 @@ void main() { }, ); + goldenTest( + 'voice recording idle tooltip', + fileName: 'voice_recording_idle_tooltip', + constraints: const BoxConstraints.tightFor(width: 375, height: 150), + builder: () { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + _setupChannel(client, clientState, channel, channelState); + + final audioRecorderController = _makeRecorderController( + const RecordStateIdle(message: 'Hold to record, release to send.'), + ); + + return _buildVoiceRecordingComposerScaffold( + client: client, + channel: channel, + audioRecorderController: audioRecorderController, + ); + }, + ); + goldenTest( 'voice recording attachment playing', fileName: 'voice_recording_attachment_playing', @@ -397,4 +488,98 @@ void main() { ); }, ); + + goldenTest( + 'voice recording attachment custom theme', + fileName: 'voice_recording_attachment_custom', + constraints: const BoxConstraints.tightFor(width: 375, height: 400), + builder: () { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + _setupChannel(client, clientState, channel, channelState); + + const roboto = TextStyle(fontFamily: 'Roboto'); + final customTheme = docsStreamChatThemeData().copyWith( + voiceRecordingAttachmentTheme: StreamVoiceRecordingAttachmentThemeData( + titleTextStyle: roboto.copyWith(color: Colors.black54), + durationTextStyle: roboto.copyWith(color: Colors.black54), + activeDurationTextStyle: roboto.copyWith(color: Colors.black), + controlButtonStyle: StreamButtonThemeStyle.from( + backgroundColor: Colors.green, + foregroundColor: Colors.white, + ), + speedToggleStyle: StreamPlaybackSpeedToggleStyle.from( + backgroundColor: Colors.green, + foregroundColor: Colors.white, + ), + waveformStyle: const StreamAudioWaveformThemeData( + color: Colors.black54, + progressColor: Colors.black, + ), + ), + ); + + final voiceMessage = Message( + id: 'voice-msg-custom', + user: User(id: 'user-2', name: 'Bob'), + attachments: [ + Attachment( + type: 'voiceRecording', + assetUrl: 'https://example.com/recording.m4a', + uploadState: const UploadState.success(), + extraData: const { + 'duration': 15.0, + 'waveform_data': [ + 0.1, + 0.3, + 0.5, + 0.7, + 0.9, + 0.7, + 0.5, + 0.3, + 0.1, + 0.2, + 0.4, + 0.6, + 0.8, + 0.6, + 0.4, + 0.2, + 0.5, + 0.8, + 0.6, + 0.3, + 0.1, + 0.4, + 0.7, + 0.9, + 0.6, + 0.3, + 0.1, + 0.4, + 0.7, + 0.5, + 0.2, + 0.6, + 0.8, + 0.4, + 0.2, + ], + }, + ), + ], + createdAt: DateTime(2024, 6, 1, 10, 0), + ); + + return _buildVoiceRecordingContextScaffold( + client: client, + channel: channel, + voiceWidget: StreamMessageWidget(message: voiceMessage), + streamChatThemeData: customTheme, + ); + }, + ); } 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 c59b59500..d15950af9 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 @@ -151,19 +151,31 @@ class _StreamChatMessageComposerState extends State { const targetAlignment = AlignmentDirectional.topEnd; const followerAlignment = AlignmentDirectional.bottomEnd; + final idleMessage = state is RecordStateIdle ? state.message : null; + final showIdleTooltip = idleMessage != null && idleMessage.isNotEmpty; + return PortalTarget( + visible: showIdleTooltip, anchor: Aligned( - target: targetAlignment.resolve(textDirection), - follower: followerAlignment.resolve(textDirection), - offset: Offset(-streamSpacing.md, -streamSpacing.md).directional(textDirection), + target: Alignment.topCenter, + follower: Alignment.bottomCenter, + offset: Offset(0, -streamSpacing.md), ), - visible: state is RecordStateRecording, - portalFollower: SwipeToLockButton(isLocked: state is RecordStateRecordingLocked), - child: DefaultStreamChatMessageComposer( - props: widget.props, - inputController: _controller, - audioRecorderState: state, - body: body, + portalFollower: showIdleTooltip ? HoldToRecordInfoTooltip(message: idleMessage) : const SizedBox.shrink(), + child: PortalTarget( + anchor: Aligned( + target: targetAlignment.resolve(textDirection), + follower: followerAlignment.resolve(textDirection), + offset: Offset(-streamSpacing.md, -streamSpacing.md).directional(textDirection), + ), + visible: state is RecordStateRecording, + portalFollower: SwipeToLockButton(isLocked: state is RecordStateRecordingLocked), + child: DefaultStreamChatMessageComposer( + props: widget.props, + inputController: _controller, + audioRecorderState: state, + body: body, + ), ), ); }, diff --git a/packages/stream_chat_flutter/lib/src/localization/translations.dart b/packages/stream_chat_flutter/lib/src/localization/translations.dart index ca334c117..a8cd2cca0 100644 --- a/packages/stream_chat_flutter/lib/src/localization/translations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/translations.dart @@ -1346,7 +1346,7 @@ Attachment limit exceeded: it's not possible to add more than $limit attachments String get slideToCancelLabel => 'Slide to cancel'; @override - String get holdToRecordLabel => 'Hold to record, release to send.'; + String get holdToRecordLabel => 'Hold to record. Release to save.'; @override String get sendAnywayLabel => 'Send Anyway'; 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 fd6ca4520..4f410c1c3 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 @@ -985,30 +985,23 @@ class HoldToRecordInfoTooltip extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); - - const recordButtonWidth = - kDefaultMessageInputIconSize + kDefaultMessageInputIconPadding * 2; // right, left padding. - - const arrowSize = Size(recordButtonWidth / 2, 6); + final spacing = context.streamSpacing; + final colorScheme = context.streamColorScheme; + final radius = context.streamRadius; + final textTheme = context.streamTextTheme; - return Padding( - padding: EdgeInsets.only(bottom: arrowSize.height), - child: CustomPaint( - painter: TooltipPainter( - arrowSize: arrowSize, - arrowMargin: arrowSize.width / 2, - color: theme.colorTheme.textLowEmphasis, - borderRadius: BorderRadius.circular(24), - ), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 12), - child: Text( - message, - style: theme.textTheme.body.copyWith( - color: theme.colorTheme.barsBg, - ), - ), + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + padding: EdgeInsets.symmetric(vertical: spacing.sm, horizontal: spacing.xs * 2), + decoration: ShapeDecoration( + color: colorScheme.backgroundInverse, + shape: RoundedSuperellipseBorder(borderRadius: .all(radius.max)), + ), + child: Text( + message, + textAlign: TextAlign.center, + style: textTheme.captionDefault.copyWith( + color: colorScheme.textOnInverse, ), ), ); diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 7dad6ed0b..b7df0356a 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -3,6 +3,7 @@ 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 + StreamAudioWaveformThemeData, StreamAvatarGroupSize, StreamAvatarSize, StreamAvatarStackSize, 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 00b0e33ea..b852ced22 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 @@ -632,7 +632,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get slideToCancelLabel => 'Slide to cancel'; @override - String get holdToRecordLabel => 'Hold to record, release to send.'; + String get holdToRecordLabel => 'Hold to record. Release to save.'; @override String get sendAnywayLabel => 'Send Anyway';