264 lines
8.1 KiB
Dart
264 lines
8.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'dart:io';
|
|
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
|
|
import '../models/message.dart';
|
|
import 'package:flutter/foundation.dart' as foundation;
|
|
import '../../../core/theme/app_colors.dart';
|
|
import 'reply_bar.dart';
|
|
|
|
class MessageInputWidget extends StatelessWidget {
|
|
final TextEditingController messageController;
|
|
final FocusNode focusNode;
|
|
final bool isUploading;
|
|
final File? selectedImage;
|
|
final bool showEmojiKeyboard;
|
|
final bool isReplying;
|
|
final Message? replyToMessage;
|
|
final VoidCallback onSend;
|
|
final VoidCallback onImageOptions;
|
|
final VoidCallback onEmojiToggle;
|
|
final VoidCallback onClearImage;
|
|
final VoidCallback onCancelReply;
|
|
final Color themeColor;
|
|
|
|
const MessageInputWidget({
|
|
Key? key,
|
|
required this.messageController,
|
|
required this.focusNode,
|
|
required this.isUploading,
|
|
required this.selectedImage,
|
|
required this.showEmojiKeyboard,
|
|
required this.isReplying,
|
|
required this.replyToMessage,
|
|
required this.onSend,
|
|
required this.onImageOptions,
|
|
required this.onEmojiToggle,
|
|
required this.onClearImage,
|
|
required this.onCancelReply,
|
|
required this.themeColor,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// Wrap everything in a Column to contain emoji keyboard
|
|
return Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// Selected image preview
|
|
if (selectedImage != null) _buildImagePreview(),
|
|
|
|
// Reply bar
|
|
if (isReplying) _buildReplyBar(),
|
|
|
|
// Input bar
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 3,
|
|
offset: Offset(0, -1),
|
|
),
|
|
],
|
|
),
|
|
child: SafeArea(
|
|
top: false,
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
// Emoji button
|
|
IconButton(
|
|
icon: Icon(
|
|
showEmojiKeyboard ? Icons.keyboard : Icons.emoji_emotions_outlined,
|
|
color: themeColor,
|
|
),
|
|
onPressed: onEmojiToggle,
|
|
padding: EdgeInsets.zero,
|
|
constraints: BoxConstraints(
|
|
minWidth: 36,
|
|
minHeight: 36,
|
|
),
|
|
),
|
|
|
|
// Text field
|
|
Expanded(
|
|
child: ConstrainedBox(
|
|
constraints: BoxConstraints(
|
|
maxHeight: 120.0, // Limit max height
|
|
),
|
|
child: TextField(
|
|
controller: messageController,
|
|
focusNode: focusNode,
|
|
minLines: 1,
|
|
maxLines: 5, // Allow multiple lines but not too many
|
|
textCapitalization: TextCapitalization.sentences,
|
|
decoration: InputDecoration(
|
|
hintText: 'Ketik pesan...',
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 10.0),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
// Attachment button
|
|
IconButton(
|
|
icon: Icon(
|
|
Icons.attach_file,
|
|
color: themeColor,
|
|
),
|
|
onPressed: onImageOptions,
|
|
padding: EdgeInsets.zero,
|
|
constraints: BoxConstraints(
|
|
minWidth: 36,
|
|
minHeight: 36,
|
|
),
|
|
),
|
|
|
|
// Send button
|
|
_buildSendButton(),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
// Emoji keyboard - Wrap in AnimatedContainer for smooth transitions
|
|
AnimatedContainer(
|
|
duration: Duration(milliseconds: 200),
|
|
height: showEmojiKeyboard ? _getEmojiKeyboardHeight(context) : 0,
|
|
child: showEmojiKeyboard ? _buildEmojiPicker(context) : SizedBox(),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildImagePreview() {
|
|
return Container(
|
|
width: double.infinity,
|
|
padding: EdgeInsets.all(8.0),
|
|
color: Colors.grey[200],
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
// Image preview with fixed height
|
|
Container(
|
|
height: 150,
|
|
width: double.infinity,
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(8),
|
|
color: Colors.black,
|
|
),
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(8),
|
|
child: Image.file(
|
|
selectedImage!,
|
|
fit: BoxFit.cover,
|
|
),
|
|
),
|
|
),
|
|
|
|
// Loading indicator
|
|
if (isUploading)
|
|
Container(
|
|
height: 150,
|
|
width: double.infinity,
|
|
color: Colors.black54,
|
|
child: Center(
|
|
child: CircularProgressIndicator(
|
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
|
),
|
|
),
|
|
),
|
|
|
|
// Close button
|
|
Positioned(
|
|
top: 0,
|
|
right: 0,
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(20),
|
|
onTap: onClearImage,
|
|
child: Container(
|
|
padding: EdgeInsets.all(4),
|
|
decoration: BoxDecoration(
|
|
color: Colors.black54,
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(
|
|
Icons.close,
|
|
color: Colors.white,
|
|
size: 20,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildReplyBar() {
|
|
if (replyToMessage == null) return SizedBox.shrink();
|
|
|
|
return ReplyBar(
|
|
message: replyToMessage!,
|
|
onCancel: onCancelReply,
|
|
);
|
|
}
|
|
|
|
Widget _buildSendButton() {
|
|
final bool canSend = messageController.text.trim().isNotEmpty || selectedImage != null;
|
|
|
|
return GestureDetector(
|
|
onTap: canSend ? onSend : null,
|
|
child: Container(
|
|
width: 36,
|
|
height: 36,
|
|
margin: EdgeInsets.only(left: 4, right: 4),
|
|
decoration: BoxDecoration(
|
|
color: canSend ? themeColor : Colors.grey,
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(
|
|
Icons.send,
|
|
color: Colors.white,
|
|
size: 18,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildEmojiPicker(BuildContext context) {
|
|
return EmojiPicker(
|
|
onEmojiSelected: (category, emoji) {
|
|
messageController.text = messageController.text + emoji.emoji;
|
|
},
|
|
textEditingController: messageController,
|
|
config: Config(
|
|
checkPlatformCompatibility: true,
|
|
),
|
|
);
|
|
}
|
|
|
|
// Calculate emoji keyboard height based on screen size and keyboard visibility
|
|
double _getEmojiKeyboardHeight(BuildContext context) {
|
|
final screenHeight = MediaQuery.of(context).size.height;
|
|
final keyboardVisible = MediaQuery.of(context).viewInsets.bottom > 0;
|
|
|
|
// Calculate safe height for emoji picker
|
|
final double baseHeight = keyboardVisible
|
|
? screenHeight * 0.25 // 25% when keyboard visible
|
|
: screenHeight * 0.35; // 35% when keyboard hidden
|
|
|
|
// Ensure we don't exceed available space
|
|
final availableHeight = screenHeight -
|
|
MediaQuery.of(context).viewInsets.bottom -
|
|
kToolbarHeight - 100;
|
|
|
|
return baseHeight.clamp(100.0, availableHeight);
|
|
}
|
|
} |