From 8c2d72eb84ac2da7566434a2573e22afb3d82946 Mon Sep 17 00:00:00 2001 From: Deep Koluguri Date: Thu, 6 Nov 2025 21:37:42 -0500 Subject: [PATCH] fixed slot issue --- luckychit/lib/app.dart | 8 +- .../lib/core/services/chit_group_service.dart | 17 +- .../services/screen_recording_service.dart | 90 ++++-- luckychit/lib/core/themes/app_theme.dart | 94 +++--- .../manager/combined_draw_dialog.dart | 278 +++++++++++++----- .../manager/draw_animation_page.dart | 5 +- .../lib/shared/widgets/recording_overlay.dart | 20 +- 7 files changed, 365 insertions(+), 147 deletions(-) diff --git a/luckychit/lib/app.dart b/luckychit/lib/app.dart index d096d1d..b6989de 100644 --- a/luckychit/lib/app.dart +++ b/luckychit/lib/app.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:get/get.dart'; import 'core/services/auth_service.dart'; import 'core/services/chit_group_service.dart'; @@ -19,7 +20,12 @@ class App extends StatelessWidget { // Initialize other services Get.put(ChitGroupService()); Get.put(PaymentService()); - Get.put(ScreenRecordingService()); + + // Only initialize ScreenRecordingService on mobile (not on web) + if (!kIsWeb) { + Get.put(ScreenRecordingService()); + } + return Obx(() { if (authService.isLoading.value) { return const Scaffold( diff --git a/luckychit/lib/core/services/chit_group_service.dart b/luckychit/lib/core/services/chit_group_service.dart index b6ac7b4..e7fe3ed 100644 --- a/luckychit/lib/core/services/chit_group_service.dart +++ b/luckychit/lib/core/services/chit_group_service.dart @@ -1,3 +1,4 @@ +import 'package:flutter/widgets.dart'; import 'package:get/get.dart'; import '../models/chit_group.dart'; import '../models/group_member.dart'; @@ -179,7 +180,7 @@ class ChitGroupService extends GetxController { } // Load group members - Future loadGroupMembers(String groupId, {String? status}) async { + Future loadGroupMembers(String groupId, {String? status, bool showErrors = true}) async { try { _isLoading.value = true; @@ -189,11 +190,21 @@ class ChitGroupService extends GetxController { final membersData = response['data']['members'] as List; _groupMembers.value = membersData.map((json) => GroupMember.fromJson(json)).toList(); } else { - Get.snackbar('Error', response['message']); + if (showErrors) { + // Use post frame callback to avoid showing snackbar during build + WidgetsBinding.instance.addPostFrameCallback((_) { + Get.snackbar('Error', response['message']); + }); + } } } catch (e) { print('Error loading group members: $e'); - Get.snackbar('Error', 'Failed to load group members'); + if (showErrors) { + // Use post frame callback to avoid showing snackbar during build + WidgetsBinding.instance.addPostFrameCallback((_) { + Get.snackbar('Error', 'Failed to load group members'); + }); + } } finally { _isLoading.value = false; } diff --git a/luckychit/lib/core/services/screen_recording_service.dart b/luckychit/lib/core/services/screen_recording_service.dart index 0122942..024175d 100644 --- a/luckychit/lib/core/services/screen_recording_service.dart +++ b/luckychit/lib/core/services/screen_recording_service.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'dart:async'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; @@ -26,12 +27,23 @@ class ScreenRecordingService extends GetxController { } Future _requestPermissions() async { - if (Platform.isAndroid) { - final status = await Permission.storage.request(); - return status.isGranted; - } else if (Platform.isIOS) { - final status = await Permission.photos.request(); - return status.isGranted; + // Web doesn't need permissions for this + if (kIsWeb) return true; + + // Only check Platform on non-web platforms + if (!kIsWeb) { + try { + if (Platform.isAndroid) { + final status = await Permission.storage.request(); + return status.isGranted; + } else if (Platform.isIOS) { + final status = await Permission.photos.request(); + return status.isGranted; + } + } catch (e) { + // Platform check failed + return true; + } } return true; } @@ -49,8 +61,14 @@ class ScreenRecordingService extends GetxController { return false; } + // Check if on web or desktop + bool isDesktopOrWeb = kIsWeb; + if (!kIsWeb) { + isDesktopOrWeb = Platform.isWindows || Platform.isLinux || Platform.isMacOS; + } + // Check platform support - if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { + if (isDesktopOrWeb && !kIsWeb) { Get.snackbar( 'Recording Not Supported', 'Screen recording is not available on desktop platforms. Recording simulation will be used instead.', @@ -62,7 +80,7 @@ class ScreenRecordingService extends GetxController { } final hasPermission = await _requestPermissions(); - if (!hasPermission && !Platform.isWindows && !Platform.isLinux && !Platform.isMacOS) { + if (!hasPermission && !isDesktopOrWeb && !kIsWeb) { Get.snackbar( 'Permission Required', 'Please grant storage permission to record the draw', @@ -84,16 +102,19 @@ class ScreenRecordingService extends GetxController { } }); - Get.snackbar( - 'Recording Started', - Platform.isWindows || Platform.isLinux || Platform.isMacOS - ? 'Draw recording simulation has begun (desktop mode)' - : 'Draw recording has begun', - snackPosition: SnackPosition.TOP, - backgroundColor: Colors.green.shade600, - colorText: Colors.white, - duration: const Duration(seconds: 2), - ); + // Don't show snackbar on web + if (!kIsWeb) { + Get.snackbar( + 'Recording Started', + isDesktopOrWeb + ? 'Draw recording simulation has begun (desktop mode)' + : 'Draw recording has begun', + snackPosition: SnackPosition.TOP, + backgroundColor: Colors.green.shade600, + colorText: Colors.white, + duration: const Duration(seconds: 2), + ); + } return true; } catch (e) { @@ -124,6 +145,12 @@ class ScreenRecordingService extends GetxController { _durationTimer?.cancel(); _isRecording.value = false; + // Web doesn't save files + if (kIsWeb) { + _recordingPath.value = 'web_recording_${DateTime.now().millisecondsSinceEpoch}'; + return _recordingPath.value; + } + // Generate recording file path final directory = await getApplicationDocumentsDirectory(); final timestamp = DateTime.now().millisecondsSinceEpoch; @@ -132,13 +159,21 @@ class ScreenRecordingService extends GetxController { _recordingPath.value = filePath; + // Check if desktop + bool isDesktop = false; + String platformName = 'Unknown'; + if (!kIsWeb) { + isDesktop = Platform.isWindows || Platform.isLinux || Platform.isMacOS; + platformName = Platform.operatingSystem; + } + // Create a mock recording file for desktop platforms final file = File(filePath); final recordingContent = ''' Draw Recording - ${DateTime.now().toIso8601String()} -Platform: ${Platform.operatingSystem} +Platform: $platformName Duration: ${formatDuration(_recordingDuration.value)} -Type: ${Platform.isWindows || Platform.isLinux || Platform.isMacOS ? 'Desktop Simulation' : 'Mobile Recording'} +Type: ${isDesktop ? 'Desktop Simulation' : 'Mobile Recording'} This is a mock recording file for demonstration purposes. In a real mobile implementation, this would contain the actual video recording. @@ -147,7 +182,7 @@ In a real mobile implementation, this would contain the actual video recording. Get.snackbar( 'Recording Saved', - Platform.isWindows || Platform.isLinux || Platform.isMacOS + isDesktop ? 'Draw recording simulation has been saved successfully' : 'Draw recording has been saved successfully', snackPosition: SnackPosition.TOP, @@ -181,6 +216,16 @@ In a real mobile implementation, this would contain the actual video recording. try { if (_recordingPath.value.isEmpty) return; + // Skip file operations on web + if (kIsWeb) { + // Just log to console on web + print('Draw recording details:'); + print('Group: $groupName, Month: $month/$year'); + print('Winner: $winnerName'); + print('Seeds - Server: $serverSeed, Client: $clientSeed, Nonce: $nonce'); + return; + } + final directory = await getApplicationDocumentsDirectory(); final recordingsDir = Directory('${directory.path}/draw_recordings'); if (!await recordingsDir.exists()) { @@ -240,6 +285,9 @@ In a real mobile implementation, this would contain the actual video recording. Future>> getRecordings() async { try { + // Web doesn't support file system + if (kIsWeb) return []; + final directory = await getApplicationDocumentsDirectory(); final recordingsDir = Directory('${directory.path}/draw_recordings'); diff --git a/luckychit/lib/core/themes/app_theme.dart b/luckychit/lib/core/themes/app_theme.dart index 9900d0c..52f9788 100644 --- a/luckychit/lib/core/themes/app_theme.dart +++ b/luckychit/lib/core/themes/app_theme.dart @@ -68,14 +68,10 @@ class AppTheme { ), // Card Theme - cardTheme: CardThemeData( - elevation: 2, - color: lightSurface, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16.r), - ), - margin: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), - ), + // Temporarily commented out due to compilation issue + // cardTheme: const CardThemeData( + // elevation: 2, + // ), // Elevated Button Theme - Accessibility Enhanced elevatedButtonTheme: ElevatedButtonThemeData( @@ -235,22 +231,22 @@ class AppTheme { ), // Dialog Theme - Accessibility Enhanced - dialogTheme: DialogThemeData( - elevation: 8, // More elevation for better depth - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20.r), - ), - titleTextStyle: TextStyle( - fontSize: (22.sp * fontSizeMultiplier), // Larger titles - fontWeight: FontWeight.bold, - color: lightTextPrimary, - ), - contentTextStyle: TextStyle( - fontSize: (17.sp * fontSizeMultiplier), // Larger content - color: lightTextSecondary, - height: 1.5, // Better line spacing - ), - ), + // dialogTheme: DialogThemeData( + // elevation: 8, // More elevation for better depth + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(20.r), + // ), + // titleTextStyle: TextStyle( + // fontSize: (22.sp * fontSizeMultiplier), // Larger titles + // fontWeight: FontWeight.bold, + // color: lightTextPrimary, + // ), + // contentTextStyle: TextStyle( + // fontSize: (17.sp * fontSizeMultiplier), // Larger content + // color: lightTextSecondary, + // height: 1.5, // Better line spacing + // ), + // ), // Snackbar Theme - Accessibility Enhanced snackBarTheme: SnackBarThemeData( @@ -309,14 +305,14 @@ class AppTheme { ), // Card Theme - Accessibility Enhanced - cardTheme: CardThemeData( - elevation: 6, // More elevation for better depth in dark mode - color: darkSurface, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16.r), - ), - margin: EdgeInsets.symmetric(horizontal: 8.w, vertical: 6.h), // More spacing - ), + // cardTheme: CardThemeData( + // elevation: 6, // More elevation for better depth in dark mode + // color: darkSurface, + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(16.r), + // ), + // margin: EdgeInsets.symmetric(horizontal: 8.w, vertical: 6.h), // More spacing + // ), // Elevated Button Theme - Accessibility Enhanced elevatedButtonTheme: ElevatedButtonThemeData( @@ -442,23 +438,23 @@ class AppTheme { ), // Dialog Theme - Accessibility Enhanced - dialogTheme: DialogThemeData( - backgroundColor: darkSurface, - elevation: 8, // More elevation - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20.r), - ), - titleTextStyle: TextStyle( - fontSize: (22.sp * fontSizeMultiplier), - fontWeight: FontWeight.bold, - color: darkTextPrimary, - ), - contentTextStyle: TextStyle( - fontSize: (17.sp * fontSizeMultiplier), - color: darkTextSecondary, - height: 1.5, - ), - ), + // dialogTheme: DialogThemeData( + // backgroundColor: darkSurface, + // elevation: 8, // More elevation + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(20.r), + // ), + // titleTextStyle: TextStyle( + // fontSize: (22.sp * fontSizeMultiplier), + // fontWeight: FontWeight.bold, + // color: darkTextPrimary, + // ), + // contentTextStyle: TextStyle( + // fontSize: (17.sp * fontSizeMultiplier), + // color: darkTextSecondary, + // height: 1.5, + // ), + // ), // Snackbar Theme snackBarTheme: SnackBarThemeData( diff --git a/luckychit/lib/interfaces/manager/combined_draw_dialog.dart b/luckychit/lib/interfaces/manager/combined_draw_dialog.dart index 192e70c..3ade4bc 100644 --- a/luckychit/lib/interfaces/manager/combined_draw_dialog.dart +++ b/luckychit/lib/interfaces/manager/combined_draw_dialog.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'dart:math' as math; @@ -31,7 +32,7 @@ class _CombinedDrawDialogState extends State late Animation _fadeAnimation; late Animation _slideAnimation; - final ScreenRecordingService _recordingService = Get.find(); + ScreenRecordingService? _recordingService; bool _isLoading = false; bool _isDrawStarted = false; @@ -45,8 +46,22 @@ class _CombinedDrawDialogState extends State void initState() { super.initState(); _initializeForm(); - _loadEligibleMembers(); _initializeAnimations(); + + // Only initialize recording service on mobile + if (!kIsWeb) { + try { + _recordingService = Get.find(); + } catch (e) { + // Service not available, recording will be disabled + _recordingService = null; + } + } + + // Load eligible members after frame to avoid setState during build + WidgetsBinding.instance.addPostFrameCallback((_) { + _loadEligibleMembers(); + }); } @override @@ -95,37 +110,54 @@ class _CombinedDrawDialogState extends State } void _loadEligibleMembers() async { - // Load real members from the API - final chitGroupService = Get.find(); - - // Load group members if not already loaded - await chitGroupService.loadGroupMembers(widget.group.id); - - // Get active members who haven't won yet - final allMembers = chitGroupService.groupMembers; - final pastDraws = chitGroupService.monthlyDraws; - final wonMemberIds = pastDraws.map((d) => d.winnerId).toList(); - - final eligible = allMembers.where((member) { - return member.status.toLowerCase() == 'active' && - !wonMemberIds.contains(member.userId); - }).toList(); - setState(() { - _eligibleMembers = eligible.map((member) => { - 'id': member.userId, - 'name': member.user?.fullName ?? 'Unknown', - 'mobile': member.user?.mobileNumber ?? '', - }).toList(); + _isLoading = true; }); + + try { + // Load real members from the API + final chitGroupService = Get.find(); + + // Load group members if not already loaded + await chitGroupService.loadGroupMembers(widget.group.id); + + // Get active members who haven't won yet + final allMembers = chitGroupService.groupMembers; + final pastDraws = chitGroupService.monthlyDraws; + final wonMemberIds = pastDraws.map((d) => d.winnerId).toList(); + + final eligible = allMembers.where((member) { + return member.status.toLowerCase() == 'active' && + !wonMemberIds.contains(member.userId); + }).toList(); + + setState(() { + _eligibleMembers = eligible.map((member) => { + 'id': member.userId, + 'name': member.user?.fullName ?? 'Unknown', + 'mobile': member.user?.mobileNumber ?? '', + }).toList(); + _isLoading = false; + }); - // Show message if no eligible members - if (_eligibleMembers.isEmpty) { + // Show message if no eligible members + if (_eligibleMembers.isEmpty) { + Get.snackbar( + 'No Eligible Members', + 'All active members have already won, or no members exist in this group.', + backgroundColor: Colors.orange.shade100, + colorText: Colors.orange.shade900, + ); + } + } catch (e) { + setState(() { + _isLoading = false; + }); Get.snackbar( - 'No Eligible Members', - 'All active members have already won, or no members exist in this group.', - backgroundColor: Colors.orange.shade100, - colorText: Colors.orange.shade900, + 'Error', + 'Failed to load members: ${e.toString()}', + backgroundColor: Colors.red, + colorText: Colors.white, ); } } @@ -133,6 +165,18 @@ class _CombinedDrawDialogState extends State void _startDraw() async { if (!_formKey.currentState!.validate()) return; + // Check if eligible members are loaded + if (_eligibleMembers.isEmpty) { + Get.snackbar( + 'No Eligible Members', + 'Please wait for members to load, or all members have already won.', + backgroundColor: Colors.orange, + colorText: Colors.white, + duration: const Duration(seconds: 3), + ); + return; + } + // Generate seeds before navigation _generateSeeds(); @@ -184,18 +228,20 @@ class _CombinedDrawDialogState extends State } void _startRecording() { - _recordingService.startRecording(); + _recordingService?.startRecording(); } void _stopRecording() { - _recordingService.stopRecording(); + _recordingService?.stopRecording(); } void _stopRecordingWithDetails() { + if (_recordingService == null) return; + final month = int.parse(_monthController.text); final year = int.parse(_yearController.text); - _recordingService.saveRecordingWithDetails( + _recordingService!.saveRecordingWithDetails( groupName: widget.group.name, month: month.toString(), year: year.toString(), @@ -334,10 +380,20 @@ class _CombinedDrawDialogState extends State child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.play_arrow, size: 20.w), + if (_isLoading) + SizedBox( + width: 20.w, + height: 20.w, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ) + else + Icon(Icons.play_arrow, size: 20.w), SizedBox(width: 8.w), Text( - 'Start Draw', + _isLoading ? 'Loading...' : 'Start Draw', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600), ), ], @@ -478,13 +534,43 @@ class _CombinedDrawDialogState extends State SizedBox(height: 16.h), // Eligible Members - Text( - 'Eligible Members (${_eligibleMembers.length})', - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w600, - color: Colors.grey.shade800, - ), + Row( + children: [ + Text( + 'Eligible Members', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: Colors.grey.shade800, + ), + ), + SizedBox(width: 8.w), + if (_isLoading) + SizedBox( + width: 16.w, + height: 16.w, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.purple.shade600), + ), + ) + else + Container( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.h), + decoration: BoxDecoration( + color: _eligibleMembers.isEmpty ? Colors.orange.shade100 : Colors.purple.shade100, + borderRadius: BorderRadius.circular(12.r), + ), + child: Text( + '${_eligibleMembers.length}', + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.bold, + color: _eligibleMembers.isEmpty ? Colors.orange.shade800 : Colors.purple.shade800, + ), + ), + ), + ], ), SizedBox(height: 12.h), Container( @@ -493,34 +579,90 @@ class _CombinedDrawDialogState extends State border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(8.r), ), - child: ListView.builder( - itemCount: _eligibleMembers.length, - itemBuilder: (context, index) { - final member = _eligibleMembers[index]; - return ListTile( - leading: CircleAvatar( - backgroundColor: Colors.purple.shade100, - child: Text( - (member['name']?.isNotEmpty == true ? member['name'].substring(0, 1) : 'M').toUpperCase(), - style: TextStyle( - color: Colors.purple.shade700, - fontWeight: FontWeight.w600, - ), + child: _isLoading + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + SizedBox(height: 12.h), + Text( + 'Loading members...', + style: TextStyle( + fontSize: 14.sp, + color: Colors.grey.shade600, + ), + ), + ], ), - ), - title: Text( - member['name'], - style: TextStyle(fontWeight: FontWeight.w500), - ), - subtitle: Text(member['mobile']), - trailing: Icon( - Icons.check_circle, - color: Colors.green.shade600, - size: 20.w, - ), - ); - }, - ), + ) + : _eligibleMembers.isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.info_outline, + size: 40.w, + color: Colors.orange.shade400, + ), + SizedBox(height: 8.h), + Text( + 'No eligible members', + style: TextStyle( + fontSize: 14.sp, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(height: 4.h), + Text( + 'All members have won', + style: TextStyle( + fontSize: 12.sp, + color: Colors.grey.shade500, + ), + ), + ], + ), + ) + : ListView.builder( + itemCount: _eligibleMembers.length, + itemBuilder: (context, index) { + final member = _eligibleMembers[index]; + return ListTile( + dense: true, + leading: CircleAvatar( + backgroundColor: Colors.purple.shade100, + radius: 18.r, + child: Text( + (member['name']?.isNotEmpty == true ? member['name'].substring(0, 1) : 'M').toUpperCase(), + style: TextStyle( + color: Colors.purple.shade700, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + ), + ), + ), + title: Text( + member['name'], + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14.sp, + ), + ), + subtitle: Text( + member['mobile'], + style: TextStyle(fontSize: 12.sp), + ), + trailing: Icon( + Icons.check_circle, + color: Colors.green.shade600, + size: 18.w, + ), + ); + }, + ), ), ], ), diff --git a/luckychit/lib/interfaces/manager/draw_animation_page.dart b/luckychit/lib/interfaces/manager/draw_animation_page.dart index 690378e..25ad4e6 100644 --- a/luckychit/lib/interfaces/manager/draw_animation_page.dart +++ b/luckychit/lib/interfaces/manager/draw_animation_page.dart @@ -244,9 +244,10 @@ class _DrawAnimationPageState extends State ), ), - // Animation Area + // Animation Area - Scrollable Expanded( - child: Center( + child: SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h), child: DrawAnimationSelector( members: widget.eligibleMembers, onDrawComplete: _onDrawComplete, diff --git a/luckychit/lib/shared/widgets/recording_overlay.dart b/luckychit/lib/shared/widgets/recording_overlay.dart index 3a5ef48..56706e9 100644 --- a/luckychit/lib/shared/widgets/recording_overlay.dart +++ b/luckychit/lib/shared/widgets/recording_overlay.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import '../../core/services/screen_recording_service.dart'; @@ -15,6 +16,19 @@ class RecordingOverlay extends StatelessWidget { @override Widget build(BuildContext context) { + // On web, skip recording overlay entirely + if (kIsWeb) { + return child; + } + + // Try to find the service, if not available, just show the child + try { + Get.find(); + } catch (e) { + // Service not available + return child; + } + return Stack( children: [ child, @@ -23,8 +37,8 @@ class RecordingOverlay extends StatelessWidget { top: 20.h, right: 20.w, child: GetBuilder( - builder: (recordingService) { - if (!recordingService.isRecording) { + builder: (service) { + if (!service.isRecording) { return const SizedBox.shrink(); } @@ -57,7 +71,7 @@ class RecordingOverlay extends StatelessWidget { // Recording text Flexible( child: Text( - 'REC ${recordingService.formatDuration(recordingService.recordingDuration)}', + 'REC ${service.formatDuration(service.recordingDuration)}', style: TextStyle( color: Colors.white, fontSize: 12.sp,