mirror of
https://github.com/Iconica-Development/flutter_chat.git
synced 2025-05-19 10:53:51 +02:00
refactor: move widgets from ChatDetailScreen to seperate files
This makes the chat_detail_screen.dart easier to read and only containing the logic
This commit is contained in:
parent
d298fbe15b
commit
0adc4f975b
3 changed files with 214 additions and 199 deletions
|
@ -4,7 +4,8 @@ import "dart:typed_data";
|
||||||
import "package:chat_repository_interface/chat_repository_interface.dart";
|
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_chat/src/config/screen_types.dart";
|
import "package:flutter_chat/src/config/screen_types.dart";
|
||||||
import "package:flutter_chat/src/screens/chat_detail/widgets/default_message_builder.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/image_picker.dart";
|
||||||
import "package:flutter_chat/src/util/scope.dart";
|
import "package:flutter_chat/src/util/scope.dart";
|
||||||
import "package:flutter_hooks/flutter_hooks.dart";
|
import "package:flutter_hooks/flutter_hooks.dart";
|
||||||
|
@ -90,14 +91,14 @@ class ChatDetailScreen extends HookWidget {
|
||||||
[onExit],
|
[onExit],
|
||||||
);
|
);
|
||||||
|
|
||||||
var appBar = _AppBar(
|
var appBar = _ChatAppBar(
|
||||||
chatTitle: chatTitle.value,
|
chatTitle: chatTitle.value,
|
||||||
onPressChatTitle: onPressChatTitle,
|
onPressChatTitle: onPressChatTitle,
|
||||||
chatModel: chat,
|
chatModel: chat,
|
||||||
onPressBack: onExit,
|
onPressBack: onExit,
|
||||||
);
|
);
|
||||||
|
|
||||||
var body = _Body(
|
var body = _ChatBody(
|
||||||
chatId: chatId,
|
chatId: chatId,
|
||||||
chat: chat,
|
chat: chat,
|
||||||
chatUsers: allUsers,
|
chatUsers: allUsers,
|
||||||
|
@ -147,8 +148,8 @@ class ChatDetailScreen extends HookWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The app bar widget for the chat detail screen
|
/// The app bar widget for the chat detail screen
|
||||||
class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
class _ChatAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
const _AppBar({
|
const _ChatAppBar({
|
||||||
required this.chatTitle,
|
required this.chatTitle,
|
||||||
required this.chatModel,
|
required this.chatModel,
|
||||||
required this.onPressChatTitle,
|
required this.onPressChatTitle,
|
||||||
|
@ -202,8 +203,8 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
|
|
||||||
/// Body for the chat detail screen
|
/// Body for the chat detail screen
|
||||||
/// Displays messages, a scrollable list, and a bottom input field.
|
/// Displays messages, a scrollable list, and a bottom input field.
|
||||||
class _Body extends HookWidget {
|
class _ChatBody extends HookWidget {
|
||||||
const _Body({
|
const _ChatBody({
|
||||||
required this.chatId,
|
required this.chatId,
|
||||||
required this.chat,
|
required this.chat,
|
||||||
required this.chatUsers,
|
required this.chatUsers,
|
||||||
|
@ -265,12 +266,12 @@ class _Body extends HookWidget {
|
||||||
|
|
||||||
var listViewChildren = messages.isEmpty && !showIndicator.value
|
var listViewChildren = messages.isEmpty && !showIndicator.value
|
||||||
? [
|
? [
|
||||||
_ChatNoMessages(isGroupChat: chat!.isGroupChat),
|
ChatNoMessages(isGroupChat: chat!.isGroupChat),
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
for (var (index, message) in messages.indexed) ...[
|
for (var (index, message) in messages.indexed) ...[
|
||||||
if (chat!.id == message.chatId)
|
if (chat!.id == message.chatId)
|
||||||
_ChatBubble(
|
ChatBubble(
|
||||||
key: ValueKey(message.id),
|
key: ValueKey(message.id),
|
||||||
sender: chatUsers
|
sender: chatUsers
|
||||||
.where(
|
.where(
|
||||||
|
@ -302,7 +303,7 @@ class _Body extends HookWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_ChatBottom(
|
ChatBottomInputSection(
|
||||||
chat: chat!,
|
chat: chat!,
|
||||||
onPressSelectImage: () async => onPressSelectImage(
|
onPressSelectImage: () async => onPressSelectImage(
|
||||||
context,
|
context,
|
||||||
|
@ -320,192 +321,3 @@ class _Body extends HookWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Widget displayed when there are no messages in the chat.
|
|
||||||
class _ChatNoMessages extends HookWidget {
|
|
||||||
const _ChatNoMessages({
|
|
||||||
required this.isGroupChat,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Determines if this chat is a group chat.
|
|
||||||
final bool isGroupChat;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
var chatScope = ChatScope.of(context);
|
|
||||||
var translations = chatScope.options.translations;
|
|
||||||
var theme = Theme.of(context);
|
|
||||||
|
|
||||||
return Center(
|
|
||||||
child: Text(
|
|
||||||
isGroupChat
|
|
||||||
? translations.writeFirstMessageInGroupChat
|
|
||||||
: translations.writeMessageToStartChat,
|
|
||||||
style: theme.textTheme.bodySmall,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bottom input field where the user can type or upload images.
|
|
||||||
class _ChatBottom extends HookWidget {
|
|
||||||
const _ChatBottom({
|
|
||||||
required this.chat,
|
|
||||||
required this.onMessageSubmit,
|
|
||||||
this.onPressSelectImage,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// The chat model.
|
|
||||||
final ChatModel chat;
|
|
||||||
|
|
||||||
/// Callback function invoked when a message is submitted.
|
|
||||||
final Function(String text) onMessageSubmit;
|
|
||||||
|
|
||||||
/// Callback function invoked when the select image button is pressed.
|
|
||||||
final VoidCallback? onPressSelectImage;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
var chatScope = ChatScope.of(context);
|
|
||||||
var options = chatScope.options;
|
|
||||||
var theme = Theme.of(context);
|
|
||||||
|
|
||||||
var textController = useTextEditingController();
|
|
||||||
var isTyping = useState(false);
|
|
||||||
var isSending = useState(false);
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() {
|
|
||||||
void listener() => isTyping.value = textController.text.isNotEmpty;
|
|
||||||
textController.addListener(listener);
|
|
||||||
return () => textController.removeListener(listener);
|
|
||||||
},
|
|
||||||
[textController],
|
|
||||||
);
|
|
||||||
|
|
||||||
Future<void> sendMessage() async {
|
|
||||||
isSending.value = true;
|
|
||||||
var value = textController.text;
|
|
||||||
if (value.isNotEmpty) {
|
|
||||||
await onMessageSubmit(value);
|
|
||||||
textController.clear();
|
|
||||||
}
|
|
||||||
isSending.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> Function()? onClickSendMessage;
|
|
||||||
if (isTyping.value && !isSending.value) {
|
|
||||||
onClickSendMessage = () async => sendMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Image and send buttons
|
|
||||||
var messageSendButtons = Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: onPressSelectImage,
|
|
||||||
icon: Icon(
|
|
||||||
Icons.image_outlined,
|
|
||||||
color: options.iconEnabledColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
disabledColor: options.iconDisabledColor,
|
|
||||||
color: options.iconEnabledColor,
|
|
||||||
onPressed: onClickSendMessage,
|
|
||||||
icon: const Icon(Icons.send_rounded),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
var defaultInputField = TextField(
|
|
||||||
textAlign: TextAlign.start,
|
|
||||||
textAlignVertical: TextAlignVertical.center,
|
|
||||||
style: theme.textTheme.bodySmall,
|
|
||||||
textCapitalization: TextCapitalization.sentences,
|
|
||||||
controller: textController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
enabledBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(25),
|
|
||||||
borderSide: const BorderSide(color: Colors.black),
|
|
||||||
),
|
|
||||||
focusedBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(25),
|
|
||||||
borderSide: const BorderSide(color: Colors.black),
|
|
||||||
),
|
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 0,
|
|
||||||
horizontal: 30,
|
|
||||||
),
|
|
||||||
hintText: options.translations.messagePlaceholder,
|
|
||||||
hintStyle: theme.textTheme.bodyMedium,
|
|
||||||
fillColor: Colors.white,
|
|
||||||
filled: true,
|
|
||||||
border: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(25)),
|
|
||||||
borderSide: BorderSide.none,
|
|
||||||
),
|
|
||||||
suffixIcon: messageSendButtons,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
|
||||||
child: SizedBox(
|
|
||||||
height: 45,
|
|
||||||
child: options.builders.messageInputBuilder?.call(
|
|
||||||
context,
|
|
||||||
textController,
|
|
||||||
messageSendButtons,
|
|
||||||
options.translations,
|
|
||||||
) ??
|
|
||||||
defaultInputField,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A single chat bubble in the chat
|
|
||||||
class _ChatBubble extends HookWidget {
|
|
||||||
const _ChatBubble({
|
|
||||||
required this.message,
|
|
||||||
required this.sender,
|
|
||||||
required this.onPressSender,
|
|
||||||
this.previousMessage,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// The message to display.
|
|
||||||
final MessageModel message;
|
|
||||||
|
|
||||||
/// The user who sent the message. This can be null because some messages are
|
|
||||||
/// not from users
|
|
||||||
final UserModel? sender;
|
|
||||||
|
|
||||||
/// The previous message in the list, if any.
|
|
||||||
final MessageModel? previousMessage;
|
|
||||||
|
|
||||||
/// Callback function when a message sender is pressed.
|
|
||||||
final Function(UserModel user) onPressSender;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
var chatScope = ChatScope.of(context);
|
|
||||||
var options = chatScope.options;
|
|
||||||
|
|
||||||
return options.builders.chatMessageBuilder.call(
|
|
||||||
context,
|
|
||||||
message,
|
|
||||||
previousMessage,
|
|
||||||
sender,
|
|
||||||
onPressSender,
|
|
||||||
) ??
|
|
||||||
DefaultChatMessageBuilder(
|
|
||||||
message: message,
|
|
||||||
previousMessage: previousMessage,
|
|
||||||
sender: sender,
|
|
||||||
onPressSender: onPressSender,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||||
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_chat/src/util/scope.dart";
|
||||||
|
import "package:flutter_hooks/flutter_hooks.dart";
|
||||||
|
|
||||||
|
/// Chat Bottom section where the user can type or upload images.
|
||||||
|
class ChatBottomInputSection extends HookWidget {
|
||||||
|
/// Creates a new [ChatBottomInputSection].
|
||||||
|
const ChatBottomInputSection({
|
||||||
|
required this.chat,
|
||||||
|
required this.onMessageSubmit,
|
||||||
|
this.onPressSelectImage,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The chat model.
|
||||||
|
final ChatModel chat;
|
||||||
|
|
||||||
|
/// Callback function invoked when a message is submitted.
|
||||||
|
final Function(String text) onMessageSubmit;
|
||||||
|
|
||||||
|
/// Callback function invoked when the select image button is pressed.
|
||||||
|
final VoidCallback? onPressSelectImage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var chatScope = ChatScope.of(context);
|
||||||
|
var options = chatScope.options;
|
||||||
|
var theme = Theme.of(context);
|
||||||
|
|
||||||
|
var textController = useTextEditingController();
|
||||||
|
var isTyping = useState(false);
|
||||||
|
var isSending = useState(false);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() {
|
||||||
|
void listener() => isTyping.value = textController.text.isNotEmpty;
|
||||||
|
textController.addListener(listener);
|
||||||
|
return () => textController.removeListener(listener);
|
||||||
|
},
|
||||||
|
[textController],
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<void> sendMessage() async {
|
||||||
|
isSending.value = true;
|
||||||
|
var value = textController.text;
|
||||||
|
if (value.isNotEmpty) {
|
||||||
|
await onMessageSubmit(value);
|
||||||
|
textController.clear();
|
||||||
|
}
|
||||||
|
isSending.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> Function()? onClickSendMessage;
|
||||||
|
if (isTyping.value && !isSending.value) {
|
||||||
|
onClickSendMessage = () async => sendMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Image and send buttons
|
||||||
|
var messageSendButtons = Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: onPressSelectImage,
|
||||||
|
icon: Icon(
|
||||||
|
Icons.image_outlined,
|
||||||
|
color: options.iconEnabledColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
disabledColor: options.iconDisabledColor,
|
||||||
|
color: options.iconEnabledColor,
|
||||||
|
onPressed: onClickSendMessage,
|
||||||
|
icon: const Icon(Icons.send_rounded),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
var defaultInputField = TextField(
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
textAlignVertical: TextAlignVertical.center,
|
||||||
|
style: theme.textTheme.bodySmall,
|
||||||
|
textCapitalization: TextCapitalization.sentences,
|
||||||
|
controller: textController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(25),
|
||||||
|
borderSide: const BorderSide(color: Colors.black),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(25),
|
||||||
|
borderSide: const BorderSide(color: Colors.black),
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 0,
|
||||||
|
horizontal: 30,
|
||||||
|
),
|
||||||
|
hintText: options.translations.messagePlaceholder,
|
||||||
|
hintStyle: theme.textTheme.bodyMedium,
|
||||||
|
fillColor: Colors.white,
|
||||||
|
filled: true,
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(25)),
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
),
|
||||||
|
suffixIcon: messageSendButtons,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 45,
|
||||||
|
child: options.builders.messageInputBuilder?.call(
|
||||||
|
context,
|
||||||
|
textController,
|
||||||
|
messageSendButtons,
|
||||||
|
options.translations,
|
||||||
|
) ??
|
||||||
|
defaultInputField,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||||
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_chat/src/screens/chat_detail/widgets/default_message_builder.dart";
|
||||||
|
import "package:flutter_chat/src/util/scope.dart";
|
||||||
|
import "package:flutter_hooks/flutter_hooks.dart";
|
||||||
|
|
||||||
|
/// Widget displayed when there are no messages in the chat.
|
||||||
|
class ChatNoMessages extends HookWidget {
|
||||||
|
/// Creates a new [ChatNoMessages] widget.
|
||||||
|
const ChatNoMessages({
|
||||||
|
required this.isGroupChat,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Determines if this chat is a group chat.
|
||||||
|
final bool isGroupChat;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var chatScope = ChatScope.of(context);
|
||||||
|
var translations = chatScope.options.translations;
|
||||||
|
var theme = Theme.of(context);
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: Text(
|
||||||
|
isGroupChat
|
||||||
|
? translations.writeFirstMessageInGroupChat
|
||||||
|
: translations.writeMessageToStartChat,
|
||||||
|
style: theme.textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A single chat bubble in the chat
|
||||||
|
class ChatBubble extends HookWidget {
|
||||||
|
/// Creates a new [ChatBubble] widget.
|
||||||
|
const ChatBubble({
|
||||||
|
required this.message,
|
||||||
|
required this.sender,
|
||||||
|
required this.onPressSender,
|
||||||
|
this.previousMessage,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The message to display.
|
||||||
|
final MessageModel message;
|
||||||
|
|
||||||
|
/// The user who sent the message. This can be null because some messages are
|
||||||
|
/// not from users
|
||||||
|
final UserModel? sender;
|
||||||
|
|
||||||
|
/// The previous message in the list, if any.
|
||||||
|
final MessageModel? previousMessage;
|
||||||
|
|
||||||
|
/// Callback function when a message sender is pressed.
|
||||||
|
final Function(UserModel user) onPressSender;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var chatScope = ChatScope.of(context);
|
||||||
|
var options = chatScope.options;
|
||||||
|
|
||||||
|
return options.builders.chatMessageBuilder.call(
|
||||||
|
context,
|
||||||
|
message,
|
||||||
|
previousMessage,
|
||||||
|
sender,
|
||||||
|
onPressSender,
|
||||||
|
) ??
|
||||||
|
DefaultChatMessageBuilder(
|
||||||
|
message: message,
|
||||||
|
previousMessage: previousMessage,
|
||||||
|
sender: sender,
|
||||||
|
onPressSender: onPressSender,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue