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 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 enabled boolean to the messageInputBuilder and made parameters named
## 4.0.0
- Move to the new user story architecture

View file

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

View file

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

View file

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

View file

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

View file

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