post_enrollment_app/lib/pages/postEnrollment/claimprocess.dart

949 lines
37 KiB
Dart
Executable File

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<claimprocess> createState() => _claimprocessState();
}
class _claimprocessState extends State<claimprocess> {
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<Map<String, dynamic>> policyList = [];
bool _isDownloadingClaimForm = false;
final EcardDownloadService _ecardDownloadService = const EcardDownloadService();
dynamic cashLessClaimsDetails;
dynamic reimbursementClaimsDetails;
dynamic cashLessSectionName;
dynamic cashLessHeading;
late Map<String, String> cashLessContent;
late Map<String, String> cashLessNotes;
dynamic reimbursementSectionName;
dynamic reimbursementHeading;
late Map<String, String> reimbursementContent;
late Map<String, String> reimbursementNotes;
final session = SessionManager();
String? cashLessContentHtml;
String? cashLessNotesHtml;
String? reimbursementContentHtml;
String? reimbursementNotesHtml;
List<String> multiVideoUrlsList = [];
final PageController _videoPageController = PageController();
int _currentVideoIndex = 0;
final List<VideoPlayerController> _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<void> _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<String, dynamic>? 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<void> 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<Map<String, dynamic>> activeData = isActiveSuccess
? List<Map<String, dynamic>>.from(responseActive['data'])
: [];
final List<Map<String, dynamic>> inactiveData = isInactiveSuccess
? List<Map<String, dynamic>>.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<String, dynamic>.from(policy))
.toList();
});
}
} catch (e) {
logDebug('Error fetching policies for claim form: $e');
}
}
String _policyDropdownLabel(Map<String, dynamic> 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<String, dynamic> 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<String>(
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<String>(
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<void> _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<void> 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<dynamic> 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<void> _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
),
));
}
}