import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:jwt_decode/jwt_decode.dart'; import 'package:nhance_app_pwa/customAppBar/customAppBar.dart'; import 'package:nhance_app_pwa/pages/postEnrollment/service/api_service.dart'; import 'package:nhance_app_pwa/pages/postEnrollment/service/svg_service.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:http/http.dart' as http; import 'package:video_player/video_player.dart'; import '../../customAppBar/customFooter.dart'; import '../../customAppBar/responsive.dart'; import '../../customAppBar/tabs.dart'; import '../../customAppBar/toastHelper.dart'; import '../helpers/custom_download_snackbar.dart'; import '../helpers/ecard_download_notification_service.dart'; import '../helpers/ecard_download_service.dart'; import '../service/SessionManager.dart'; import '../service/TokenService.dart'; import '../service/multi_video_player.dart'; import '../service/popup_helper.dart'; import '../helpers/aligned_html_content.dart'; import 'package:nhance_app_pwa/logger.dart'; class claimprocess extends StatefulWidget { const claimprocess({Key? key}) : super(key: key); @override State createState() => _claimprocessState(); } class _claimprocessState extends State { late ApiService apiService; bool isLoading = false; bool isActive = true; dynamic _token; dynamic empCodeString; dynamic empPrimaryId; dynamic client_id; dynamic mobileNo; dynamic client_branch_id; dynamic emailId; List> policyList = []; bool _isDownloadingClaimForm = false; final EcardDownloadService _ecardDownloadService = const EcardDownloadService(); dynamic cashLessClaimsDetails; dynamic reimbursementClaimsDetails; dynamic cashLessSectionName; dynamic cashLessHeading; late Map cashLessContent; late Map cashLessNotes; dynamic reimbursementSectionName; dynamic reimbursementHeading; late Map reimbursementContent; late Map reimbursementNotes; final session = SessionManager(); String? cashLessContentHtml; String? cashLessNotesHtml; String? reimbursementContentHtml; String? reimbursementNotesHtml; List multiVideoUrlsList = []; final PageController _videoPageController = PageController(); int _currentVideoIndex = 0; final List _videoControllers = []; @override void initState() { super.initState(); apiService = ApiService(context); // Initialize ApiService here _loadToken(); } @override void dispose() { for (final controller in _videoControllers) { controller.dispose(); } _videoPageController.dispose(); super.dispose(); } Future _loadToken() async { logDebug('_loadToken'); final String? token = await TokenService.getPostToken(); if (token != null && token.isNotEmpty) { // Decode the JWT token received from the API response Map? decodedToken = Jwt.parseJwt(token); logDebug(decodedToken); empCodeString = session.empCodeString; logDebug(empCodeString); // Check if emp_code is correct empPrimaryId = session.empPrimaryId; client_id = session.client_id; mobileNo = session.mobileNo; client_branch_id = session.empClientBranchId; emailId = session.empEmailCorporate ?? ''; logDebug(client_id); getCashlessAndReimbursement(); getActiveAndInactivePolicyDetails(); } } Future getActiveAndInactivePolicyDetails() async { if (client_id == null || empCodeString == null) { return; } try { final responseActive = await apiService.getActiveAndInactivePolicyDetails( client_id!, empCodeString!, 'Active', client_branch_id, mobileNo, emailId); final responseInactive = await apiService.getActiveAndInactivePolicyDetails( client_id!, empCodeString!, 'Inactive', client_branch_id, mobileNo, emailId); final bool isActiveSuccess = responseActive['status'] == 'success' && responseActive['data'] != null; final bool isInactiveSuccess = responseInactive['status'] == 'success' && responseInactive['data'] != null; final List> activeData = isActiveSuccess ? List>.from(responseActive['data']) : []; final List> inactiveData = isInactiveSuccess ? List>.from(responseInactive['data']) : []; final retailPolicyDetails = List.from(responseActive['retail_policy_data'] ?? []); final filteredRetail = retailPolicyDetails .where((item) => item.containsKey('policy_transaction_id')) .toList(); final combinedPolicies = [...activeData, ...inactiveData, ...filteredRetail]; if (mounted) { setState(() { policyList = combinedPolicies .where((policy) => policy['client_policy_id'] != null && policy['client_policy_id'].toString().trim().isNotEmpty) .map((policy) => Map.from(policy)) .toList(); }); } } catch (e) { logDebug('Error fetching policies for claim form: $e'); } } String _policyDropdownLabel(Map policy) { final name = policy['policy_name'] ?? policy['heading'] ?? ''; final no = policy['policy_no'] ?? ''; if (name.toString().isNotEmpty && no.toString().isNotEmpty) { return '$name - $no'; } return name.toString().isNotEmpty ? name.toString() : no.toString(); } bool _isApiSuccess(Map response) { final status = response['status']; return status == true || status == 'success'; } String? _extractDownloadUrl(dynamic data) { if (data == null) return null; if (data is Map && data['download_url'] != null) { final url = data['download_url'].toString().trim(); if (url.isNotEmpty) return url; } if (data is String && data.trim().isNotEmpty) return data.trim(); return null; } void _showInsurerClaimFormPopup() { if (policyList.isEmpty) { ToastHelper.showErrorToast(context, 'No policies available'); return; } String? selectedClientPolicyId = policyList.first['client_policy_id']?.toString(); showDialog( context: context, barrierDismissible: true, builder: (dialogContext) { return StatefulBuilder( builder: (context, setDialogState) { return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), title: Text( 'Insurer Claim Form', style: GoogleFonts.poppins( fontSize: Responsive.isDesktop(context) ? 20 : 18, fontWeight: FontWeight.w600, color: const Color(0xFF000000), ), ), content: SizedBox( width: Responsive.isDesktop(context) ? 420 : double.maxFinite, child: DropdownButtonFormField( value: selectedClientPolicyId, decoration: InputDecoration( labelText: 'Select Policy', border: const OutlineInputBorder(), labelStyle: GoogleFonts.poppins( fontSize: 14, color: const Color(0xFF636363), ), ), items: policyList.map((policy) { final clientPolicyId = policy['client_policy_id']?.toString() ?? ''; return DropdownMenuItem( value: clientPolicyId, child: Text( _policyDropdownLabel(policy), style: GoogleFonts.poppins( fontSize: 14, color: const Color(0xFF000000), ), ), ); }).toList(), onChanged: (value) { setDialogState(() { selectedClientPolicyId = value; }); }, ), ), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext), child: Text( 'Cancel', style: GoogleFonts.poppins( color: const Color(0xFF636363), fontWeight: FontWeight.w500, ), ), ), TextButton( onPressed: _isDownloadingClaimForm || selectedClientPolicyId == null || selectedClientPolicyId!.isEmpty ? null : () async { await _downloadInsurerClaimForm( selectedClientPolicyId!, dialogContext, ); }, child: Text( 'Download', style: GoogleFonts.poppins( color: const Color(0xFFE26728), fontWeight: FontWeight.w600, ), ), ), ], ); }, ); }, ); } Future _downloadInsurerClaimForm( String clientPolicyId, BuildContext dialogContext, ) async { if (_isDownloadingClaimForm) return; setState(() => _isDownloadingClaimForm = true); try { final response = await apiService.getInsurerClaimFormDownloadUrl(clientPolicyId); if (!mounted) return; if (!_isApiSuccess(response)) { ToastHelper.showErrorToast( context, response['message']?.toString() ?? 'Unable to download claim form', ); return; } final downloadUrl = _extractDownloadUrl(response['data']); if (downloadUrl == null || downloadUrl.isEmpty) { ToastHelper.showErrorToast( context, response['message']?.toString() ?? 'Claim form is not available', ); return; } final successMessage = response['message']?.toString() ?? 'Claim form downloaded successfully'; final selectedPolicy = policyList.firstWhere( (policy) => policy['client_policy_id']?.toString() == clientPolicyId, orElse: () => {}, ); final policyNo = selectedPolicy['policy_no']?.toString(); final safePolicyNo = policyNo?.replaceAll(RegExp(r'[^\w\-.]'), '_') ?? clientPolicyId; final fileName = 'claim_form_$safePolicyNo.pdf'; if (!kIsWeb) { CustomDownloadSnackbar.show( context, message: 'Downloading claim form...', ); } final token = await TokenService.getPostToken(); final result = await _ecardDownloadService.downloadEcard( url: downloadUrl, fileName: fileName, headers: { 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', if (token != null && token.isNotEmpty) 'Authorization': 'Bearer $token', }, ); if (!mounted) return; if (result.success) { if (dialogContext.mounted) { Navigator.pop(dialogContext); } ToastHelper.showSuccessToast(context, successMessage); if (!kIsWeb && result.savedPath != null && result.savedPath!.isNotEmpty) { await EcardDownloadNotificationService.showDownloadCompleted( filePath: result.savedPath!, fileName: fileName, ); } } else { ToastHelper.showErrorToast( context, result.message ?? 'Could not download claim form', ); } } catch (e) { logDebug('Claim form download error: $e'); if (mounted) { ToastHelper.showErrorToast( context, 'Unable to download claim form. Please try again.', ); } } finally { if (mounted) { setState(() => _isDownloadingClaimForm = false); } } } Widget _buildInsurerClaimFormButton(BuildContext context) { return MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( onTap: _showInsurerClaimFormPopup, child: Card( elevation: 0, shape: RoundedRectangleBorder( side: const BorderSide( color: Color(0xFFD9D9D9), width: 1.0, ), borderRadius: BorderRadius.circular(8.0), ), child: Container( decoration: const BoxDecoration( color: Colors.white, ), padding: Responsive.isDesktop(context) ? const EdgeInsets.symmetric( vertical: 15, horizontal: 15) : const EdgeInsets.symmetric( vertical: 20, horizontal: 10), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( flex: 11, child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ SvgPicture.string( SvgService.getSvg('general'), width: 25, height: 25, ), const SizedBox(width: 10), Text( 'Insurer Claim Form', textAlign: TextAlign.start, style: GoogleFonts.poppins( fontSize: Responsive.isDesktop(context) ? 18 : 12, fontWeight: FontWeight.w400, color: const Color(0xFF404040), ), ), ], ), ), const Expanded( flex: 1, child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Icon( Icons.chevron_right, color: Color(0xFFE26728), size: 20, ), ], ), ), ], ), ), ), ), ); } Future getCashlessAndReimbursement() async { setState(() { isLoading = true; }); final response = await apiService.getCashlessAndReimbursementToApi(); logDebug('check 1 getCashlessAndReimbursement'); if (response['status'] == 'success') { logDebug(response['data']); setState(() { cashLessClaimsDetails = response['data'][0]; cashLessSectionName = cashLessClaimsDetails['content_section']; cashLessHeading = cashLessClaimsDetails['heading']; cashLessContentHtml = cashLessClaimsDetails['content']; cashLessNotesHtml = cashLessClaimsDetails['notes']; reimbursementClaimsDetails = response['data'][1]; reimbursementSectionName = reimbursementClaimsDetails['content_section']; reimbursementHeading = reimbursementClaimsDetails['heading']; reimbursementContentHtml = reimbursementClaimsDetails['content']; reimbursementNotesHtml = reimbursementClaimsDetails['notes']; final List dataList = response['data']; multiVideoUrlsList.clear(); for (final item in dataList) { // ✅ FIND VIDEO SECTION if (item['content_section'] == 'Video') { final String? videoUrl = item['content']; // ✅ SAFETY CHECK if (videoUrl != null && videoUrl.trim().isNotEmpty) { multiVideoUrlsList.add(videoUrl.trim()); } } } /// ✅ DIRECT VIDEO FILE PATHS (MP4) // multiVideoUrlsList = [ // "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", // "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4", // "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4", // ]; isLoading = false; }); await _initVideoControllers(); } else { logDebug('API request failed with status: ${response['status']}'); } } Future _initVideoControllers() async { // ✅ Clear old controllers for (final controller in _videoControllers) { controller.dispose(); } _videoControllers.clear(); for (final url in multiVideoUrlsList) { final controller = VideoPlayerController.networkUrl(Uri.parse(url)); await controller.initialize(); controller.setLooping(true); controller.setVolume(1); _videoControllers.add(controller); } if (mounted) { setState(() {}); } } ThemeData _buildPageTheme(BuildContext context) { final isDesktop = Responsive.isDesktop(context); final baseTheme = Theme.of(context); return baseTheme.copyWith( textTheme: GoogleFonts.poppinsTextTheme(baseTheme.textTheme).copyWith( titleLarge: GoogleFonts.poppins( fontSize: isDesktop ? 18 : 16, fontWeight: FontWeight.w600, color: const Color(0xFF000000), ), titleMedium: GoogleFonts.poppins( fontSize: isDesktop ? 18 : 16, fontWeight: FontWeight.w500, color: const Color(0xFF000000), ), bodyLarge: GoogleFonts.poppins( fontSize: isDesktop ? 16 : 14, fontWeight: FontWeight.w400, color: const Color(0xFF000000), height: 1.5, ), labelLarge: GoogleFonts.poppins( fontSize: isDesktop ? 18 : 14, fontWeight: FontWeight.w500, color: const Color(0xFF593AFF), ), labelMedium: GoogleFonts.poppins( fontSize: isDesktop ? 18 : 14, fontWeight: FontWeight.w400, color: const Color(0xFF636363), ), ), ); } Widget _buildHtmlContent(BuildContext context, String html) { final bodyStyle = Theme.of(context).textTheme.bodyLarge ?? GoogleFonts.poppins(fontSize: 14, color: const Color(0xFF000000)); return AlignedHtmlContent( html: html, bodyStyle: bodyStyle, markerWidth: Responsive.isDesktop(context) ? 36 : 32, ); } Widget _buildTabLabel({ required BuildContext context, required String label, required bool selected, required VoidCallback onTap, }) { final isDesktop = Responsive.isDesktop(context); final horizontalPadding = isDesktop ? 80.0 : 16.0; final verticalPadding = isDesktop ? 12.0 : 10.0; return GestureDetector( onTap: onTap, child: selected ? Material( elevation: 5, borderRadius: BorderRadius.circular(10), color: Colors.white, child: TextButton( onPressed: onTap, style: TextButton.styleFrom( padding: EdgeInsets.symmetric( horizontal: horizontalPadding, vertical: verticalPadding, ), ), child: Text( label, textAlign: TextAlign.center, style: Theme.of(context).textTheme.labelLarge, ), ), ) : Container( width: double.infinity, alignment: Alignment.center, padding: EdgeInsets.symmetric( horizontal: horizontalPadding, vertical: verticalPadding, ), child: Text( label, textAlign: TextAlign.center, style: Theme.of(context).textTheme.labelMedium, ), ), ); } @override Widget build(BuildContext context) { return PopScope( canPop: false, onPopInvokedWithResult: (didPop, result) { if (didPop) return; // context.go('/claims'); context.pop(); }, child: Scaffold( backgroundColor: Colors.white, appBar: CustomAppBar(), body: Theme( data: _buildPageTheme(context), child: Stack( children: [ SingleChildScrollView( child: Container( padding: Responsive.isDesktop(context) ? EdgeInsets.symmetric( horizontal: MediaQuery.of(context).size.width * 0.2, vertical: MediaQuery.of(context).size.height * 0.03, ) : const EdgeInsets.fromLTRB(16, 8, 16, 24), color: Colors.white, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: Responsive.isDesktop(context) ? EdgeInsets.symmetric(vertical: 15, horizontal: 25) : EdgeInsets.all(0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Expanded( flex: 12, child: InkWell( onTap: () { // context.pop(); // context.go('/claims'); }, child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ // if (!Responsive.isDesktop(context)) Icon( Icons.chevron_left, color: Color(0xFF000000), size: 30, ), SizedBox( width: Responsive.isDesktop(context) ? 0 : 5), Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Claim Process', textAlign: TextAlign.start, style: Theme.of(context) .textTheme .titleLarge, ), ], ), ], ), ), ), ], ), ], ), ), SizedBox(height: 5), if (multiVideoUrlsList.isNotEmpty) ...[ NhanceMultiVideoWrapper( videoUrls: multiVideoUrlsList, ) ], SizedBox(height: 15), Container( decoration: BoxDecoration( color: Color(0xFFF6FAFF), borderRadius: BorderRadius.circular(5), ), padding: Responsive.isDesktop(context) ? EdgeInsets.symmetric(vertical: 15, horizontal: 25) : const EdgeInsets.all(8), child: Row( children: [ Expanded( child: _buildTabLabel( context: context, label: cashLessSectionName ?? '', selected: isActive, onTap: () => setState(() => isActive = true), ), ), const SizedBox(width: 8), Expanded( child: _buildTabLabel( context: context, label: reimbursementSectionName ?? '', selected: !isActive, onTap: () => setState(() => isActive = false), ), ), ], ), ), SizedBox(height: 15), if (isActive) Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(5), ), padding: Responsive.isDesktop(context) ? const EdgeInsets.symmetric( vertical: 10, horizontal: 10) : EdgeInsets.zero, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (cashLessContentHtml != null) Padding( padding: const EdgeInsets.only(bottom: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( cashLessHeading ?? '', textAlign: TextAlign.start, style: Theme.of(context) .textTheme .titleMedium, ), const SizedBox(height: 8), _buildHtmlContent( context, cashLessContentHtml!, ), ] ) ), if (cashLessNotesHtml != null) Padding( padding: const EdgeInsets.only(bottom: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Notes', textAlign: TextAlign.start, style: Theme.of(context) .textTheme .titleMedium, ), const SizedBox(height: 8), _buildHtmlContent( context, cashLessNotesHtml!, ), ], ), ) ]), ) else Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(5), ), padding: Responsive.isDesktop(context) ? const EdgeInsets.symmetric( vertical: 10, horizontal: 10) : EdgeInsets.zero, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (reimbursementContentHtml != null) Padding( padding: const EdgeInsets.only(bottom: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( reimbursementHeading ?? '', textAlign: TextAlign.start, style: Theme.of(context) .textTheme .titleMedium, ), const SizedBox(height: 8), _buildHtmlContent( context, reimbursementContentHtml!, ), ] ) ), if (reimbursementNotesHtml != null) Padding( padding: const EdgeInsets.only(bottom: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Notes', textAlign: TextAlign.start, style: Theme.of(context) .textTheme .titleMedium, ), const SizedBox(height: 8), _buildHtmlContent( context, reimbursementNotesHtml!, ), ] ) ), ]), ), _buildInsurerClaimFormButton(context), SizedBox(height: Responsive.isDesktop(context) ? 40 : 10), ], ), ), ), if (isLoading) Container( color: Color(0x98FFFCE5), // Semi-transparent background child: Center( child: // Your GIF loader widget Image.asset( height: 60, width: 60, 'assets/nhance-loader.gif'), // Adjust path to your GIF loader ), ), if (Responsive.isDesktop(context)) Align( alignment: Alignment.bottomCenter, child: Container( width: double.infinity, child: CustomFooter(), ), ), ], ), ), // floatingActionButton: Responsive.isDesktop(context) // ? null // : FloatingActionButton( // onPressed: () { // Navigator.pushNamed(context, 'chatbot'); // }, // child: Icon(Icons.chat), // ), floatingActionButtonLocation: Responsive.isDesktop(context) ? null : FloatingActionButtonLocation.miniEndFloat, bottomNavigationBar: Responsive.isDesktop(context) ? null : CustomBottomNavigationBar( onTabChanged: (index) { // Add your navigation logic here // repositionBotman(); if (index == 0) { context.push('/home'); } else if (index == 1) { context.push('/claims'); } else if (index == 2) { context.push('/faqs'); } else if (index == 3) { context.push('/profile'); } else if (index == 4) { context.push('/help'); } // else if (index == 4) { // // context.push('/wellness'); // // Wellness tab clicked → show popup // if(!isRetailLoggedIn) // PopupHelper.showRedirectPopup( // context: context, // apiService: apiService, // empPrimaryId: session.empPrimaryId, // ); // } }, icons: [ Icons.home_outlined, Icons.sticky_note_2_outlined, Icons.question_answer_outlined, Icons.person_outline_outlined, Icons.headset_mic_outlined, ], labels: ["Home", "Claims", "FAQs", "Profile","Help"], initialIndex: 0, // Initial index of the bottom navigation bar ), )); } }