From d2baffb0fe347b66de16aeabc01fc70822ca27bd Mon Sep 17 00:00:00 2001 From: Omar Mouki Date: Sat, 24 Dec 2022 17:36:18 +0300 Subject: [PATCH 1/6] change input field --- example/ios/Podfile.lock | 6 - example/pubspec.lock | 42 ------- lib/chat_package.dart | 118 ++++-------------- .../chat_input_field/chat_input_field.dart | 3 - .../attachment_bottom_sheet.dart | 86 +++++++++++++ .../new_chat_input_field.dart | 93 ++++++++++++++ .../new_chat_input_field/send_button.dart | 45 +++++++ .../new_chat_input_field/text_field.dart | 67 ++++++++++ pubspec.lock | 42 ------- pubspec.yaml | 2 +- 10 files changed, 313 insertions(+), 191 deletions(-) create mode 100644 lib/components/new_chat_input_field/attachment_bottom_sheet.dart create mode 100644 lib/components/new_chat_input_field/new_chat_input_field.dart create mode 100644 lib/components/new_chat_input_field/send_button.dart create mode 100644 lib/components/new_chat_input_field/text_field.dart diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 5214f13..6af34f6 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2,8 +2,6 @@ PODS: - audio_session (0.0.1): - Flutter - Flutter (1.0.0) - - flutter_keyboard_visibility (0.0.1): - - Flutter - image_picker_ios (0.0.1): - Flutter - just_audio (0.0.1): @@ -18,7 +16,6 @@ PODS: DEPENDENCIES: - audio_session (from `.symlinks/plugins/audio_session/ios`) - Flutter (from `Flutter`) - - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - just_audio (from `.symlinks/plugins/just_audio/ios`) - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) @@ -30,8 +27,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/audio_session/ios" Flutter: :path: Flutter - flutter_keyboard_visibility: - :path: ".symlinks/plugins/flutter_keyboard_visibility/ios" image_picker_ios: :path: ".symlinks/plugins/image_picker_ios/ios" just_audio: @@ -46,7 +41,6 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: audio_session: 4f3e461722055d21515cf3261b64c973c062f345 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069 image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 diff --git a/example/pubspec.lock b/example/pubspec.lock index 7e9123c..360ed6d 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,48 +97,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_keyboard_visibility: - dependency: transitive - description: - name: flutter_keyboard_visibility - url: "https://pub.dartlang.org" - source: hosted - version: "5.4.0" - flutter_keyboard_visibility_linux: - dependency: transitive - description: - name: flutter_keyboard_visibility_linux - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - flutter_keyboard_visibility_macos: - dependency: transitive - description: - name: flutter_keyboard_visibility_macos - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - flutter_keyboard_visibility_platform_interface: - dependency: transitive - description: - name: flutter_keyboard_visibility_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - flutter_keyboard_visibility_web: - dependency: transitive - description: - name: flutter_keyboard_visibility_web - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - flutter_keyboard_visibility_windows: - dependency: transitive - description: - name: flutter_keyboard_visibility_windows - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" flutter_plugin_android_lifecycle: dependency: transitive description: diff --git a/lib/chat_package.dart b/lib/chat_package.dart index 2614cc2..6e1aa07 100644 --- a/lib/chat_package.dart +++ b/lib/chat_package.dart @@ -3,13 +3,14 @@ library chat_package; import 'dart:developer'; import 'package:chat_package/components/message/message_widget.dart'; +import 'package:chat_package/components/new_chat_input_field/new_chat_input_field.dart'; import 'package:chat_package/models/chat_message.dart'; import 'package:chat_package/models/media/chat_media.dart'; import 'package:chat_package/models/media/media_type.dart'; import 'package:chat_package/utils/constants.dart'; import 'package:chat_package/components/chat_input_field/chat_input_field.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; +// import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:image_picker/image_picker.dart'; class ChatScreen extends StatefulWidget { @@ -138,105 +139,28 @@ class _ChatScreenState extends State { @override Widget build(BuildContext context) { - return Column( + return Stack( children: [ - Expanded( - child: ListView.builder( - padding: const EdgeInsets.symmetric(horizontal: kDefaultPadding), - controller: widget.scrollController ?? _controller, - itemCount: widget.messages.length, - itemBuilder: (context, index) => MessageWidget( - message: widget.messages[index], - activeAudioSliderColor: - widget.activeAudioSliderColor ?? kSecondaryColor, - inActiveAudioSliderColor: - widget.inActiveAudioSliderColor ?? kLightColor, - senderColor: widget.senderColor ?? kPrimaryColor, - messageContainerTextStyle: widget.messageContainerTextStyle, - sendDateTextStyle: widget.sendDateTextStyle, - ), + ListView.builder( + padding: const EdgeInsets.symmetric(horizontal: kDefaultPadding), + controller: widget.scrollController ?? _controller, + itemCount: widget.messages.length, + itemBuilder: (context, index) => MessageWidget( + message: widget.messages[index], + activeAudioSliderColor: + widget.activeAudioSliderColor ?? kSecondaryColor, + inActiveAudioSliderColor: + widget.inActiveAudioSliderColor ?? kLightColor, + senderColor: widget.senderColor ?? kPrimaryColor, + messageContainerTextStyle: widget.messageContainerTextStyle, + sendDateTextStyle: widget.sendDateTextStyle, ), ), - KeyboardVisibilityProvider( - child: ChatInputField( - imageAttachmentCancelText: widget.imageAttachmentCancelText, - imageAttachmentFromCameraText: widget.imageAttachmentFromCameraText, - imageAttachmentFromGalleryText: - widget.imageAttachmentFromGalleryText, - chatInputFieldColor: widget.chatInputFieldColor, - recordingNoteHintText: widget.recordingNoteHintText, - sendMessageHintText: widget.sendMessageHintText, - disableInput: widget.disableInput, - chatInputFieldDecoration: widget.chatInputFieldDecoration, - chatInputFieldPadding: widget.chatInputFieldPadding, - imageAttachmentTextStyle: widget.imageAttachmentTextStyle, - imageAttachmentFromGalleryIcon: - widget.imageAttachmentFromGalleryIcon, - imageAttachmentFromCameraIcon: widget.imageAttachmentFromCameraIcon, - imageAttachmentCancelIcon: widget.imageAttachmentCancelIcon, - attachmentClick: widget.attachmentClick, - handleRecord: widget.handleRecord ?? - (source, canceled) { - if (!canceled && source != null) { - setState(() { - widget.messages.add( - ChatMessage( - isSender: true, - chatMedia: ChatMedia( - url: source, - mediaType: MediaType.audioMediaType(), - ), - ), - ); - widget.scrollController?.jumpTo( - widget.scrollController!.position.maxScrollExtent + - 90); - }); - } - }, - handleImageSelect: widget.handleImageSelect ?? - (file) async { - // final bytes = await file.readAsBytes(); - // final image = await decodeImageFromList(bytes); - // final name = file.path.split('/').last; - setState(() { - widget.messages.add( - ChatMessage( - isSender: true, - chatMedia: ChatMedia( - url: file.path, - mediaType: MediaType.imageMediaType(), - ), - ), - ); - }); - - setState(() { - widget.scrollController?.jumpTo( - widget.scrollController!.position.maxScrollExtent + - 300); - }); - }, - onSlideToCancelRecord: widget.onSlideToCancelRecord ?? - () { - log('slide to cancel'); - }, - onTextSubmit: (text) { - if (widget.onSubmit != null) { - widget.onSubmit!(text); - } else { - if (text != null) { - setState(() { - widget.messages - .add(ChatMessage(isSender: true, text: text)); - - widget.scrollController?.jumpTo( - widget.scrollController!.position.maxScrollExtent + 50); - }); - } - } - }, - ), + Positioned( + bottom: 20, + left: 5, + right: 5, + child: NewChatInputField(), ), ], ); diff --git a/lib/components/chat_input_field/chat_input_field.dart b/lib/components/chat_input_field/chat_input_field.dart index e5758e3..c4a16f0 100644 --- a/lib/components/chat_input_field/chat_input_field.dart +++ b/lib/components/chat_input_field/chat_input_field.dart @@ -3,7 +3,6 @@ import 'package:chat_package/components/chat_input_field/widgets/chat_animated_b import 'package:chat_package/components/chat_input_field/widgets/chat_attachment_bottom_sheet.dart'; import 'package:chat_package/components/chat_input_field/widgets/chat_input_field_container_widget.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:image_picker/image_picker.dart'; import 'package:provider/provider.dart'; import 'widgets/chat_drag_trail.dart'; @@ -121,8 +120,6 @@ class ChatInputField extends StatelessWidget { ), child: Consumer( builder: (context, provider, child) { - provider.isText = - KeyboardVisibilityProvider.isKeyboardVisible(context); return Padding( padding: chatInputFieldPadding ?? EdgeInsets.only(bottom: 3), child: IgnorePointer( diff --git a/lib/components/new_chat_input_field/attachment_bottom_sheet.dart b/lib/components/new_chat_input_field/attachment_bottom_sheet.dart new file mode 100644 index 0000000..c7fec5c --- /dev/null +++ b/lib/components/new_chat_input_field/attachment_bottom_sheet.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; + +class AttachmentBottomSheet extends StatelessWidget { + const AttachmentBottomSheet({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + height: 278, + width: MediaQuery.of(context).size.width, + child: Card( + margin: const EdgeInsets.all(18.0), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 20), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + iconCreation( + Icons.insert_drive_file, Colors.indigo, "Document"), + SizedBox( + width: 40, + ), + iconCreation(Icons.camera_alt, Colors.pink, "Camera"), + SizedBox( + width: 40, + ), + iconCreation(Icons.insert_photo, Colors.purple, "Gallery"), + ], + ), + SizedBox( + height: 30, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + iconCreation(Icons.headset, Colors.orange, "Audio"), + SizedBox( + width: 40, + ), + iconCreation(Icons.location_pin, Colors.teal, "Location"), + SizedBox( + width: 40, + ), + iconCreation(Icons.person, Colors.blue, "Contact"), + ], + ), + ], + ), + ), + ), + ); + } + + Widget iconCreation(IconData icons, Color color, String text) { + return InkWell( + onTap: () {}, + child: Column( + children: [ + CircleAvatar( + radius: 30, + backgroundColor: color, + child: Icon( + icons, + // semanticLabel: "Help", + size: 29, + color: Colors.white, + ), + ), + SizedBox( + height: 5, + ), + Text( + text, + style: TextStyle( + fontSize: 12, + // fontWeight: FontWeight.w100, + ), + ) + ], + ), + ); + } +} diff --git a/lib/components/new_chat_input_field/new_chat_input_field.dart b/lib/components/new_chat_input_field/new_chat_input_field.dart new file mode 100644 index 0000000..347fcc8 --- /dev/null +++ b/lib/components/new_chat_input_field/new_chat_input_field.dart @@ -0,0 +1,93 @@ +import 'dart:developer'; + +import 'package:chat_package/components/new_chat_input_field/send_button.dart'; +import 'package:flutter/material.dart'; +import './text_field.dart'; + +import 'attachment_bottom_sheet.dart'; + +class NewChatInputField extends StatefulWidget { + NewChatInputField({ + Key? key, + }) : super(key: key); + + @override + _IndividualPageState createState() => _IndividualPageState(); +} + +class _IndividualPageState extends State { + FocusNode focusNode = FocusNode(); + bool sendButton = false; + + TextEditingController _controller = TextEditingController(); + // ScrollController _scrollController = ScrollController(); + double scale = 0; + + @override + Widget build(BuildContext context) { + double width = MediaQuery.of(context).size.width * (1 - scale); + return AnimatedContainer( + duration: Duration(milliseconds: 100), + width: width, + child: WillPopScope( + child: Row( + children: [ + CustomTextField( + width: width * (0.85 - scale), + focusNode: focusNode, + controller: _controller, + onChanged: (value) { + if (value.length > 0) { + setState(() { + sendButton = true; + }); + } else { + setState(() { + sendButton = false; + }); + } + }, + onAttachmentClicked: () { + showModalBottomSheet( + backgroundColor: Colors.transparent, + context: context, + builder: (builder) => AttachmentBottomSheet()); + }, + ), + SendButton( + onDragChange: (p0) { + setState(() { + double x = -p0 / 200; + scale = x; + }); + }, + onPressed: () { + if (sendButton) { + // _scrollController.animateTo( + // _scrollController.position.maxScrollExtent, + // duration: Duration(milliseconds: 300), + // curve: Curves.easeOut); + + _controller.clear(); + setState(() { + sendButton = false; + }); + } + }, + onLongPress: () { + if (!sendButton) { + log('start recording'); + } + }, + sendButton: sendButton, + ), + ], + ), + onWillPop: () { + Navigator.pop(context); + return Future.value(false); + }, + ), + ); + } +} diff --git a/lib/components/new_chat_input_field/send_button.dart b/lib/components/new_chat_input_field/send_button.dart new file mode 100644 index 0000000..a57982a --- /dev/null +++ b/lib/components/new_chat_input_field/send_button.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; + +class SendButton extends StatelessWidget { + final bool sendButton; + final Function() onPressed; + final Function() onLongPress; + final Function(double) onDragChange; + const SendButton({ + Key? key, + required this.sendButton, + required this.onPressed, + required this.onLongPress, + required this.onDragChange, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onPressed, + onLongPressMoveUpdate: (details) { + if (!sendButton && + details.localPosition.dx < 0 && + details.localPosition.dx >= -20) { + onDragChange(details.localPosition.dx); + } + }, + onLongPressEnd: (details) { + onDragChange(0); + }, + onLongPress: () { + if (!sendButton) { + onLongPress(); + } + }, + child: CircleAvatar( + radius: 25, + backgroundColor: Color(0xFF128C7E), + child: Icon( + sendButton ? Icons.send : Icons.mic, + color: Colors.white, + ), + ), + ); + } +} diff --git a/lib/components/new_chat_input_field/text_field.dart b/lib/components/new_chat_input_field/text_field.dart new file mode 100644 index 0000000..7096e5f --- /dev/null +++ b/lib/components/new_chat_input_field/text_field.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; + +class CustomTextField extends StatelessWidget { + final TextEditingController controller; + final FocusNode focusNode; + final Function(String)? onChanged; + final Function()? onAttachmentClicked; + final double width; + const CustomTextField({ + super.key, + required this.controller, + required this.focusNode, + required this.onChanged, + required this.onAttachmentClicked, + required this.width, + }); + + @override + Widget build(BuildContext context) { + return AnimatedContainer( + duration: Duration(milliseconds: 100), + width: width, + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(25), + ), + child: TextFormField( + controller: controller, + focusNode: focusNode, + textAlignVertical: TextAlignVertical.center, + keyboardType: TextInputType.multiline, + maxLines: 5, + minLines: 1, + onChanged: onChanged, + decoration: InputDecoration( + border: InputBorder.none, + hintText: "Type a message", + hintStyle: TextStyle(color: Colors.grey), + prefixIcon: Icon( + Icons.keyboard, + ), + suffixIcon: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(Icons.attach_file), + onPressed: onAttachmentClicked, + ), + IconButton( + icon: Icon(Icons.camera_alt), + onPressed: () { + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (builder) => + // CameraApp())); + }, + ), + ], + ), + contentPadding: EdgeInsets.all(5), + ), + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index ac07d37..50e6203 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -202,48 +202,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_keyboard_visibility: - dependency: "direct main" - description: - name: flutter_keyboard_visibility - url: "https://pub.dartlang.org" - source: hosted - version: "5.4.0" - flutter_keyboard_visibility_linux: - dependency: transitive - description: - name: flutter_keyboard_visibility_linux - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - flutter_keyboard_visibility_macos: - dependency: transitive - description: - name: flutter_keyboard_visibility_macos - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - flutter_keyboard_visibility_platform_interface: - dependency: transitive - description: - name: flutter_keyboard_visibility_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - flutter_keyboard_visibility_web: - dependency: transitive - description: - name: flutter_keyboard_visibility_web - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - flutter_keyboard_visibility_windows: - dependency: transitive - description: - name: flutter_keyboard_visibility_windows - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" flutter_lints: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index d91ddce..a898e3f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_keyboard_visibility: ^5.4.0 + # flutter_keyboard_visibility: ^5.4.0 permission_handler: ^10.2.0 image_picker: ^0.8.6 just_audio: ^0.9.30 From bea5a643e747b8e9fd5bd9211aa55646502630ad Mon Sep 17 00:00:00 2001 From: Omar Mouki Date: Sat, 24 Dec 2022 17:56:47 +0300 Subject: [PATCH 2/6] add provider for the text field --- .../chat_input_field_provider.dart | 48 +++++++ .../new_chat_input_field.dart | 120 +++++++----------- .../attachment_bottom_sheet.dart | 0 .../{ => widgets}/send_button.dart | 10 +- .../{ => widgets}/text_field.dart | 0 5 files changed, 99 insertions(+), 79 deletions(-) create mode 100644 lib/components/new_chat_input_field/chat_input_field_provider.dart rename lib/components/new_chat_input_field/{ => widgets}/attachment_bottom_sheet.dart (100%) rename lib/components/new_chat_input_field/{ => widgets}/send_button.dart (85%) rename lib/components/new_chat_input_field/{ => widgets}/text_field.dart (100%) diff --git a/lib/components/new_chat_input_field/chat_input_field_provider.dart b/lib/components/new_chat_input_field/chat_input_field_provider.dart new file mode 100644 index 0000000..fe2f9c8 --- /dev/null +++ b/lib/components/new_chat_input_field/chat_input_field_provider.dart @@ -0,0 +1,48 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; + +class NewChatInputFieldProvider extends ChangeNotifier { + final TextEditingController textFieldController; + + NewChatInputFieldProvider({required this.textFieldController}); + + double scale = 0; + bool isText = false; + + void onTextFieldValueChanged(String value) { + if (value.length > 0) { + isText = true; + notifyListeners(); + } else { + isText = false; + notifyListeners(); + } + } + + void onSendButtonClicked() { + if (isText) { + // _scrollController.animateTo( + // _scrollController.position.maxScrollExtent, + // duration: Duration(milliseconds: 300), + // curve: Curves.easeOut); + + textFieldController.clear(); + + isText = false; + notifyListeners(); + } + } + + void onLongPressDragChange(double value) { + double x = -value / 200; + scale = x; + notifyListeners(); + } + + void onLongPress() { + if (!isText) { + log('start recording'); + } + } +} diff --git a/lib/components/new_chat_input_field/new_chat_input_field.dart b/lib/components/new_chat_input_field/new_chat_input_field.dart index 347fcc8..cc5a2e3 100644 --- a/lib/components/new_chat_input_field/new_chat_input_field.dart +++ b/lib/components/new_chat_input_field/new_chat_input_field.dart @@ -1,91 +1,63 @@ -import 'dart:developer'; - -import 'package:chat_package/components/new_chat_input_field/send_button.dart'; +import 'package:chat_package/components/new_chat_input_field/widgets/send_button.dart'; import 'package:flutter/material.dart'; -import './text_field.dart'; +import 'package:provider/provider.dart'; +import 'chat_input_field_provider.dart'; +import 'widgets/text_field.dart'; -import 'attachment_bottom_sheet.dart'; +import 'widgets/attachment_bottom_sheet.dart'; -class NewChatInputField extends StatefulWidget { +class NewChatInputField extends StatelessWidget { NewChatInputField({ Key? key, }) : super(key: key); - @override - _IndividualPageState createState() => _IndividualPageState(); -} - -class _IndividualPageState extends State { - FocusNode focusNode = FocusNode(); - bool sendButton = false; + final FocusNode focusNode = FocusNode(); - TextEditingController _controller = TextEditingController(); + final TextEditingController textFieldController = TextEditingController(); // ScrollController _scrollController = ScrollController(); - double scale = 0; @override Widget build(BuildContext context) { - double width = MediaQuery.of(context).size.width * (1 - scale); - return AnimatedContainer( - duration: Duration(milliseconds: 100), - width: width, - child: WillPopScope( - child: Row( - children: [ - CustomTextField( - width: width * (0.85 - scale), - focusNode: focusNode, - controller: _controller, - onChanged: (value) { - if (value.length > 0) { - setState(() { - sendButton = true; - }); - } else { - setState(() { - sendButton = false; - }); - } - }, - onAttachmentClicked: () { - showModalBottomSheet( - backgroundColor: Colors.transparent, - context: context, - builder: (builder) => AttachmentBottomSheet()); - }, - ), - SendButton( - onDragChange: (p0) { - setState(() { - double x = -p0 / 200; - scale = x; - }); - }, - onPressed: () { - if (sendButton) { - // _scrollController.animateTo( - // _scrollController.position.maxScrollExtent, - // duration: Duration(milliseconds: 300), - // curve: Curves.easeOut); - - _controller.clear(); - setState(() { - sendButton = false; - }); - } - }, - onLongPress: () { - if (!sendButton) { - log('start recording'); - } + return ChangeNotifierProvider( + create: (context) => NewChatInputFieldProvider( + textFieldController: textFieldController, + ), + child: Consumer( + builder: (context, provider, child) { + double width = + MediaQuery.of(context).size.width * (1 - provider.scale); + return AnimatedContainer( + duration: Duration(milliseconds: 100), + width: width, + child: WillPopScope( + child: Row( + children: [ + CustomTextField( + width: width * (0.85 - provider.scale), + focusNode: focusNode, + controller: textFieldController, + onChanged: provider.onTextFieldValueChanged, + onAttachmentClicked: () { + showModalBottomSheet( + backgroundColor: Colors.transparent, + context: context, + builder: (builder) => AttachmentBottomSheet()); + }, + ), + SendButton( + onDragChange: provider.onLongPressDragChange, + onPressed: provider.onSendButtonClicked, + onLongPress: provider.onLongPress, + isText: provider.isText, + ), + ], + ), + onWillPop: () { + Navigator.pop(context); + return Future.value(false); }, - sendButton: sendButton, ), - ], - ), - onWillPop: () { - Navigator.pop(context); - return Future.value(false); + ); }, ), ); diff --git a/lib/components/new_chat_input_field/attachment_bottom_sheet.dart b/lib/components/new_chat_input_field/widgets/attachment_bottom_sheet.dart similarity index 100% rename from lib/components/new_chat_input_field/attachment_bottom_sheet.dart rename to lib/components/new_chat_input_field/widgets/attachment_bottom_sheet.dart diff --git a/lib/components/new_chat_input_field/send_button.dart b/lib/components/new_chat_input_field/widgets/send_button.dart similarity index 85% rename from lib/components/new_chat_input_field/send_button.dart rename to lib/components/new_chat_input_field/widgets/send_button.dart index a57982a..8dfa31d 100644 --- a/lib/components/new_chat_input_field/send_button.dart +++ b/lib/components/new_chat_input_field/widgets/send_button.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; class SendButton extends StatelessWidget { - final bool sendButton; + final bool isText; final Function() onPressed; final Function() onLongPress; final Function(double) onDragChange; const SendButton({ Key? key, - required this.sendButton, + required this.isText, required this.onPressed, required this.onLongPress, required this.onDragChange, @@ -18,7 +18,7 @@ class SendButton extends StatelessWidget { return GestureDetector( onTap: onPressed, onLongPressMoveUpdate: (details) { - if (!sendButton && + if (!isText && details.localPosition.dx < 0 && details.localPosition.dx >= -20) { onDragChange(details.localPosition.dx); @@ -28,7 +28,7 @@ class SendButton extends StatelessWidget { onDragChange(0); }, onLongPress: () { - if (!sendButton) { + if (!isText) { onLongPress(); } }, @@ -36,7 +36,7 @@ class SendButton extends StatelessWidget { radius: 25, backgroundColor: Color(0xFF128C7E), child: Icon( - sendButton ? Icons.send : Icons.mic, + isText ? Icons.send : Icons.mic, color: Colors.white, ), ), diff --git a/lib/components/new_chat_input_field/text_field.dart b/lib/components/new_chat_input_field/widgets/text_field.dart similarity index 100% rename from lib/components/new_chat_input_field/text_field.dart rename to lib/components/new_chat_input_field/widgets/text_field.dart From 238d299d3770f1617c460886699d31fa3f1d38ba Mon Sep 17 00:00:00 2001 From: Omar Mouki Date: Sat, 24 Dec 2022 18:17:52 +0300 Subject: [PATCH 3/6] add parameters for the new send button --- lib/chat_package.dart | 27 ++++++++++++---- .../new_chat_input_field.dart | 16 ++++++++++ .../widgets/send_button.dart | 31 +++++++++++++++---- 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/lib/chat_package.dart b/lib/chat_package.dart index 6e1aa07..356a176 100644 --- a/lib/chat_package.dart +++ b/lib/chat_package.dart @@ -1,14 +1,9 @@ library chat_package; -import 'dart:developer'; - import 'package:chat_package/components/message/message_widget.dart'; import 'package:chat_package/components/new_chat_input_field/new_chat_input_field.dart'; import 'package:chat_package/models/chat_message.dart'; -import 'package:chat_package/models/media/chat_media.dart'; -import 'package:chat_package/models/media/media_type.dart'; import 'package:chat_package/utils/constants.dart'; -import 'package:chat_package/components/chat_input_field/chat_input_field.dart'; import 'package:flutter/material.dart'; // import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:image_picker/image_picker.dart'; @@ -100,6 +95,13 @@ class ChatScreen extends StatefulWidget { /// this is an optional parameter to override the default attachment bottom sheet final Function(BuildContext context)? attachmentClick; + //TODO check for dublickates + final Color? sendButtonColor; + final bool? disableRecording; + final IconData? sendButtonTextIcon; + final IconData? sendButtonRecordIcon; + final Color? sendButtonIconColor; + ChatScreen({ Key? key, this.senderColor, @@ -128,6 +130,13 @@ class ChatScreen extends StatefulWidget { this.messageContainerTextStyle, this.sendDateTextStyle, this.attachmentClick, + + /// + this.sendButtonColor, + this.disableRecording, + this.sendButtonRecordIcon, + this.sendButtonTextIcon, + this.sendButtonIconColor, }) : super(key: key); @override @@ -160,7 +169,13 @@ class _ChatScreenState extends State { bottom: 20, left: 5, right: 5, - child: NewChatInputField(), + child: NewChatInputField( + sendButtonColor: widget.sendButtonColor, + disableRecording: widget.disableRecording, + sendButtonRecordIcon: widget.sendButtonRecordIcon, + sendButtonTextIcon: widget.sendButtonTextIcon, + sendButtonIconColor: widget.sendButtonIconColor, + ), ), ], ); diff --git a/lib/components/new_chat_input_field/new_chat_input_field.dart b/lib/components/new_chat_input_field/new_chat_input_field.dart index cc5a2e3..eca888b 100644 --- a/lib/components/new_chat_input_field/new_chat_input_field.dart +++ b/lib/components/new_chat_input_field/new_chat_input_field.dart @@ -7,8 +7,19 @@ import 'widgets/text_field.dart'; import 'widgets/attachment_bottom_sheet.dart'; class NewChatInputField extends StatelessWidget { + final Color? sendButtonColor; + final bool? disableRecording; + final IconData? sendButtonTextIcon; + final IconData? sendButtonRecordIcon; + final Color? sendButtonIconColor; + NewChatInputField({ Key? key, + this.sendButtonColor, + this.disableRecording, + this.sendButtonRecordIcon, + this.sendButtonTextIcon, + this.sendButtonIconColor, }) : super(key: key); final FocusNode focusNode = FocusNode(); @@ -45,10 +56,15 @@ class NewChatInputField extends StatelessWidget { }, ), SendButton( + sendButtonColor: sendButtonColor, onDragChange: provider.onLongPressDragChange, onPressed: provider.onSendButtonClicked, onLongPress: provider.onLongPress, isText: provider.isText, + disableRecording: disableRecording ?? false, + sendButtonRecordIcon: sendButtonRecordIcon, + sendButtonTextIcon: sendButtonTextIcon, + sendButtonIconColor: sendButtonIconColor, ), ], ), diff --git a/lib/components/new_chat_input_field/widgets/send_button.dart b/lib/components/new_chat_input_field/widgets/send_button.dart index 8dfa31d..9290ca6 100644 --- a/lib/components/new_chat_input_field/widgets/send_button.dart +++ b/lib/components/new_chat_input_field/widgets/send_button.dart @@ -1,6 +1,13 @@ +import 'package:chat_package/utils/constants.dart'; import 'package:flutter/material.dart'; class SendButton extends StatelessWidget { + final Color? sendButtonColor; + final bool disableRecording; + final IconData? sendButtonTextIcon; + final IconData? sendButtonRecordIcon; + final Color? sendButtonIconColor; + final bool isText; final Function() onPressed; final Function() onLongPress; @@ -11,6 +18,11 @@ class SendButton extends StatelessWidget { required this.onPressed, required this.onLongPress, required this.onDragChange, + this.sendButtonColor = kPrimaryColor, + this.disableRecording = false, + this.sendButtonRecordIcon, + this.sendButtonTextIcon, + this.sendButtonIconColor, }) : super(key: key); @override @@ -20,24 +32,31 @@ class SendButton extends StatelessWidget { onLongPressMoveUpdate: (details) { if (!isText && details.localPosition.dx < 0 && - details.localPosition.dx >= -20) { + details.localPosition.dx >= -20 && + !disableRecording) { onDragChange(details.localPosition.dx); } }, onLongPressEnd: (details) { - onDragChange(0); + if (!disableRecording) { + onDragChange(0); + } }, onLongPress: () { - if (!isText) { + if (!isText && !disableRecording) { onLongPress(); } }, child: CircleAvatar( radius: 25, - backgroundColor: Color(0xFF128C7E), + backgroundColor: sendButtonColor, child: Icon( - isText ? Icons.send : Icons.mic, - color: Colors.white, + isText + ? (sendButtonTextIcon ?? Icons.send) + : (!disableRecording + ? (sendButtonRecordIcon ?? Icons.mic) + : (sendButtonTextIcon ?? Icons.send)), + color: sendButtonIconColor ?? Colors.white, ), ), ); From 1861322b944fc88300f936adf27133a0879e9172 Mon Sep 17 00:00:00 2001 From: Omar Mouki Date: Sat, 24 Dec 2022 18:33:57 +0300 Subject: [PATCH 4/6] add parameters for text field --- lib/chat_package.dart | 24 +++++++++ .../new_chat_input_field.dart | 21 ++++++++ .../widgets/text_field.dart | 50 +++++++++++++------ 3 files changed, 79 insertions(+), 16 deletions(-) diff --git a/lib/chat_package.dart b/lib/chat_package.dart index 356a176..0fa2a21 100644 --- a/lib/chat_package.dart +++ b/lib/chat_package.dart @@ -96,11 +96,21 @@ class ChatScreen extends StatefulWidget { final Function(BuildContext context)? attachmentClick; //TODO check for dublickates + //send button final Color? sendButtonColor; final bool? disableRecording; final IconData? sendButtonTextIcon; final IconData? sendButtonRecordIcon; final Color? sendButtonIconColor; + //text field + //text field + final String? textFieldHintText; + final TextStyle? textFieldHintTextStyle; + final bool? disableCamera; + final bool? disableAttachment; + + final IconData? cameraIcon; + final IconData? attachmentIcon; ChatScreen({ Key? key, @@ -137,6 +147,14 @@ class ChatScreen extends StatefulWidget { this.sendButtonRecordIcon, this.sendButtonTextIcon, this.sendButtonIconColor, + + ///text field + this.textFieldHintTextStyle, + this.textFieldHintText, + this.disableCamera, + this.disableAttachment, + this.cameraIcon, + this.attachmentIcon, }) : super(key: key); @override @@ -175,6 +193,12 @@ class _ChatScreenState extends State { sendButtonRecordIcon: widget.sendButtonRecordIcon, sendButtonTextIcon: widget.sendButtonTextIcon, sendButtonIconColor: widget.sendButtonIconColor, + textFieldHintText: widget.textFieldHintText, + textFieldHintTextStyle: widget.textFieldHintTextStyle, + disableCamera: widget.disableCamera, + disableAttachment: widget.disableAttachment, + cameraIcon: widget.cameraIcon, + attachmentIcon: widget.attachmentIcon, ), ), ], diff --git a/lib/components/new_chat_input_field/new_chat_input_field.dart b/lib/components/new_chat_input_field/new_chat_input_field.dart index eca888b..c6d20fa 100644 --- a/lib/components/new_chat_input_field/new_chat_input_field.dart +++ b/lib/components/new_chat_input_field/new_chat_input_field.dart @@ -7,11 +7,20 @@ import 'widgets/text_field.dart'; import 'widgets/attachment_bottom_sheet.dart'; class NewChatInputField extends StatelessWidget { + //send button final Color? sendButtonColor; final bool? disableRecording; final IconData? sendButtonTextIcon; final IconData? sendButtonRecordIcon; final Color? sendButtonIconColor; + //text field + final String? textFieldHintText; + final TextStyle? textFieldHintTextStyle; + final bool? disableCamera; + final bool? disableAttachment; + + final IconData? cameraIcon; + final IconData? attachmentIcon; NewChatInputField({ Key? key, @@ -20,6 +29,12 @@ class NewChatInputField extends StatelessWidget { this.sendButtonRecordIcon, this.sendButtonTextIcon, this.sendButtonIconColor, + this.textFieldHintText, + this.textFieldHintTextStyle, + this.disableCamera, + this.disableAttachment, + this.cameraIcon, + this.attachmentIcon, }) : super(key: key); final FocusNode focusNode = FocusNode(); @@ -54,6 +69,12 @@ class NewChatInputField extends StatelessWidget { context: context, builder: (builder) => AttachmentBottomSheet()); }, + textFieldHintText: textFieldHintText, + textFieldHintTextStyle: textFieldHintTextStyle, + disableCamera: disableCamera, + disableAttachment: disableAttachment, + cameraIcon: cameraIcon, + attachmentIcon: attachmentIcon, ), SendButton( sendButtonColor: sendButtonColor, diff --git a/lib/components/new_chat_input_field/widgets/text_field.dart b/lib/components/new_chat_input_field/widgets/text_field.dart index 7096e5f..9c02433 100644 --- a/lib/components/new_chat_input_field/widgets/text_field.dart +++ b/lib/components/new_chat_input_field/widgets/text_field.dart @@ -1,6 +1,14 @@ import 'package:flutter/material.dart'; class CustomTextField extends StatelessWidget { + final String? textFieldHintText; + final TextStyle? textFieldHintTextStyle; + final bool? disableCamera; + final bool? disableAttachment; + + final IconData? cameraIcon; + final IconData? attachmentIcon; + final TextEditingController controller; final FocusNode focusNode; final Function(String)? onChanged; @@ -13,6 +21,12 @@ class CustomTextField extends StatelessWidget { required this.onChanged, required this.onAttachmentClicked, required this.width, + this.textFieldHintText, + required this.textFieldHintTextStyle, + required this.disableCamera, + required this.disableAttachment, + required this.cameraIcon, + required this.attachmentIcon, }); @override @@ -34,28 +48,32 @@ class CustomTextField extends StatelessWidget { onChanged: onChanged, decoration: InputDecoration( border: InputBorder.none, - hintText: "Type a message", - hintStyle: TextStyle(color: Colors.grey), + hintText: textFieldHintText ?? 'Type a message', + hintStyle: textFieldHintTextStyle ?? TextStyle(color: Colors.grey), prefixIcon: Icon( Icons.keyboard, ), suffixIcon: Row( mainAxisSize: MainAxisSize.min, children: [ - IconButton( - icon: Icon(Icons.attach_file), - onPressed: onAttachmentClicked, - ), - IconButton( - icon: Icon(Icons.camera_alt), - onPressed: () { - // Navigator.push( - // context, - // MaterialPageRoute( - // builder: (builder) => - // CameraApp())); - }, - ), + disableAttachment ?? false + ? Container() + : IconButton( + icon: Icon(attachmentIcon ?? Icons.attach_file), + onPressed: onAttachmentClicked, + ), + disableCamera ?? false + ? Container() + : IconButton( + icon: Icon(cameraIcon ?? Icons.camera_alt), + onPressed: () { + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (builder) => + // CameraApp())); + }, + ), ], ), contentPadding: EdgeInsets.all(5), From 23ea5269f82f411be4bfff819764f8cbeb13c5dd Mon Sep 17 00:00:00 2001 From: Omar Mouki Date: Sat, 24 Dec 2022 20:11:48 +0300 Subject: [PATCH 5/6] add media screen --- example/android/app/build.gradle | 2 +- example/pubspec.lock | 49 ++++++ lib/chat_package.dart | 4 + .../chat_input_field_provider.dart | 16 ++ .../new_chat_input_field.dart | 31 ++++ .../widgets/camera/camera_capture_screen.dart | 162 ++++++++++++++++++ .../widgets/camera/camptured_media_view.dart | 107 ++++++++++++ .../widgets/text_field.dart | 11 +- pubspec.lock | 44 ++++- pubspec.yaml | 1 + 10 files changed, 418 insertions(+), 9 deletions(-) create mode 100644 lib/components/new_chat_input_field/widgets/camera/camera_capture_screen.dart create mode 100644 lib/components/new_chat_input_field/widgets/camera/camptured_media_view.dart diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 81791a3..c981d0f 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -35,7 +35,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.example" - minSdkVersion 19 + minSdkVersion 21 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/example/pubspec.lock b/example/pubspec.lock index 360ed6d..674d39f 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -22,6 +22,41 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + camera: + dependency: transitive + description: + name: camera + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.1" + camera_android: + dependency: transitive + description: + name: camera_android + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.2" + camera_avfoundation: + dependency: transitive + description: + name: camera_avfoundation + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.10" + camera_platform_interface: + dependency: transitive + description: + name: camera_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.2" + camera_web: + dependency: transitive + description: + name: camera_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.1" characters: dependency: transitive description: @@ -366,6 +401,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.0.4" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.1" record: dependency: transitive description: @@ -448,6 +490,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: diff --git a/lib/chat_package.dart b/lib/chat_package.dart index 0fa2a21..6744f1c 100644 --- a/lib/chat_package.dart +++ b/lib/chat_package.dart @@ -112,6 +112,8 @@ class ChatScreen extends StatefulWidget { final IconData? cameraIcon; final IconData? attachmentIcon; + final String? capturedMediaHintText; + ChatScreen({ Key? key, this.senderColor, @@ -155,6 +157,7 @@ class ChatScreen extends StatefulWidget { this.disableAttachment, this.cameraIcon, this.attachmentIcon, + this.capturedMediaHintText, }) : super(key: key); @override @@ -199,6 +202,7 @@ class _ChatScreenState extends State { disableAttachment: widget.disableAttachment, cameraIcon: widget.cameraIcon, attachmentIcon: widget.attachmentIcon, + capturedMediaHintText: widget.capturedMediaHintText, ), ), ], diff --git a/lib/components/new_chat_input_field/chat_input_field_provider.dart b/lib/components/new_chat_input_field/chat_input_field_provider.dart index fe2f9c8..147d58b 100644 --- a/lib/components/new_chat_input_field/chat_input_field_provider.dart +++ b/lib/components/new_chat_input_field/chat_input_field_provider.dart @@ -1,5 +1,6 @@ import 'dart:developer'; +import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; class NewChatInputFieldProvider extends ChangeNotifier { @@ -45,4 +46,19 @@ class NewChatInputFieldProvider extends ChangeNotifier { log('start recording'); } } + + Future> getCameras() async { + final cameras = await availableCameras(); + return cameras; + } + + Future takePhoto(CameraController cameraController) async { + XFile file = await cameraController.takePicture(); + print(file.toString()); + return file; + } + + onSubmitMediaFromCamera(String path, String? caption) { + log(path); + } } diff --git a/lib/components/new_chat_input_field/new_chat_input_field.dart b/lib/components/new_chat_input_field/new_chat_input_field.dart index c6d20fa..02ec58c 100644 --- a/lib/components/new_chat_input_field/new_chat_input_field.dart +++ b/lib/components/new_chat_input_field/new_chat_input_field.dart @@ -1,3 +1,5 @@ +import 'package:chat_package/components/new_chat_input_field/widgets/camera/camera_capture_screen.dart'; +import 'package:chat_package/components/new_chat_input_field/widgets/camera/camptured_media_view.dart'; import 'package:chat_package/components/new_chat_input_field/widgets/send_button.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -22,6 +24,8 @@ class NewChatInputField extends StatelessWidget { final IconData? cameraIcon; final IconData? attachmentIcon; + final String? capturedMediaHintText; + NewChatInputField({ Key? key, this.sendButtonColor, @@ -35,6 +39,7 @@ class NewChatInputField extends StatelessWidget { this.disableAttachment, this.cameraIcon, this.attachmentIcon, + this.capturedMediaHintText, }) : super(key: key); final FocusNode focusNode = FocusNode(); @@ -75,6 +80,32 @@ class NewChatInputField extends StatelessWidget { disableAttachment: disableAttachment, cameraIcon: cameraIcon, attachmentIcon: attachmentIcon, + onCameraIconPressed: () async { + final cameras = await provider.getCameras(); + Navigator.push( + context, + MaterialPageRoute( + builder: (builder) => CameraCaptureScreen( + cameras: cameras, + takeImage: (controller) async { + final file = await provider.takePhoto(controller); + Navigator.push( + context, + MaterialPageRoute( + builder: (builder) => CapturedMediaView( + path: file.path, + capturedMediaHintText: + capturedMediaHintText, + onSubmitMediaFromCamera: + provider.onSubmitMediaFromCamera, + ), + ), + ); + }, + ), + ), + ); + }, ), SendButton( sendButtonColor: sendButtonColor, diff --git a/lib/components/new_chat_input_field/widgets/camera/camera_capture_screen.dart b/lib/components/new_chat_input_field/widgets/camera/camera_capture_screen.dart new file mode 100644 index 0000000..d2f1201 --- /dev/null +++ b/lib/components/new_chat_input_field/widgets/camera/camera_capture_screen.dart @@ -0,0 +1,162 @@ +import 'dart:math'; + +import 'package:camera/camera.dart'; +import 'package:flutter/material.dart'; + +class CameraCaptureScreen extends StatefulWidget { + final List cameras; + final Function(CameraController cameraController) takeImage; + CameraCaptureScreen({ + Key? key, + required this.cameras, + required this.takeImage, + }) : super(key: key); + + @override + _CameraCaptureScreenState createState() => _CameraCaptureScreenState(); +} + +class _CameraCaptureScreenState extends State { + late CameraController _cameraController; + late Future cameraValue; + late bool isRecording = false; + bool flash = false; + bool isFrontCamera = true; + double transform = 0; + + @override + void initState() { + super.initState(); + _cameraController = + CameraController(widget.cameras[0], ResolutionPreset.high); + cameraValue = _cameraController.initialize(); + } + + @override + void dispose() { + super.dispose(); + _cameraController.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + FutureBuilder( + future: cameraValue, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + return Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, + child: CameraPreview(_cameraController)); + } else { + return Center( + child: CircularProgressIndicator(), + ); + } + }), + Positioned( + bottom: 0.0, + child: Container( + color: Colors.red, + padding: EdgeInsets.only(top: 5, bottom: 5), + width: MediaQuery.of(context).size.width, + child: Column( + children: [ + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + icon: Icon( + flash ? Icons.flash_on : Icons.flash_off, + color: Colors.white, + size: 28, + ), + onPressed: () { + setState(() { + flash = !flash; + }); + flash + ? _cameraController + .setFlashMode(FlashMode.torch) + : _cameraController.setFlashMode(FlashMode.off); + }), + GestureDetector( + onLongPress: () async { + await _cameraController.startVideoRecording(); + setState(() { + isRecording = true; + }); + }, + onLongPressUp: () async { + // XFile videopath = + // await _cameraController.stopVideoRecording(); + // setState(() { + // isRecording = false; + // }); + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (builder) => VideoViewPage( + // path: videopath.path, + // ))); + }, + onTap: () { + if (!isRecording) widget.takeImage(_cameraController); + }, + child: isRecording + ? Icon( + Icons.radio_button_on, + color: Colors.red, + size: 80, + ) + : Icon( + Icons.panorama_fish_eye, + color: Colors.white, + size: 70, + ), + ), + IconButton( + icon: Transform.rotate( + angle: transform, + child: Icon( + Icons.flip_camera_ios, + color: Colors.white, + size: 28, + ), + ), + onPressed: () async { + setState(() { + isFrontCamera = !isFrontCamera; + transform = transform + pi; + }); + int cameraPos = isFrontCamera ? 0 : 1; + _cameraController = CameraController( + widget.cameras[cameraPos], + ResolutionPreset.high); + cameraValue = _cameraController.initialize(); + }), + ], + ), + SizedBox( + height: 4, + ), + Text( + "Hold for Video, tap for photo", + style: TextStyle( + color: Colors.white, + ), + textAlign: TextAlign.center, + ) + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/components/new_chat_input_field/widgets/camera/camptured_media_view.dart b/lib/components/new_chat_input_field/widgets/camera/camptured_media_view.dart new file mode 100644 index 0000000..d44e6b1 --- /dev/null +++ b/lib/components/new_chat_input_field/widgets/camera/camptured_media_view.dart @@ -0,0 +1,107 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; + +class CapturedMediaView extends StatelessWidget { + final String path; + final String? capturedMediaHintText; + final Function(String path, String? caption) onSubmitMediaFromCamera; + + CapturedMediaView( + {Key? key, + required this.path, + required this.onSubmitMediaFromCamera, + this.capturedMediaHintText}) + : super(key: key); + String caption = ''; + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + appBar: AppBar( + backgroundColor: Colors.black, + // actions: [ + // IconButton( + // icon: Icon( + // Icons.crop_rotate, + // size: 27, + // ), + // onPressed: () {}), + // IconButton( + // icon: Icon( + // Icons.emoji_emotions_outlined, + // size: 27, + // ), + // onPressed: () {}), + // IconButton( + // icon: Icon( + // Icons.title, + // size: 27, + // ), + // onPressed: () {}), + // IconButton( + // icon: Icon( + // Icons.edit, + // size: 27, + // ), + // onPressed: () {}), + // ], + ), + body: Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, + child: Stack( + children: [ + Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height - 150, + child: Image.file( + File(path), + fit: BoxFit.cover, + ), + ), + Positioned( + bottom: 0, + child: Container( + color: Colors.black38, + width: MediaQuery.of(context).size.width, + padding: EdgeInsets.symmetric(vertical: 5, horizontal: 8), + child: TextFormField( + style: TextStyle( + color: Colors.white, + fontSize: 17, + ), + maxLines: 6, + minLines: 1, + onChanged: (value) { + caption = value; + }, + decoration: InputDecoration( + border: InputBorder.none, + hintText: capturedMediaHintText ?? 'Add Caption....', + hintStyle: TextStyle( + color: Colors.white, + fontSize: 17, + ), + suffixIcon: CircleAvatar( + radius: 27, + backgroundColor: Colors.tealAccent[700], + child: IconButton( + onPressed: () { + onSubmitMediaFromCamera(path, caption); + }, + icon: Icon( + Icons.check, + color: Colors.white, + size: 27, + )), + )), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/components/new_chat_input_field/widgets/text_field.dart b/lib/components/new_chat_input_field/widgets/text_field.dart index 9c02433..b93aa41 100644 --- a/lib/components/new_chat_input_field/widgets/text_field.dart +++ b/lib/components/new_chat_input_field/widgets/text_field.dart @@ -9,6 +9,8 @@ class CustomTextField extends StatelessWidget { final IconData? cameraIcon; final IconData? attachmentIcon; + final Function() onCameraIconPressed; + final TextEditingController controller; final FocusNode focusNode; final Function(String)? onChanged; @@ -27,6 +29,7 @@ class CustomTextField extends StatelessWidget { required this.disableAttachment, required this.cameraIcon, required this.attachmentIcon, + required this.onCameraIconPressed, }); @override @@ -66,13 +69,7 @@ class CustomTextField extends StatelessWidget { ? Container() : IconButton( icon: Icon(cameraIcon ?? Icons.camera_alt), - onPressed: () { - // Navigator.push( - // context, - // MaterialPageRoute( - // builder: (builder) => - // CameraApp())); - }, + onPressed: onCameraIconPressed, ), ], ), diff --git a/pubspec.lock b/pubspec.lock index 50e6203..c7136c8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -99,6 +99,41 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "8.4.2" + camera: + dependency: "direct main" + description: + name: camera + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.1" + camera_android: + dependency: transitive + description: + name: camera_android + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.2" + camera_avfoundation: + dependency: transitive + description: + name: camera_avfoundation + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.10" + camera_platform_interface: + dependency: transitive + description: + name: camera_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.2" + camera_web: + dependency: transitive + description: + name: camera_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.1" characters: dependency: transitive description: @@ -562,6 +597,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.1" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.1" record: dependency: "direct main" description: @@ -758,4 +800,4 @@ packages: version: "3.1.1" sdks: dart: ">=2.18.1 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index a898e3f..aeabefe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,6 +20,7 @@ dependencies: stop_watch_timer: ^2.0.0 provider: ^6.0.4 freezed_annotation: ^2.2.0 + camera: ^0.10.1 dev_dependencies: flutter_test: From 956f062b9886dc5472b84e0eca2ef2ab7099ed82 Mon Sep 17 00:00:00 2001 From: Omar Mouki Date: Sat, 24 Dec 2022 20:33:07 +0300 Subject: [PATCH 6/6] add images from camera --- lib/chat_package.dart | 57 ++++++++++++++----- .../image_message/image_message_widget.dart | 1 + .../chat_input_field_provider.dart | 6 +- .../new_chat_input_field.dart | 23 +++++--- 4 files changed, 63 insertions(+), 24 deletions(-) diff --git a/lib/chat_package.dart b/lib/chat_package.dart index 6744f1c..551fa50 100644 --- a/lib/chat_package.dart +++ b/lib/chat_package.dart @@ -3,6 +3,8 @@ library chat_package; import 'package:chat_package/components/message/message_widget.dart'; import 'package:chat_package/components/new_chat_input_field/new_chat_input_field.dart'; import 'package:chat_package/models/chat_message.dart'; +import 'package:chat_package/models/media/chat_media.dart'; +import 'package:chat_package/models/media/media_type.dart'; import 'package:chat_package/utils/constants.dart'; import 'package:flutter/material.dart'; // import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; @@ -114,6 +116,8 @@ class ChatScreen extends StatefulWidget { final String? capturedMediaHintText; + final Function(String path, String caption)? handleMediaSubmitted; + ChatScreen({ Key? key, this.senderColor, @@ -158,6 +162,7 @@ class ChatScreen extends StatefulWidget { this.cameraIcon, this.attachmentIcon, this.capturedMediaHintText, + this.handleMediaSubmitted, }) : super(key: key); @override @@ -172,7 +177,8 @@ class _ChatScreenState extends State { return Stack( children: [ ListView.builder( - padding: const EdgeInsets.symmetric(horizontal: kDefaultPadding), + padding: const EdgeInsets.only( + left: kDefaultPadding, right: kDefaultPadding, bottom: 100), controller: widget.scrollController ?? _controller, itemCount: widget.messages.length, itemBuilder: (context, index) => MessageWidget( @@ -191,19 +197,42 @@ class _ChatScreenState extends State { left: 5, right: 5, child: NewChatInputField( - sendButtonColor: widget.sendButtonColor, - disableRecording: widget.disableRecording, - sendButtonRecordIcon: widget.sendButtonRecordIcon, - sendButtonTextIcon: widget.sendButtonTextIcon, - sendButtonIconColor: widget.sendButtonIconColor, - textFieldHintText: widget.textFieldHintText, - textFieldHintTextStyle: widget.textFieldHintTextStyle, - disableCamera: widget.disableCamera, - disableAttachment: widget.disableAttachment, - cameraIcon: widget.cameraIcon, - attachmentIcon: widget.attachmentIcon, - capturedMediaHintText: widget.capturedMediaHintText, - ), + sendButtonColor: widget.sendButtonColor, + disableRecording: widget.disableRecording, + sendButtonRecordIcon: widget.sendButtonRecordIcon, + sendButtonTextIcon: widget.sendButtonTextIcon, + sendButtonIconColor: widget.sendButtonIconColor, + textFieldHintText: widget.textFieldHintText, + textFieldHintTextStyle: widget.textFieldHintTextStyle, + disableCamera: widget.disableCamera, + disableAttachment: widget.disableAttachment, + cameraIcon: widget.cameraIcon, + attachmentIcon: widget.attachmentIcon, + capturedMediaHintText: widget.capturedMediaHintText, + handleMediaSubmitted: widget.handleMediaSubmitted ?? + (path, caption) { + setState(() { + widget.messages.add( + ChatMessage( + isSender: true, + text: caption, + chatMedia: ChatMedia( + url: path, + mediaType: MediaType.imageMediaType(), + ), + ), + ); + }); + + setState(() { + (widget.scrollController ?? _controller).animateTo( + (widget.scrollController ?? _controller) + .position + .maxScrollExtent, + duration: Duration(milliseconds: 300), + curve: Curves.easeOut); + }); + }), ), ], ); diff --git a/lib/components/message/image_message/image_message_widget.dart b/lib/components/message/image_message/image_message_widget.dart index f9a5269..e56c196 100644 --- a/lib/components/message/image_message/image_message_widget.dart +++ b/lib/components/message/image_message/image_message_widget.dart @@ -64,6 +64,7 @@ class ImageMessageWidget extends StatelessWidget { ) : Image.file( File(message.chatMedia!.url), + fit: BoxFit.cover, ), ), ), diff --git a/lib/components/new_chat_input_field/chat_input_field_provider.dart b/lib/components/new_chat_input_field/chat_input_field_provider.dart index 147d58b..9c8cb77 100644 --- a/lib/components/new_chat_input_field/chat_input_field_provider.dart +++ b/lib/components/new_chat_input_field/chat_input_field_provider.dart @@ -5,8 +5,10 @@ import 'package:flutter/material.dart'; class NewChatInputFieldProvider extends ChangeNotifier { final TextEditingController textFieldController; + final Function(String path, String caption) handleMediaSubmitted; - NewChatInputFieldProvider({required this.textFieldController}); + NewChatInputFieldProvider( + {required this.textFieldController, required this.handleMediaSubmitted}); double scale = 0; bool isText = false; @@ -59,6 +61,6 @@ class NewChatInputFieldProvider extends ChangeNotifier { } onSubmitMediaFromCamera(String path, String? caption) { - log(path); + handleMediaSubmitted(path, caption ?? ''); } } diff --git a/lib/components/new_chat_input_field/new_chat_input_field.dart b/lib/components/new_chat_input_field/new_chat_input_field.dart index 02ec58c..73a0be1 100644 --- a/lib/components/new_chat_input_field/new_chat_input_field.dart +++ b/lib/components/new_chat_input_field/new_chat_input_field.dart @@ -2,6 +2,7 @@ import 'package:chat_package/components/new_chat_input_field/widgets/camera/came import 'package:chat_package/components/new_chat_input_field/widgets/camera/camptured_media_view.dart'; import 'package:chat_package/components/new_chat_input_field/widgets/send_button.dart'; import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:provider/provider.dart'; import 'chat_input_field_provider.dart'; import 'widgets/text_field.dart'; @@ -26,6 +27,8 @@ class NewChatInputField extends StatelessWidget { final String? capturedMediaHintText; + final Function(String path, String caption) handleMediaSubmitted; + NewChatInputField({ Key? key, this.sendButtonColor, @@ -40,6 +43,7 @@ class NewChatInputField extends StatelessWidget { this.cameraIcon, this.attachmentIcon, this.capturedMediaHintText, + required this.handleMediaSubmitted, }) : super(key: key); final FocusNode focusNode = FocusNode(); @@ -51,8 +55,8 @@ class NewChatInputField extends StatelessWidget { Widget build(BuildContext context) { return ChangeNotifierProvider( create: (context) => NewChatInputFieldProvider( - textFieldController: textFieldController, - ), + textFieldController: textFieldController, + handleMediaSubmitted: handleMediaSubmitted), child: Consumer( builder: (context, provider, child) { double width = @@ -93,12 +97,15 @@ class NewChatInputField extends StatelessWidget { context, MaterialPageRoute( builder: (builder) => CapturedMediaView( - path: file.path, - capturedMediaHintText: - capturedMediaHintText, - onSubmitMediaFromCamera: - provider.onSubmitMediaFromCamera, - ), + path: file.path, + capturedMediaHintText: + capturedMediaHintText, + onSubmitMediaFromCamera: (path, caption) { + provider.onSubmitMediaFromCamera( + path, caption); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }), ), ); },