diff --git a/melos.yaml b/melos.yaml index f8ca447b1..8e2a53afc 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: 492f6f4ef8c73c64b3f399f92c5e508b7fb39e55 + ref: 5572475bb3e4e661cfeca3c9bc232c465a6e08cd path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_command_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_command_picker.dart index 9218959a3..10ebd28ce 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_command_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_command_picker.dart @@ -24,18 +24,18 @@ class StreamCommandPicker extends StatelessWidget { return OptionDrawer( margin: EdgeInsets.zero, - title: Padding( - padding: EdgeInsets.symmetric(horizontal: spacing.md), - child: Text( - context.translations.instantCommandsLabel, - style: textTheme.headlineBold, - ), - ), child: ListView.builder( padding: EdgeInsets.zero, - itemCount: commands.length, + itemCount: commands.length + 1, itemBuilder: (context, index) { - final command = commands[index]; + final command = index == 0 ? null : commands[index - 1]; + if (command == null) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: spacing.md), + child: Text(context.translations.instantCommandsLabel, style: textTheme.headlineBold), + ); + } + return Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: InkWell( 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 fdf1717e3..c5ff97c05 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 @@ -8,6 +8,7 @@ import 'package:stream_chat_flutter/scrollable_positioned_list/scrollable_positi import 'package:stream_chat_flutter/src/message_list_view/floating_date_divider.dart'; 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/stream_message_list_skeleton_loading.dart'; import 'package:stream_chat_flutter/src/message_list_view/thread_separator.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'; @@ -563,11 +564,7 @@ class _StreamMessageListViewState extends State { child: MessageListCore( paginationLimit: widget.paginationLimit, messageFilter: widget.messageFilter, - loadingBuilder: - widget.loadingBuilder ?? - (context) => const Center( - child: CircularProgressIndicator.adaptive(), - ), + loadingBuilder: widget.loadingBuilder ?? (context) => const StreamMessageListSkeletonLoading(), emptyBuilder: widget.emptyBuilder ?? (context) => Center( diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/stream_message_list_skeleton_loading.dart b/packages/stream_chat_flutter/lib/src/message_list_view/stream_message_list_skeleton_loading.dart new file mode 100644 index 000000000..7a1c7f378 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/message_list_view/stream_message_list_skeleton_loading.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// A shimmer loading placeholder for the message list view. +/// +/// Displays a skeleton UI with shimmer animation that mimics a chat +/// conversation with incoming (left-aligned) and outgoing (right-aligned) +/// message bubbles using [StreamSkeletonLoading] and [StreamSkeletonBox]. +class StreamMessageListSkeletonLoading extends StatelessWidget { + /// Creates a new instance of [StreamMessageListSkeletonLoading]. + const StreamMessageListSkeletonLoading({super.key}); + + @override + Widget build(BuildContext context) { + final spacing = context.streamSpacing; + + return StreamSkeletonLoading( + child: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: EdgeInsets.all(spacing.md), + child: Column( + children: [ + _IncomingBubble(), + SizedBox(height: spacing.lg), + _OutgoingBubble(), + SizedBox(height: spacing.lg), + _IncomingBubble(), + SizedBox(height: spacing.lg), + _OutgoingBubble(), + SizedBox(height: spacing.lg), + _IncomingBubble(), + SizedBox(height: spacing.md), + ], + ), + ); + }, + ), + ); + } +} + +class _IncomingBubble extends StatelessWidget { + @override + Widget build(BuildContext context) { + final spacing = context.streamSpacing; + + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + const StreamSkeletonBox.circular(radius: 16), + SizedBox(width: spacing.xs), + Expanded( + flex: 3, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StreamSkeletonBox( + height: 56, + borderRadius: BorderRadius.only( + topRight: context.streamRadius.xl, + bottomRight: context.streamRadius.xl, + topLeft: context.streamRadius.xl, + ), + ), + SizedBox(height: spacing.xs), + StreamSkeletonBox( + width: 56, + height: 12, + borderRadius: BorderRadius.all(context.streamRadius.max), + ), + SizedBox(height: spacing.xs), + ], + ), + ), + const Spacer( + flex: 1, + ), + ], + ); + } +} + +class _OutgoingBubble extends StatelessWidget { + @override + Widget build(BuildContext context) { + final spacing = context.streamSpacing; + + return Row( + children: [ + const Spacer( + flex: 1, + ), + Expanded( + flex: 2, + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + StreamSkeletonBox( + height: 56, + borderRadius: BorderRadius.all( + context.streamRadius.xl, + ), + ), + SizedBox(height: spacing.xs), + StreamSkeletonBox( + width: 56, + height: 12, + borderRadius: BorderRadius.all(context.streamRadius.max), + ), + ], + ), + ), + ], + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_skeleton_loading.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_skeleton_loading.dart new file mode 100644 index 000000000..005f4d3fe --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_skeleton_loading.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// A shimmer loading placeholder for the channel list view. +/// +/// Displays a skeleton UI with shimmer animation using +/// [StreamSkeletonLoading] and [StreamSkeletonBox] from the core package. +class StreamChannelListSkeletonLoading extends StatelessWidget { + /// Creates a new instance of [StreamChannelListSkeletonLoading]. + const StreamChannelListSkeletonLoading({ + super.key, + this.itemCount = 7, + }); + + /// The number of skeleton items to display. + final int itemCount; + + @override + Widget build(BuildContext context) { + return StreamSkeletonLoading( + child: ListView.separated( + physics: const NeverScrollableScrollPhysics(), + itemCount: itemCount, + separatorBuilder: (context, index) => const SizedBox(height: 1), + itemBuilder: (context, index) => const _StreamChannelListItemSkeleton(), + ), + ); + } +} + +class _StreamChannelListItemSkeleton extends StatelessWidget { + const _StreamChannelListItemSkeleton(); + + @override + Widget build(BuildContext context) { + final spacing = context.streamSpacing; + + return Padding( + padding: EdgeInsets.all(spacing.md), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + spacing: spacing.md, + children: [ + const StreamSkeletonBox.circular(radius: 24), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: spacing.xs, + children: [ + Row( + children: [ + Expanded( + child: StreamSkeletonBox( + width: double.infinity, + height: 16, + borderRadius: BorderRadius.all(context.streamRadius.max), + ), + ), + SizedBox(width: spacing.md), + StreamSkeletonBox( + width: 48, + height: 16, + borderRadius: BorderRadius.all(context.streamRadius.max), + ), + ], + ), + Row( + children: [ + Expanded( + flex: 3, + child: StreamSkeletonBox( + width: double.infinity, + height: 16, + borderRadius: BorderRadius.all(context.streamRadius.max), + ), + ), + const Spacer( + flex: 2, + ), + ], + ), + ], + ), + ), + ], + ), + ); + } +} 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 1d697dfd6..06af4ca41 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 @@ -1,9 +1,9 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/scroll_view/channel_scroll_view/stream_channel_list_skeleton_loading.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 [StreamChannelListView]. @@ -347,11 +347,7 @@ class StreamChannelListView extends StatelessWidget { child: StreamScrollViewLoadMoreIndicator(), ), ), - loadingBuilder: (context) => - loadingBuilder?.call(context) ?? - const Center( - child: StreamScrollViewLoadingWidget(), - ), + loadingBuilder: (context) => loadingBuilder?.call(context) ?? const StreamChannelListSkeletonLoading(), errorBuilder: (context, error) => errorBuilder?.call(context, error) ?? Center( diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_skeleton_loading.dart b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_skeleton_loading.dart new file mode 100644 index 000000000..6a92bc91e --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_skeleton_loading.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// A shimmer loading placeholder for the thread list view. +/// +/// Displays a skeleton UI with shimmer animation using +/// [StreamSkeletonLoading] and [StreamSkeletonBox] from the core package. +class StreamThreadListSkeletonLoading extends StatelessWidget { + /// Creates a new instance of [StreamThreadListSkeletonLoading]. + const StreamThreadListSkeletonLoading({ + super.key, + this.itemCount = 6, + }); + + /// The number of skeleton items to display. + final int itemCount; + + @override + Widget build(BuildContext context) { + return StreamSkeletonLoading( + child: ListView.separated( + physics: const NeverScrollableScrollPhysics(), + itemCount: itemCount, + separatorBuilder: (context, index) => const SizedBox(height: 1), + itemBuilder: (context, index) => const _StreamThreadListItemSkeleton(), + ), + ); + } +} + +class _StreamThreadListItemSkeleton extends StatelessWidget { + const _StreamThreadListItemSkeleton(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.all(context.streamSpacing.sm), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const StreamSkeletonBox.circular(radius: 24), + SizedBox(width: context.streamSpacing.sm), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + flex: 2, + child: StreamSkeletonBox( + width: double.infinity, + height: 12, + borderRadius: BorderRadius.all(context.streamRadius.max), + ), + ), + SizedBox(width: context.streamSpacing.sm), + const Spacer( + flex: 2, + ), + StreamSkeletonBox( + width: 48, + height: 16, + borderRadius: BorderRadius.all(context.streamRadius.max), + ), + ], + ), + SizedBox(height: context.streamSpacing.xs), + Row( + children: [ + Expanded( + child: StreamSkeletonBox( + width: double.infinity, + height: 20, + borderRadius: BorderRadius.all(context.streamRadius.max), + ), + ), + SizedBox(width: context.streamSpacing.sm), + const SizedBox(width: 48), + ], + ), + SizedBox(height: context.streamSpacing.xs), + Row( + children: [ + const StreamSkeletonBox.circular(radius: 12), + SizedBox(width: context.streamSpacing.xs), + StreamSkeletonBox( + width: 64, + height: 12, + borderRadius: BorderRadius.all(context.streamRadius.max), + ), + SizedBox(width: context.streamSpacing.xs), + StreamSkeletonBox( + width: 64, + height: 12, + borderRadius: BorderRadius.all(context.streamRadius.max), + ), + ], + ), + ], + ), + ), + ], + ), + ); + } +} 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 6fc1fea02..62bf3521d 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 @@ -3,7 +3,7 @@ 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/src/scroll_view/thread_scroll_view/stream_thread_list_skeleton_loading.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Default separator builder for [StreamThreadListView]. @@ -342,7 +342,7 @@ class StreamThreadListView extends StatelessWidget { loadingBuilder: (context) => loadingBuilder?.call(context) ?? const Center( - child: StreamScrollViewLoadingWidget(), + child: StreamThreadListSkeletonLoading(), ), errorBuilder: (context, error) => errorBuilder?.call(context, error) ?? diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 106ba49cd..eb5b708a1 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: 492f6f4ef8c73c64b3f399f92c5e508b7fb39e55 + ref: 5572475bb3e4e661cfeca3c9bc232c465a6e08cd path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1