onufitness_mobile/lib/screens/navbar/bottom_nav_bar.dart
2026-06-29 12:28:16 +05:30

576 lines
20 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<DashboardScreen> createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen>
with WidgetsBindingObserver {
final notificationService = Get.find<NotificationService>();
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<void> 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<bool>(
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<void> 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<void> 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<void> agoraChatTokenAPIcall() async {
try {
if (!Get.isRegistered<AgoraTokenController>()) {
Get.put(AgoraTokenController());
}
final agoraTokenController = Get.find<AgoraTokenController>();
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<void> 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<AgoraChatService>()) {
Get.put(await AgoraChatService().init(), permanent: true);
} else {
final chatService = Get.find<AgoraChatService>();
await chatService.initChatSdk();
await chatService.loginToAgora();
}
}
initializeCallAgora() async {
// No need to re-initialize, just ensure it's available
if (!Get.isRegistered<AgoraCallService>()) {
await Get.putAsync<AgoraCallService>(() async {
return await AgoraCallService().init();
}, permanent: true);
} else {
Get.find<AgoraCallService>().agoraRTMlogin();
}
}
final NavigationController navController = Get.put(
NavigationController(),
permanent: true,
);
final EchoBoardController echoBoardController = Get.put(
EchoBoardController(),
permanent: true,
);
final List<Widget> 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<SystemUiOverlayStyle>(
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();
}
}