onufitness_mobile/lib/screens/chat/widgets/chat_input_area.dart
2026-01-13 11:36:24 +05:30

413 lines
14 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:onufitness/constants/api_enum_constant.dart';
import 'package:onufitness/constants/color_constant.dart';
import 'package:onufitness/screens/chat/controllers/chat_controller.dart';
import 'package:onufitness/screens/chat/widgets/show_media_bottom_sheet.dart';
class ChatInputAreaWithVoice extends StatelessWidget {
final ChatController chatCtrl;
final TextEditingController textController;
final String targetUserId;
final bool isGroupMessage;
final String targetUserName;
const ChatInputAreaWithVoice({
super.key,
required this.chatCtrl,
required this.textController,
required this.targetUserId,
this.isGroupMessage = false,
required this.targetUserName,
});
@override
Widget build(BuildContext context) {
return PopScope(
canPop: true,
onPopInvokedWithResult: (didPop, result) {
// Stop recording when navigating back
if (chatCtrl.isRecording.value) {
chatCtrl.cancelRecording();
}
},
child: Obx(() {
// Show recording interface when recording
if (chatCtrl.isRecording.value) {
return recordingInterface(context);
}
// Show normal input interface
return messageInputInterface(context);
}),
);
}
// Normal chat input interface
Widget messageInputInterface(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 10.h),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
offset: const Offset(0, -1),
blurRadius: 3,
),
],
),
child: Row(
children: [
// Attachment button
IconButton(
icon: Icon(
Icons.add_circle_outline,
color: Colors.grey.shade600,
size: 26.sp,
),
onPressed:
() => showMediaOptions(
context,
targetUserId,
isGroup: isGroupMessage,
),
),
// Text input
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(22),
),
child: TextField(
controller: textController,
decoration: InputDecoration(
hintText: "Message",
hintStyle: TextStyle(
color: Colors.grey.shade500,
fontSize: 14.sp,
),
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 16.w,
vertical: 10.h,
),
),
maxLines: null,
),
),
),
SizedBox(width: 8.w),
// Mic or Send button
ValueListenableBuilder<TextEditingValue>(
valueListenable: textController,
builder: (context, value, child) {
final hasText = value.text.trim().isNotEmpty;
return GestureDetector(
onTap: hasText ? _sendMessage : null,
onLongPress: !hasText ? () => _startRecording() : null,
child: Container(
width: 40.w,
height: 40.h,
decoration: BoxDecoration(
color: Color(primaryColor),
shape: BoxShape.circle,
),
child: Icon(
hasText ? Icons.send_rounded : Icons.mic,
color: Colors.black,
size: 20.sp,
),
),
);
},
),
],
),
),
// Bottom padding
Container(
color: Colors.white,
height: MediaQuery.of(context).padding.bottom,
),
],
);
}
// Compact professional voice recording interface with pause button
Widget recordingInterface(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
decoration: BoxDecoration(
color: Colors.white,
border: Border(
top: BorderSide(color: Colors.red.shade100, width: 2),
),
boxShadow: [
BoxShadow(
color: Colors.red.withValues(alpha: 0.08),
offset: Offset(0, -2),
blurRadius: 8,
),
],
),
child: Row(
children: [
// Animated recording indicator (only shows when not paused)
Obx(() {
final isPaused = chatCtrl.isRecordingPaused.value;
return TweenAnimationBuilder(
key: ValueKey(isPaused),
tween: Tween<double>(begin: 0.7, end: 1.0),
duration: const Duration(milliseconds: 600),
curve: Curves.easeInOut,
onEnd: () {
// This will restart via widget rebuild
},
builder: (context, double value, child) {
return Transform.scale(
scale: isPaused ? 1.0 : value,
child: Container(
width: 10.w,
height: 10.h,
decoration: BoxDecoration(
color: isPaused ? Colors.orange : Colors.red,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: (isPaused ? Colors.orange : Colors.red)
.withValues(alpha: 0.4),
blurRadius: 6,
spreadRadius: 1,
),
],
),
),
);
},
);
}),
SizedBox(width: 12.w),
// Recording waveform animation (only animates when not paused)
Obx(() {
final isPaused = chatCtrl.isRecordingPaused.value;
return Row(
children: List.generate(15, (index) {
return Padding(
padding: EdgeInsets.only(right: 3.w),
child: TweenAnimationBuilder(
key: ValueKey('$isPaused-$index'),
tween: Tween<double>(
begin: 8.0,
end:
isPaused
? 8.0
: [12.0, 18.0, 14.0, 20.0, 16.0][index % 5],
),
duration: Duration(milliseconds: 300 + (index * 50)),
curve: Curves.easeInOut,
onEnd: () {},
builder: (context, double height, child) {
return Container(
width: 2.5.w,
height: height.h,
decoration: BoxDecoration(
color:
isPaused
? Colors.orange.shade300
: Colors.red.shade400,
borderRadius: BorderRadius.circular(2),
),
);
},
),
);
}),
);
}),
const Spacer(),
// Duration display
Obx(() {
final isPaused = chatCtrl.isRecordingPaused.value;
return Container(
padding: EdgeInsets.symmetric(
horizontal: 12.w,
vertical: 6.h,
),
decoration: BoxDecoration(
color:
isPaused ? Colors.orange.shade50 : Colors.red.shade50,
borderRadius: BorderRadius.circular(16),
),
child: Text(
chatCtrl.formatVoiceDuration(
chatCtrl.recordingDuration.value,
),
style: TextStyle(
fontSize: 13.sp,
color:
isPaused
? Colors.orange.shade700
: Colors.red.shade700,
fontWeight: FontWeight.w600,
letterSpacing: 0.5,
),
),
);
}),
SizedBox(width: 12.w),
// Pause/Resume button
Obx(() {
final isPaused = chatCtrl.isRecordingPaused.value;
return GestureDetector(
onTap: () => chatCtrl.togglePauseRecording(),
child: Container(
width: 38.w,
height: 38.h,
decoration: BoxDecoration(
color:
isPaused
? Colors.orange.shade100
: Colors.grey.shade100,
shape: BoxShape.circle,
),
child: Icon(
isPaused ? Icons.play_arrow_rounded : Icons.pause_rounded,
color:
isPaused
? Colors.orange.shade700
: Colors.grey.shade700,
size: 20.sp,
),
),
);
}),
SizedBox(width: 8.w),
// Delete button
GestureDetector(
onTap: () => chatCtrl.cancelRecording(),
child: Container(
width: 38.w,
height: 38.h,
decoration: BoxDecoration(
color: Colors.grey.shade100,
shape: BoxShape.circle,
),
child: Icon(
Icons.delete_outline_rounded,
color: Colors.grey.shade700,
size: 20.sp,
),
),
),
SizedBox(width: 8.w),
// Send button
GestureDetector(
onTap: () async {
chatCtrl.stopAndSendRecording(
targetUserId,
isGroup: isGroupMessage,
);
chatCtrl.isRecordingPaused.value = false;
if (isGroupMessage) {
await chatCtrl.sendGroupChatNotification(
notificationMessage: "Voice message",
notificationTitle: "",
notificationType: ApiEnum.ChatNotification,
tribeId: targetUserId,
);
} else {
await chatCtrl.sendChatNotification(
notificationMessage: "Voice message",
notificationTitle: "",
notificationType: ApiEnum.ChatNotification,
receiverId: targetUserId,
);
}
},
child: Container(
width: 38.w,
height: 38.h,
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.red.withValues(alpha: 0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Icon(
Icons.send_rounded,
color: Colors.white,
size: 18.sp,
),
),
),
],
),
),
// Bottom padding
Container(
color: Colors.white,
height: MediaQuery.of(context).padding.bottom,
),
],
);
}
void _sendMessage() {
final text = textController.text.trim();
if (text.isNotEmpty) {
chatCtrl.sendTextMessage(targetUserId, text, isGroup: isGroupMessage);
textController.clear();
if (isGroupMessage) {
chatCtrl.sendGroupChatNotification(
notificationMessage: text,
notificationTitle: "",
notificationType: ApiEnum.ChatNotification,
tribeId: targetUserId,
);
} else {
chatCtrl.sendChatNotification(
notificationMessage: text,
notificationTitle: "",
notificationType: ApiEnum.ChatNotification,
receiverId: targetUserId,
);
}
}
}
void _startRecording() {
chatCtrl.startRecording(targetUserId);
}
}