Files
intaleq_driver/lib/views/widgets/voice_call_bottom_sheet.dart

301 lines
11 KiB
Dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../constant/colors.dart';
import '../../constant/style.dart';
import '../../controller/voice_call_controller.dart';
class VoiceCallBottomSheet extends StatelessWidget {
const VoiceCallBottomSheet({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final controller = Get.find<VoiceCallController>();
final double screenHeight = MediaQuery.of(context).size.height;
final bool isDark = Theme.of(context).brightness == Brightness.dark;
// Harmonious curated colors
final Color bgColor = isDark ? const Color(0xFF121212) : Colors.white;
final Color cardColor = isDark ? const Color(0xFF1E1E1E) : const Color(0xFFF5F5F7);
final Color textColor = isDark ? Colors.white : const Color(0xFF1C1C1E);
final Color subTextColor = isDark ? Colors.white70 : Colors.black54;
return WillPopScope(
onWillPop: () async => false,
child: Container(
height: screenHeight * 0.9,
decoration: BoxDecoration(
color: bgColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(32)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 20,
offset: const Offset(0, -5),
)
],
),
child: Obx(() {
final state = controller.state.value;
final seconds = controller.elapsedSeconds.value;
final remoteName = controller.remoteName.value;
final isMuted = controller.isMuted.value;
final isSpeakerOn = controller.isSpeakerOn.value;
final errorMsg = controller.errorMessage.value;
// Progress ring logic
final double progress = seconds / 60.0;
final Color ringColor = (errorMsg.isNotEmpty || seconds <= 10)
? const Color(0xFFE74C3C)
: const Color(0xFF2ECC71);
// Status text translations
String statusText = "";
if (errorMsg.isNotEmpty) {
statusText = errorMsg;
} else {
switch (state) {
case VoiceCallState.dialing:
statusText = "${'Calling'.tr} $remoteName...";
break;
case VoiceCallState.ringing:
statusText = "${'Incoming Call...'.tr}";
break;
case VoiceCallState.connecting:
statusText = "Connecting...".tr;
break;
case VoiceCallState.active:
statusText = "Call Connected".tr;
break;
case VoiceCallState.ended:
statusText = "Call Ended".tr;
break;
case VoiceCallState.idle:
statusText = "";
break;
}
}
return Column(
children: [
// Top Drag Handle Indicator
Center(
child: Container(
margin: const EdgeInsets.only(top: 12, bottom: 24),
width: 44,
height: 5,
decoration: BoxDecoration(
color: isDark ? Colors.white24 : Colors.black12,
borderRadius: BorderRadius.circular(10),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Header Info
Column(
children: [
Text(
"Free Call".tr,
style: TextStyle(
color: ringColor,
fontWeight: FontWeight.w800,
fontSize: 14,
letterSpacing: 1.2,
),
),
const SizedBox(height: 8),
Text(
remoteName,
style: TextStyle(
color: textColor,
fontWeight: FontWeight.w900,
fontSize: 26,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
statusText,
style: TextStyle(
color: errorMsg.isNotEmpty
? const Color(0xFFE74C3C)
: subTextColor,
fontWeight: FontWeight.w600,
fontSize: 16,
),
textAlign: TextAlign.center,
),
],
),
// Avatar & Animated Progress Ring
Stack(
alignment: Alignment.center,
children: [
// Progress ring around avatar (Active state only)
if (state == VoiceCallState.active)
SizedBox(
width: 172,
height: 172,
child: CircularProgressIndicator(
value: progress,
strokeWidth: 5,
backgroundColor: isDark ? Colors.white10 : Colors.black12,
valueColor: AlwaysStoppedAnimation<Color>(ringColor),
),
),
// Main Avatar Card
Container(
width: 150,
height: 150,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: cardColor,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 15,
offset: const Offset(0, 8),
)
],
),
child: Center(
child: remoteName.isNotEmpty
? Text(
remoteName[0].toUpperCase(),
style: TextStyle(
color: textColor,
fontWeight: FontWeight.bold,
fontSize: 54,
),
)
: Icon(
Icons.person,
color: textColor.withOpacity(0.6),
size: 64,
),
),
),
],
),
// Timer Counter Display
if (state == VoiceCallState.active)
Text(
"0:${seconds.toString().padLeft(2, '0')}",
style: TextStyle(
color: seconds > 10 ? textColor : const Color(0xFFE74C3C),
fontWeight: FontWeight.bold,
fontSize: 22,
fontFamily: 'monospace',
),
)
else
const SizedBox(height: 24),
// Action Controls Block
if (state == VoiceCallState.ringing)
// Incoming Ringing Controls: Accept / Decline
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildCircleActionButton(
icon: Icons.call_end_rounded,
color: Colors.white,
bgColor: const Color(0xFFE74C3C),
onTap: () => controller.declineCall(),
label: "Decline".tr,
),
_buildCircleActionButton(
icon: Icons.call_rounded,
color: Colors.white,
bgColor: const Color(0xFF2ECC71),
onTap: () => controller.acceptCall(),
label: "Accept".tr,
),
],
)
else
// Dialing or Connected Controls: Speaker / Mute / Hangup
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Speakerphone toggle
_buildCircleActionButton(
icon: isSpeakerOn ? Icons.volume_up_rounded : Icons.volume_down_rounded,
color: isSpeakerOn ? Colors.white : textColor,
bgColor: isSpeakerOn ? const Color(0xFF2ECC71) : cardColor,
onTap: () => controller.toggleSpeaker(),
label: "Speaker".tr,
),
// Hangup Call
_buildCircleActionButton(
icon: Icons.call_end_rounded,
color: Colors.white,
bgColor: const Color(0xFFE74C3C),
onTap: () => controller.hangup(),
label: "End".tr,
),
// Mute Microphone
_buildCircleActionButton(
icon: isMuted ? Icons.mic_off_rounded : Icons.mic_rounded,
color: isMuted ? Colors.white : textColor,
bgColor: isMuted ? const Color(0xFFE74C3C) : cardColor,
onTap: () => controller.toggleMute(),
label: "Mute".tr,
),
],
),
],
),
),
),
],
);
}),
),
);
}
Widget _buildCircleActionButton({
required IconData icon,
required Color color,
required Color bgColor,
required VoidCallback onTap,
required String label,
}) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: onTap,
style: ElevatedButton.styleFrom(
shape: const CircleBorder(),
padding: const EdgeInsets.all(18),
backgroundColor: bgColor,
foregroundColor: color,
elevation: 2,
),
child: Icon(icon, size: 28),
),
const SizedBox(height: 8),
Text(
label,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.grey,
),
),
],
);
}
}