413 lines
14 KiB
Dart
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);
|
|
}
|
|
}
|