import 'dart:developer'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:onufitness/constants/api_enum_constant.dart'; import 'package:onufitness/constants/asset_constants.dart'; import 'package:onufitness/constants/color_constant.dart'; import 'package:onufitness/constants/string_constant.dart'; import 'package:onufitness/controller/get_agora_token_controller.dart'; import 'package:onufitness/screens/accounts/views/coach_account/coach_my_account_screen.dart'; import 'package:onufitness/screens/accounts/views/trainee_account/trainee_account.dart'; import 'package:onufitness/screens/chat/controllers/chat_controller.dart'; import 'package:onufitness/screens/goals/screens/goal_screen.dart'; import 'package:onufitness/screens/echoboard/controllers/echoboard_controller.dart'; import 'package:onufitness/screens/echoboard/views/echoboard_view_screen.dart'; import 'package:onufitness/screens/home/view/home_screen.dart'; import 'package:onufitness/services/agora/agora_chat_service.dart'; import 'package:onufitness/services/agora/call_services.dart'; import 'package:onufitness/services/local_storage_services/shared_services.dart'; import 'package:onufitness/services/notification_services/notification_service.dart'; import 'package:onufitness/widgets/others/mvp_stage_popup.dart'; import 'package:permission_handler/permission_handler.dart'; class NavigationController extends GetxController { var currentIndex = 0.obs; void changeIndex(int index) { if (index >= 0 && index < 4) { currentIndex.value = index; } else { currentIndex.value = 0; } } } class DashboardScreen extends StatefulWidget { const DashboardScreen({super.key}); @override State createState() => _DashboardScreenState(); } class _DashboardScreenState extends State with WidgetsBindingObserver { final notificationService = Get.find(); final AgoraTokenController agoraTokenController = Get.put( AgoraTokenController(), permanent: true, ); final ChatController agoraChatController = Get.put( ChatController(), permanent: true, ); @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addPostFrameCallback((_) async { // Request all permissions sequentially await requestAllPermissionsSequentially(); agoraChatTokenAPIcall(); // runs in background, no need to await // Show MVP popup once per day on first open if (SharedServices.shouldShowMvpPopup()) { await SharedServices.markMvpPopupShownToday(); if (mounted) MvpStagePopup.show(); } }); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); if (state == AppLifecycleState.resumed) { log("App resumed – re-checking Agora login..."); agoraChatTokenAPIcall(); } else if (state == AppLifecycleState.paused) { log("App paused – you can handle disconnects here if needed"); } } /// Request all permissions sequentially to ensure all popups appear Future requestAllPermissionsSequentially() async { try { log("Starting sequential permission requests..."); await WidgetsBinding.instance.endOfFrame; // Add a small initial delay to ensure UI is ready await Future.delayed(const Duration(milliseconds: 500)); // 1. Request Notification permission first log("Requesting notification permission..."); await notificationService.requestPermissions(); // Small delay between permissions await Future.delayed(const Duration(milliseconds: 300)); // 2. Request Microphone permission log("Requesting microphone permission..."); await requestMicrophonePermission(); // Small delay between permissions await Future.delayed(const Duration(milliseconds: 300)); // 3. Request Camera permission log("Requesting camera permission..."); await requestCameraPermission(); log("All permission requests completed"); } catch (e, st) { log("Error requesting permissions: $e", stackTrace: st); } } void _showPermissionDialog({ required String title, required IconData icon, required String message, required String steps, }) async { final result = await Get.dialog( AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), title: Row( children: [ Icon(icon, color: Colors.orange, size: 24), const SizedBox(width: 8), Text( title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), ], ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(message, style: const TextStyle(fontSize: 16)), const SizedBox(height: 16), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'How to enable:', style: TextStyle(fontWeight: FontWeight.w600, fontSize: 14), ), const SizedBox(height: 4), Text(steps, style: const TextStyle(fontSize: 13)), ], ), ), ], ), actions: [ TextButton( onPressed: () => Get.back(result: false), child: const Text( 'Maybe Later', style: TextStyle(color: Colors.grey), ), ), ElevatedButton( onPressed: () => Get.back(result: true), style: ElevatedButton.styleFrom( backgroundColor: const Color(primaryColor), foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: const Text( 'Open Settings', style: TextStyle(color: Colors.black), ), ), ], ), barrierDismissible: false, ); if (result == true) { await openAppSettings(); } } // Request microphone permission Future requestMicrophonePermission() async { try { final status = await Permission.microphone.status; log("Microphone permission: $status"); if (status.isDenied) { final result = await Permission.microphone.request(); log("Microphone permission result: $result"); if (result.isPermanentlyDenied) { _showPermissionDialog( title: 'Microphone Permission', icon: Icons.mic_off, message: 'To make voice and video calls with your trainer, please enable microphone access in your device settings.', steps: Platform.isIOS ? '1. Go to Settings\n2. Find OnuFitness\n3. Tap Microphone\n4. Enable Microphone Access' : '1. Go to Settings\n2. Find Apps\n3. Select OnuFitness\n4. Enable Microphone', ); } } else if (status.isPermanentlyDenied) { _showPermissionDialog( title: 'Microphone Permission', icon: Icons.mic_off, message: 'To make voice and video calls with your trainer, please enable microphone access in your device settings.', steps: Platform.isIOS ? '1. Go to Settings\n2. Find OnuFitness\n3. Tap Microphone\n4. Enable Microphone Access' : '1. Go to Settings\n2. Find Apps\n3. Select OnuFitness\n4. Enable Microphone', ); } } catch (e) { log("Error requesting microphone permission: $e"); } } // Request camera permission Future requestCameraPermission() async { try { final status = await Permission.camera.status; log("Camera permission: $status"); if (status.isDenied) { final result = await Permission.camera.request(); log("Camera permission result: $result"); if (result.isPermanentlyDenied) { _showPermissionDialog( title: 'Camera Permission', icon: Icons.videocam_off, message: 'To use video calls and join live fitness sessions, please enable camera access in your device settings.', steps: Platform.isIOS ? '1. Go to Settings\n2. Find OnuFitness\n3. Tap Camera\n4. Enable Camera Access' : '1. Go to Settings\n2. Find Apps\n3. Select OnuFitness\n4. Enable Camera', ); } } else if (status.isPermanentlyDenied) { _showPermissionDialog( title: 'Camera Permission', icon: Icons.videocam_off, message: 'To use video calls and join live fitness sessions, please enable camera access in your device settings.', steps: Platform.isIOS ? '1. Go to Settings\n2. Find OnuFitness\n3. Tap Camera\n4. Enable Camera Access' : '1. Go to Settings\n2. Find Apps\n3. Select OnuFitness\n4. Enable Camera', ); } } catch (e) { log("Error requesting camera permission: $e"); } } Future agoraChatTokenAPIcall() async { try { if (!Get.isRegistered()) { Get.put(AgoraTokenController()); } final agoraTokenController = Get.find(); final success = await agoraTokenController.getAgoraUserAndRrmToken(); if (success) { log("Agora token fetched successfully, proceeding to login..."); await agoraChatCallServicesLogin(); } else { log("Agora token fetch failed."); } } catch (e, st) { log("Error in agoraChatTokenAPIcall: $e", stackTrace: st); } } Future agoraChatCallServicesLogin() async { // Agora Chat Service Initialization await chatLoginToAgora(); //Small delay await Future.delayed(Duration(milliseconds: 500)); // Agora Call Service Initialization await initializeCallAgora(); } chatLoginToAgora() async { if (!Get.isRegistered()) { Get.put(await AgoraChatService().init(), permanent: true); } else { final chatService = Get.find(); await chatService.initChatSdk(); await chatService.loginToAgora(); } } initializeCallAgora() async { // No need to re-initialize, just ensure it's available if (!Get.isRegistered()) { await Get.putAsync(() async { return await AgoraCallService().init(); }, permanent: true); } else { Get.find().agoraRTMlogin(); } } final NavigationController navController = Get.put( NavigationController(), permanent: true, ); final EchoBoardController echoBoardController = Get.put( EchoBoardController(), permanent: true, ); final List pages = [ FitnessDashboard(), EchoBoardViewScreen(), GoalScreen(), SharedServices.getLoginDetails()?.data?.userRole == ApiEnum.traineeUserRole ? TraineeMyAccountScreen() : CoachMyAccountScreen(), ]; @override Widget build(BuildContext context) { WidgetsBinding.instance.addPostFrameCallback((_) { final int? initialIndex = Get.arguments; if (initialIndex != null && initialIndex != navController.currentIndex.value) { navController.changeIndex(initialIndex); } }); return LayoutBuilder( builder: (context, constraints) { if (constraints.maxWidth >= 600) { return navBarLayout(context); } else { return navBarLayout(context); } }, ); } Widget navBarLayout(BuildContext context) { return AnnotatedRegion( value: SystemUiOverlayStyle( systemNavigationBarColor: Colors.white, statusBarColor: Colors.white, systemNavigationBarIconBrightness: Brightness.dark, statusBarIconBrightness: Brightness.dark, ), child: Obx( () => Scaffold( // ✅ Add this to ensure proper system UI styling extendBody: true, extendBodyBehindAppBar: true, body: SafeArea(child: pages[navController.currentIndex.value]), bottomNavigationBar: Stack( children: [ Container( decoration: BoxDecoration( borderRadius: BorderRadius.only( topRight: Radius.circular(30), topLeft: Radius.circular(30), ), boxShadow: [ BoxShadow( color: Colors.black38, spreadRadius: 0, blurRadius: 10, ), ], ), child: ClipRRect( borderRadius: const BorderRadius.only( topRight: Radius.circular(24), topLeft: Radius.circular(24), ), child: BottomNavigationBar( type: BottomNavigationBarType.fixed, currentIndex: navController.currentIndex.value, onTap: navController.changeIndex, selectedItemColor: Color.fromRGBO(79, 84, 104, 1), unselectedItemColor: Color.fromRGBO(79, 84, 104, 1), showUnselectedLabels: true, backgroundColor: Colors.white, items: [ BottomNavigationBarItem( icon: SizedBox( height: 25.h, width: 25.w, child: Image.asset(AssetConstants.home), ), label: navHomeText, ), BottomNavigationBarItem( icon: SizedBox( height: 25.h, width: 25.w, child: Image.asset( AssetConstants.echoBoard, fit: BoxFit.contain, ), ), label: navEchoboardText, ), BottomNavigationBarItem( icon: SizedBox( height: 25.h, width: 25.w, child: Image.asset( AssetConstants.goal, fit: BoxFit.contain, ), ), label: navGoalText, ), BottomNavigationBarItem( icon: SizedBox( height: 25.h, width: 25.w, child: Image.asset( AssetConstants.account, fit: BoxFit.contain, ), ), label: navAccountText, ), ], ), ), ), Positioned( left: MediaQuery.of(context).size.width / 4 * navController.currentIndex.value + MediaQuery.of(context).size.width / 8 - 15, child: Container( width: 30, height: 4, color: Color(primaryColor), ), ), ], ), ), ), ); } // Widget navBarLayout(BuildContext context) { // return Obx( // () => Scaffold( // body: pages[navController.currentIndex.value], // bottomNavigationBar: Stack( // children: [ // Container( // decoration: BoxDecoration( // borderRadius: BorderRadius.only( // topRight: Radius.circular(30), // topLeft: Radius.circular(30), // ), // boxShadow: [ // BoxShadow( // color: Colors.black38, // spreadRadius: 0, // blurRadius: 10, // ), // ], // ), // child: ClipRRect( // borderRadius: const BorderRadius.only( // topRight: Radius.circular(24), // topLeft: Radius.circular(24), // ), // child: BottomNavigationBar( // type: BottomNavigationBarType.fixed, // currentIndex: navController.currentIndex.value, // onTap: navController.changeIndex, // selectedItemColor: Color.fromRGBO(79, 84, 104, 1), // unselectedItemColor: Color.fromRGBO(79, 84, 104, 1), // showUnselectedLabels: true, // backgroundColor: Colors.white, // items: [ // BottomNavigationBarItem( // icon: SizedBox( // height: 25.h, // width: 25.w, // child: Image.asset(AssetConstants.home), // ), // label: navHomeText, // ), // BottomNavigationBarItem( // icon: SizedBox( // height: 25.h, // width: 25.w, // child: Image.asset( // AssetConstants.echoBoard, // fit: BoxFit.contain, // ), // ), // label: navEchoboardText, // ), // BottomNavigationBarItem( // icon: SizedBox( // height: 25.h, // width: 25.w, // child: Image.asset( // AssetConstants.goal, // fit: BoxFit.contain, // ), // ), // label: navGoalText, // ), // BottomNavigationBarItem( // icon: SizedBox( // height: 25.h, // width: 25.w, // child: Image.asset( // AssetConstants.account, // fit: BoxFit.contain, // ), // ), // label: navAccountText, // ), // ], // ), // ), // ), // Positioned( // left: // MediaQuery.of(context).size.width / // 4 * // navController.currentIndex.value + // MediaQuery.of(context).size.width / 8 - // 15, // child: Container( // width: 30, // height: 4, // color: Color(primaryColor), // ), // ), // ], // ), // ), // ); // } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } }