import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; // import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/modules/cx_module/chat/chat_provider.dart'; import 'package:test_sa/modules/cx_module/chat/model/call_session.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/app_style/app_themes.dart'; import 'package:test_sa/modules/cx_module/chat/call/widgets/draggable_local_preview.dart'; class VideoCallPage extends StatefulWidget { const VideoCallPage({Key? key}) : super(key: key); @override State createState() => _VideoCallPageState(); } class _VideoCallPageState extends State { @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { // Initialize video call }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColor.backgroundTabBarDark, body: Selector( selector: (_, provider) => provider.currentCall, builder: (context, session, _) { if (session == null) { return const Center( child: Text( 'No active call', style: TextStyle(color: Colors.white), ), ); } return Stack( children: [ _buildRemoteVideo(context), _buildLocalVideo(context), _buildTopBar(context, session), _buildBottomControls(context, session), ], ); }, ), ); } Widget _buildRemoteVideo(BuildContext context) { final chatProvider = context.read(); return Positioned.fill( child: Selector( selector: (_, provider) => provider.isPeerCameraOn, builder: (context, isPeerCameraOn, _) { if (!isPeerCameraOn) { // Show avatar when peer camera is off return _buildRemoteAvatarPlaceholder(context); } // TODO: Replace with actual RTCVideoView when WebRTC is wired return Container( color: Colors.black, child: const Center( child: Text( 'Remote Video', style: TextStyle(color: Colors.white54), ), ), ); /* Will be replaced with: return RTCVideoView( chatProvider.webrtcService.remoteRenderer, objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, mirror: false, ); */ }, ), ); } Widget _buildRemoteAvatarPlaceholder(BuildContext context) { return Selector( selector: (_, provider) => provider.currentCall, builder: (context, session, _) { if (session == null) return const SizedBox.shrink(); return Container( color: AppColor.backgroundTabBarDark, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( shape: BoxShape.circle, color: AppColor.whiteF8d, border: Border.all(color: AppColor.white10.withAlpha(51), width: 1), ), child: ClipOval( child: session.peerAvatar != null ? CachedNetworkImage( imageUrl: session.peerAvatar!, fit: BoxFit.cover, placeholder: (context, url) => Center( child: CircularProgressIndicator( color: Colors.white.withAlpha(128), ), ), errorWidget: (context, url, error) => 'call_user_avatar'.toSvgAsset(height: 48, width: 48), ) : 'call_user_avatar'.toSvgAsset(height: 48, width: 48), ), ), 24.height, Text( session.peerName, style: AppTextStyles.heading2.copyWith( color: Colors.white, fontWeight: FontWeight.w600, ), textAlign: TextAlign.center, ), 8.height, Text( 'Camera is off', style: AppTextStyles.heading5.copyWith( color: AppColor.neutral100, fontWeight: FontWeight.w400, ), ), ], ), ), ); }, ); } Widget _buildLocalVideo(BuildContext context) { return Selector( selector: (_, provider) => provider.isCameraOn, builder: (context, isCameraOn, _) { if (!isCameraOn) { // Show avatar when local camera is off return DraggableLocalPreview( child: Container( width: 120, height: 160, decoration: BoxDecoration( color: Colors.grey[800], borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.white, width: 2), ), child: const Icon( Icons.videocam_off, color: Colors.white, size: 32, ), ), ); } // TODO: Replace with actual RTCVideoView when WebRTC is wired return DraggableLocalPreview( child: Container( width: 120, height: 160, decoration: BoxDecoration( color: Colors.black, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.white, width: 2), ), child: const Center( child: Text( 'You', style: TextStyle(color: Colors.white54), ), ), ), ); /* Will be replaced with: final chatProvider = context.read(); return DraggableLocalPreview( child: ClipRRect( borderRadius: BorderRadius.circular(12), child: RTCVideoView( chatProvider.webrtcService.localRenderer, objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, mirror: true, ), ), ); */ }, ); } Widget _buildTopBar(BuildContext context, CallSession session) { return Align( alignment: AlignmentGeometry.topLeft, child: Padding( padding: EdgeInsets.only( top: MediaQuery.of(context).padding.top, ), child: IconButton( icon: const Icon( Icons.arrow_back_ios, color: AppColor.white10, ), onPressed: () => Navigator.pop(context), ), ), ); } Widget _buildBottomControls(BuildContext context, CallSession session) { final chatProvider = context.read(); return Positioned( bottom: 24, left: 0, right: 0, //for now use these colors, later we can change to use the theme colors child: Container( decoration: BoxDecoration( gradient: const LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [ Color.fromRGBO(65, 67, 82, 0.7), Color.fromRGBO(105, 109, 133, 0.7), Color.fromRGBO(65, 67, 82, 0.7), ], stops: [0.0, 0.5, 1.0], ), borderRadius: BorderRadius.circular(10), border: Border.all(color: AppColor.white10.withAlpha(51)), ), padding: const EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( session.peerName, style: AppTextStyles.heading2.copyWith( color: Colors.white, fontWeight: FontWeight.w600, ), textAlign: TextAlign.center, ), 16.height, _buildCallStatusOrDuration(context), 16.height, Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Selector( selector: (_, provider) => provider.isMuted, builder: (context, isMuted, _) { return _buildVideoControlButton( icon: isMuted ? 'mic_disable' : 'mic_enable', isActive: isMuted, onPressed: () => chatProvider.toggleMute(), ); }, ), // Camera toggle Selector( selector: (_, provider) => provider.isCameraOn, builder: (context, isCameraOn, _) { return _buildVideoControlButton( icon: isCameraOn ? 'video_enable' : 'video_disable', isActive: !isCameraOn, onPressed: () => chatProvider.toggleCamera(), ); }, ), // Switch camera _buildVideoControlButton( icon: 'toggle_camera', isActive: false, onPressed: () => chatProvider.switchCamera(), ), // Speaker Selector( selector: (_, provider) => provider.isSpeakerOn, builder: (context, isSpeakerOn, _) { return _buildVideoControlButton( icon: 'speaker_enable', isActive: isSpeakerOn, onPressed: () => chatProvider.toggleSpeaker(), ); }, ), _buildVideoControlButton( icon: 'end_call', isHangUp: true, showBorder: false, onPressed: () { chatProvider.hangUp(); if (mounted) { Navigator.pop(context); } }, ), ], ), ], ), ).paddingOnly(bottom: 16, start: 16, end: 16), ); } Widget _buildCallStatusOrDuration(BuildContext context) { return Selector( selector: (_, provider) => provider.callStatus, builder: (context, status, _) { String statusText; switch (status) { case CallStatus.outgoingRinging: statusText = 'Calling...'; break; case CallStatus.incomingRinging: statusText = 'Incoming call...'; break; case CallStatus.connecting: statusText = 'Connecting...'; break; case CallStatus.connected: return _buildDuration(context); default: statusText = ''; } return Text( statusText, style: AppTextStyles.heading5.copyWith( color: AppColor.neutral100, fontWeight: FontWeight.w400, ), ); }, ); } Widget _buildDuration(BuildContext context) { return Selector( selector: (_, provider) => provider.callDuration, builder: (context, duration, _) { if (duration.inSeconds == 0) { return const SizedBox.shrink(); } String twoDigits(int n) => n.toString().padLeft(2, '0'); final minutes = twoDigits(duration.inMinutes.remainder(60)); final seconds = twoDigits(duration.inSeconds.remainder(60)); return Text( '$minutes:$seconds', style: AppTextStyles.heading5.copyWith( color: AppColor.neutral100, fontWeight: FontWeight.w400, fontFeatures: [const FontFeature.tabularFigures()], ), ); }, ); } Widget _buildVideoControlButton({ required String icon, bool? isActive, bool isHangUp = false, bool showBorder = true, required VoidCallback onPressed, }) { return Container( padding: const EdgeInsets.all(18), decoration: BoxDecoration( color: isHangUp ? AppColor.red30 : isActive != null && isActive ? AppColor.primary10 : AppColor.black2E, shape: BoxShape.circle, border: showBorder ? BoxBorder.all(color: AppColor.white10.withOpacity(0.2)) : null), child: icon.toSvgAsset(width: 24, height: 24, color: AppColor.white10), ).onPress(() { onPressed(); }); } }