feat: improve loading visualisation on the chatdetailscreen

This commit is contained in:
Freek van de Ven 2025-02-20 16:43:41 +01:00 committed by FlutterJoey
parent 5975e2f1c0
commit 313ead2029
6 changed files with 47 additions and 36 deletions

View file

@ -21,6 +21,7 @@
- Added chatScreenBuilder to the userstory configuration to customize the specific chat screen with a ChatModel as argument - Added chatScreenBuilder to the userstory configuration to customize the specific chat screen with a ChatModel as argument
- Added senderTitleResolver to the ChatOptions to resolve the title of the sender in the chat message - Added senderTitleResolver to the ChatOptions to resolve the title of the sender in the chat message
- Added imageProviderResolver to the ChatOptions to resolve ImageProvider for all images in the userstory - Added imageProviderResolver to the ChatOptions to resolve ImageProvider for all images in the userstory
- Added enabled boolean to the messageInputBuilder and made parameters named
## 4.0.0 ## 4.0.0
- Move to the new user story architecture - Move to the new user story architecture

View file

@ -122,12 +122,13 @@ typedef ImagePickerBuilder = Future<Uint8List?> Function(
/// The text input builder /// The text input builder
typedef TextInputBuilder = Widget Function( typedef TextInputBuilder = Widget Function(
BuildContext context, BuildContext context, {
TextEditingController textEditingController, required TextEditingController textEditingController,
Widget suffixIcon, required Widget suffixIcon,
ChatTranslations translations, required ChatTranslations translations,
VoidCallback onSubmit, required VoidCallback onSubmit,
); required bool enabled,
});
/// The base screen builder /// The base screen builder
/// [title] is the title of the screen and can be null while loading /// [title] is the title of the screen and can be null while loading

View file

@ -11,7 +11,7 @@ class ChatOptions {
ChatOptions({ ChatOptions({
this.dateformat, this.dateformat,
this.groupChatEnabled = true, this.groupChatEnabled = true,
this.enableLoadingIndicator = false, this.enableLoadingIndicator = true,
this.translations = const ChatTranslations.empty(), this.translations = const ChatTranslations.empty(),
this.builders = const ChatBuilders(), this.builders = const ChatBuilders(),
this.spacing = const ChatSpacing(), this.spacing = const ChatSpacing(),

View file

@ -69,6 +69,10 @@ class ChatDetailScreen extends HookWidget {
var usersSnapshot = useStream(allUsersStream); var usersSnapshot = useStream(allUsersStream);
var allUsers = usersSnapshot.data ?? []; var allUsers = usersSnapshot.data ?? [];
var chatIsloading =
chatSnapshot.connectionState == ConnectionState.waiting ||
usersSnapshot.connectionState == ConnectionState.waiting;
useEffect( useEffect(
() { () {
if (chat == null) return; if (chat == null) return;
@ -106,6 +110,7 @@ class ChatDetailScreen extends HookWidget {
onUploadImage: onUploadImage, onUploadImage: onUploadImage,
onMessageSubmit: onMessageSubmit, onMessageSubmit: onMessageSubmit,
onReadChat: onReadChat, onReadChat: onReadChat,
chatIsLoading: chatIsloading,
); );
if (options.builders.chatScreenBuilder != null) { if (options.builders.chatScreenBuilder != null) {
@ -231,6 +236,7 @@ class _ChatBody extends HookWidget {
required this.onUploadImage, required this.onUploadImage,
required this.onMessageSubmit, required this.onMessageSubmit,
required this.onReadChat, required this.onReadChat,
required this.chatIsLoading,
}); });
final String chatId; final String chatId;
@ -240,6 +246,7 @@ class _ChatBody extends HookWidget {
final Function(Uint8List image) onUploadImage; final Function(Uint8List image) onUploadImage;
final Function(String text) onMessageSubmit; final Function(String text) onMessageSubmit;
final Function(ChatModel chat) onReadChat; final Function(ChatModel chat) onReadChat;
final bool chatIsLoading;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -377,11 +384,6 @@ class _ChatBody extends HookWidget {
[autoScrollEnabled.value], [autoScrollEnabled.value],
); );
if (chat == null) {
if (!options.enableLoadingIndicator) return const SizedBox.shrink();
return options.builders.loadingWidgetBuilder.call(context);
}
var userMap = <String, UserModel>{}; var userMap = <String, UserModel>{};
for (var u in chatUsers) { for (var u in chatUsers) {
userMap[u.id] = u; userMap[u.id] = u;
@ -424,18 +426,23 @@ class _ChatBody extends HookWidget {
return Column( return Column(
children: [ children: [
Expanded( if (chatIsLoading && options.enableLoadingIndicator) ...[
child: ListView.builder( Expanded(child: options.builders.loadingWidgetBuilder.call(context)),
reverse: false, ] else ...[
controller: scrollController, Expanded(
physics: const AlwaysScrollableScrollPhysics(), child: ListView.builder(
padding: const EdgeInsets.only(top: 24), reverse: false,
itemCount: listViewChildren.length, controller: scrollController,
itemBuilder: (context, index) => listViewChildren[index], physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.only(top: 24),
itemCount: listViewChildren.length,
itemBuilder: (context, index) => listViewChildren[index],
),
), ),
), ],
ChatBottomInputSection( ChatBottomInputSection(
chat: chat!, chat: chat,
isLoading: chatIsLoading,
onPressSelectImage: () async => onPressSelectImage( onPressSelectImage: () async => onPressSelectImage(
context, context,
options, options,

View file

@ -8,13 +8,18 @@ class ChatBottomInputSection extends HookWidget {
/// Creates a new [ChatBottomInputSection]. /// Creates a new [ChatBottomInputSection].
const ChatBottomInputSection({ const ChatBottomInputSection({
required this.chat, required this.chat,
required this.isLoading,
required this.onMessageSubmit, required this.onMessageSubmit,
this.onPressSelectImage, this.onPressSelectImage,
super.key, super.key,
}); });
/// The chat model. /// The chat model.
final ChatModel chat; final ChatModel? chat;
/// Whether the chat is still loading.
/// The inputfield is disabled when the chat is loading.
final bool isLoading;
/// Callback function invoked when a message is submitted. /// Callback function invoked when a message is submitted.
final Function(String text) onMessageSubmit; final Function(String text) onMessageSubmit;
@ -65,7 +70,7 @@ class ChatBottomInputSection extends HookWidget {
children: [ children: [
IconButton( IconButton(
alignment: Alignment.bottomRight, alignment: Alignment.bottomRight,
onPressed: onPressSelectImage, onPressed: isLoading ? null : onPressSelectImage,
icon: Icon( icon: Icon(
Icons.image_outlined, Icons.image_outlined,
color: options.iconEnabledColor, color: options.iconEnabledColor,
@ -75,7 +80,7 @@ class ChatBottomInputSection extends HookWidget {
alignment: Alignment.bottomRight, alignment: Alignment.bottomRight,
disabledColor: options.iconDisabledColor, disabledColor: options.iconDisabledColor,
color: options.iconEnabledColor, color: options.iconEnabledColor,
onPressed: onClickSendMessage, onPressed: isLoading ? null : onClickSendMessage,
icon: const Icon(Icons.send_rounded), icon: const Icon(Icons.send_rounded),
), ),
], ],
@ -95,6 +100,7 @@ class ChatBottomInputSection extends HookWidget {
keyboardType: TextInputType.multiline, keyboardType: TextInputType.multiline,
maxLines: null, maxLines: null,
controller: textController, controller: textController,
enabled: !isLoading,
decoration: InputDecoration( decoration: InputDecoration(
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25), borderRadius: BorderRadius.circular(25),
@ -141,10 +147,11 @@ class ChatBottomInputSection extends HookWidget {
constraints: const BoxConstraints(maxHeight: 120, minHeight: 45), constraints: const BoxConstraints(maxHeight: 120, minHeight: 45),
child: options.builders.messageInputBuilder?.call( child: options.builders.messageInputBuilder?.call(
context, context,
textController, textEditingController: textController,
messageSendButtons, suffixIcon: messageSendButtons,
options.translations, translations: options.translations,
onSubmitField, onSubmit: onSubmitField,
enabled: !isLoading,
) ?? ) ??
defaultInputField, defaultInputField,
), ),

View file

@ -11,13 +11,8 @@ class DefaultChatLoadingOverlay extends StatelessWidget {
const DefaultChatLoadingOverlay(); const DefaultChatLoadingOverlay();
@override @override
Widget build(BuildContext context) => const Column( Widget build(BuildContext context) =>
children: [ const Center(child: CircularProgressIndicator());
SizedBox(height: 12),
Center(child: CircularProgressIndicator()),
SizedBox(height: 12),
],
);
} }
/// A small row spinner item to show partial loading /// A small row spinner item to show partial loading