From ba112415fdb69f1b75b078e40894c66a42a8ce02 Mon Sep 17 00:00:00 2001 From: Freek van de Ven Date: Tue, 18 Feb 2025 22:12:52 +0100 Subject: [PATCH] feat: add imagePickerBuilder to builders to override the default imagepicker for selecting an image --- CHANGELOG.md | 1 + .../lib/src/config/chat_builders.dart | 14 +++ .../chat_detail/chat_detail_screen.dart | 2 +- .../creation/new_group_chat_overview.dart | 2 +- .../widgets/default_image_picker.dart | 105 ++++++++++++++++++ .../creation/widgets/image_picker.dart | 83 -------------- 6 files changed, 122 insertions(+), 85 deletions(-) create mode 100644 packages/flutter_chat/lib/src/screens/creation/widgets/default_image_picker.dart delete mode 100644 packages/flutter_chat/lib/src/screens/creation/widgets/image_picker.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index f992f83..ed377f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Added ChatPaginationControls to the ChatOptions to allow for more control over the pagination - Fixed that chat message is automatically sent when the user presses enter on the keyboard in the chat input - Added sender and chatId to uploadImage in the ChatRepositoryInterface +- Added imagePickerBuilder to the builders in the ChatOptions to override the image picker with a custom implementation that needs to return a Future ## 4.0.0 - Move to the new user story architecture diff --git a/packages/flutter_chat/lib/src/config/chat_builders.dart b/packages/flutter_chat/lib/src/config/chat_builders.dart index 9da4148..ae300a2 100644 --- a/packages/flutter_chat/lib/src/config/chat_builders.dart +++ b/packages/flutter_chat/lib/src/config/chat_builders.dart @@ -1,9 +1,12 @@ +import "dart:typed_data"; + import "package:chat_repository_interface/chat_repository_interface.dart"; import "package:flutter/material.dart"; import "package:flutter_chat/src/config/chat_translations.dart"; import "package:flutter_chat/src/config/screen_types.dart"; import "package:flutter_chat/src/screens/chat_detail/widgets/default_loader.dart"; import "package:flutter_chat/src/screens/chat_detail/widgets/default_message_builder.dart"; +import "package:flutter_chat/src/screens/creation/widgets/default_image_picker.dart"; /// The chat builders class ChatBuilders { @@ -20,6 +23,7 @@ class ChatBuilders { this.noUsersPlaceholderBuilder, this.chatTitleBuilder, this.chatMessageBuilder = DefaultChatMessageBuilder.builder, + this.imagePickerBuilder = DefaultImagePickerDialog.builder, this.usernameBuilder, this.loadingWidgetBuilder = DefaultChatLoadingOverlay.builder, this.loadingChatMessageBuilder = DefaultChatMessageLoader.builder, @@ -75,6 +79,11 @@ class ChatBuilders { /// The image picker container builder final ImagePickerContainerBuilder? imagePickerContainerBuilder; + /// A way to provide your own image picker implementation + /// If not provided the [DefaultImagePicker.builder] will be used which + /// shows a modal buttom sheet with the option for a camera or gallery image + final ImagePickerBuilder imagePickerBuilder; + /// The loading widget builder /// This is used to build the loading widget that is displayed on the chat /// screen when loading the chat @@ -100,6 +109,11 @@ typedef ImagePickerContainerBuilder = Widget Function( ChatTranslations translations, ); +/// Builder definition for providing an image picker implementation +typedef ImagePickerBuilder = Future Function( + BuildContext context, +); + /// The text input builder typedef TextInputBuilder = Widget Function( BuildContext context, diff --git a/packages/flutter_chat/lib/src/screens/chat_detail/chat_detail_screen.dart b/packages/flutter_chat/lib/src/screens/chat_detail/chat_detail_screen.dart index 8313e4b..382fe50 100644 --- a/packages/flutter_chat/lib/src/screens/chat_detail/chat_detail_screen.dart +++ b/packages/flutter_chat/lib/src/screens/chat_detail/chat_detail_screen.dart @@ -6,7 +6,7 @@ import "package:flutter/material.dart"; import "package:flutter_chat/src/config/screen_types.dart"; import "package:flutter_chat/src/screens/chat_detail/widgets/chat_bottom.dart"; import "package:flutter_chat/src/screens/chat_detail/widgets/chat_widgets.dart"; -import "package:flutter_chat/src/screens/creation/widgets/image_picker.dart"; +import "package:flutter_chat/src/screens/creation/widgets/default_image_picker.dart"; import "package:flutter_chat/src/util/scope.dart"; import "package:flutter_hooks/flutter_hooks.dart"; diff --git a/packages/flutter_chat/lib/src/screens/creation/new_group_chat_overview.dart b/packages/flutter_chat/lib/src/screens/creation/new_group_chat_overview.dart index 90cac79..8b14538 100644 --- a/packages/flutter_chat/lib/src/screens/creation/new_group_chat_overview.dart +++ b/packages/flutter_chat/lib/src/screens/creation/new_group_chat_overview.dart @@ -3,7 +3,7 @@ import "dart:typed_data"; import "package:chat_repository_interface/chat_repository_interface.dart"; import "package:flutter/material.dart"; import "package:flutter_chat/src/config/screen_types.dart"; -import "package:flutter_chat/src/screens/creation/widgets/image_picker.dart"; +import "package:flutter_chat/src/screens/creation/widgets/default_image_picker.dart"; import "package:flutter_chat/src/util/scope.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_profile/flutter_profile.dart"; diff --git a/packages/flutter_chat/lib/src/screens/creation/widgets/default_image_picker.dart b/packages/flutter_chat/lib/src/screens/creation/widgets/default_image_picker.dart new file mode 100644 index 0000000..2e1e399 --- /dev/null +++ b/packages/flutter_chat/lib/src/screens/creation/widgets/default_image_picker.dart @@ -0,0 +1,105 @@ +import "dart:typed_data"; + +import "package:flutter/material.dart"; +import "package:flutter_chat/src/config/chat_options.dart"; +import "package:flutter_chat/src/config/chat_translations.dart"; +import "package:flutter_chat/src/util/scope.dart"; +import "package:flutter_image_picker/flutter_image_picker.dart"; + +/// The function to call when the user selects an image +Future onPressSelectImage( + BuildContext context, + ChatOptions options, + Function(Uint8List image) onUploadImage, +) async { + var image = await options.builders.imagePickerBuilder.call(context); + + if (image == null) return; + if (!context.mounted) return; + var messenger = ScaffoldMessenger.of(context) + ..showSnackBar( + _getImageLoadingSnackbar(options.translations), + ) + ..activate(); + await onUploadImage(image); + await Future.delayed(const Duration(seconds: 1)); + messenger.hideCurrentSnackBar(); +} + +/// Default image picker dialog for selecting an image from the gallery or +/// taking a photo. +class DefaultImagePickerDialog extends StatelessWidget { + /// Creates a new default image picker dialog. + const DefaultImagePickerDialog({ + super.key, + }); + + /// Builds the default image picker dialog. + static Future builder(BuildContext context) async => + showModalBottomSheet( + context: context, + builder: (context) => const DefaultImagePickerDialog(), + ); + + @override + Widget build(BuildContext context) { + var chatScope = ChatScope.of(context); + var options = chatScope.options; + var translations = options.translations; + var theme = Theme.of(context); + var textTheme = theme.textTheme; + + return options.builders.imagePickerContainerBuilder?.call( + context, + () => Navigator.of(context).pop(), + translations, + ) ?? + Container( + padding: const EdgeInsets.all(8.0), + color: Colors.white, + child: ImagePicker( + theme: ImagePickerTheme( + spaceBetweenIcons: 32.0, + iconColor: theme.primaryColor, + title: translations.imagePickerTitle, + titleStyle: textTheme.titleMedium, + iconSize: 60.0, + makePhotoText: translations.takePicture, + selectImageText: translations.uploadFile, + selectImageIcon: Icon( + color: theme.primaryColor, + Icons.insert_drive_file_rounded, + size: 60, + ), + closeButtonBuilder: (ontap) => TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text( + translations.cancelImagePickerBtn, + style: textTheme.bodyMedium!.copyWith( + fontSize: 18, + decoration: TextDecoration.underline, + ), + ), + ), + ), + ), + ); + } +} + +SnackBar _getImageLoadingSnackbar(ChatTranslations translations) => SnackBar( + duration: const Duration(minutes: 1), + content: Row( + children: [ + const SizedBox( + width: 25, + height: 25, + child: CircularProgressIndicator(color: Colors.grey), + ), + Padding( + padding: const EdgeInsets.only(left: 16.0), + child: Text(translations.imageUploading), + ), + ], + ), + ); diff --git a/packages/flutter_chat/lib/src/screens/creation/widgets/image_picker.dart b/packages/flutter_chat/lib/src/screens/creation/widgets/image_picker.dart deleted file mode 100644 index 07f92b3..0000000 --- a/packages/flutter_chat/lib/src/screens/creation/widgets/image_picker.dart +++ /dev/null @@ -1,83 +0,0 @@ -import "dart:typed_data"; - -import "package:flutter/material.dart"; -import "package:flutter_chat/src/config/chat_options.dart"; -import "package:flutter_chat/src/config/chat_translations.dart"; -import "package:flutter_image_picker/flutter_image_picker.dart"; - -/// The function to call when the user selects an image -Future onPressSelectImage( - BuildContext context, - ChatOptions options, - Function(Uint8List image) onUploadImage, -) async => - showModalBottomSheet( - context: context, - builder: (BuildContext context) => - options.builders.imagePickerContainerBuilder?.call( - context, - () => Navigator.of(context).pop(), - options.translations, - ) ?? - Container( - padding: const EdgeInsets.all(8.0), - color: Colors.white, - child: ImagePicker( - theme: ImagePickerTheme( - spaceBetweenIcons: 32.0, - iconColor: Theme.of(context).primaryColor, - title: options.translations.imagePickerTitle, - titleStyle: Theme.of(context).textTheme.titleMedium, - iconSize: 60.0, - makePhotoText: options.translations.takePicture, - selectImageText: options.translations.uploadFile, - selectImageIcon: Icon( - color: Theme.of(context).primaryColor, - Icons.insert_drive_file_rounded, - size: 60, - ), - closeButtonBuilder: (ontap) => TextButton( - onPressed: () => Navigator.of(context).pop(), - child: Text( - options.translations.cancelImagePickerBtn, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 18, - decoration: TextDecoration.underline, - ), - ), - ), - ), - ), - ), - ).then( - (image) async { - if (image == null) return; - if (!context.mounted) return; - var messenger = ScaffoldMessenger.of(context) - ..showSnackBar( - _getImageLoadingSnackbar(options.translations), - ) - ..activate(); - await onUploadImage(image); - Future.delayed(const Duration(seconds: 1), () { - messenger.hideCurrentSnackBar(); - }); - }, - ); - -SnackBar _getImageLoadingSnackbar(ChatTranslations translations) => SnackBar( - duration: const Duration(minutes: 1), - content: Row( - children: [ - const SizedBox( - width: 25, - height: 25, - child: CircularProgressIndicator(color: Colors.grey), - ), - Padding( - padding: const EdgeInsets.only(left: 16.0), - child: Text(translations.imageUploading), - ), - ], - ), - );