import 'package:flutter/material.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/pages/postEnrollment/service/api_service.dart'; import '../../customAppBar/customAppBar.dart'; import '../../customAppBar/customFooter.dart'; import '../../customAppBar/responsive.dart'; import '../../customAppBar/tabs.dart'; import '../../customAppBar/toastHelper.dart'; import '../service/SessionManager.dart'; import '../service/TokenService.dart'; import '../service/data_manager.dart'; import 'package:flutter_html/flutter_html.dart'; import '../service/video_player_widget.dart'; import 'package:nhance_app_pwa/logger.dart'; class faqs extends StatefulWidget { const faqs({super.key}); @override State createState() => _faqsState(); } class FaqItem { final String id; final String category; final String question; final String? answer; FaqItem({ required this.id, required this.category, required this.question, this.answer, }); } class FaqNode { final String title; final List children = []; final List faqs = []; FaqNode({required this.title}); } class _faqsState extends State { bool isLoading = false; late ApiService apiService; dynamic empCodeString; dynamic empPrimaryId; dynamic empEmailID; dynamic client_id; dynamic client_branch_id; dynamic empMobileNo; final session = SessionManager(); final dataManager = DataManager(); /// ---------------- STATE ---------------- Map faqTree = {}; String selectedTab = 'Others'; String? openSection; String? openSubSection; String? openQuestionId; /// ---------------- SAMPLE DATA (Replace with API) ---------------- @override void initState() { super.initState(); apiService = ApiService(context); _loadToken(); } @override void 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); empMobileNo = session.mobileNo ?? ''; empEmailID = session.empEmailCorporate ?? ''; client_branch_id = session.empClientBranchId; empCodeString = session.empCodeString; logDebug(empCodeString); // Check if emp_code is correct empPrimaryId = session.empPrimaryId; client_id = session.client_id; logDebug(client_id); getFAQsDetails(); } } Future getFAQsDetails() async { setState(() => isLoading = true); try { final response = await apiService.getFAQsApiData(); if (response['status'] == 'success') { final List list = response['data']['faq_list'] ?? []; // ✅ Convert API data → FaqItem list final List apiFaqs = list.where((e) => e['is_active'] == '1').map((e) { return FaqItem( id: e['id'].toString(), category: e['category']?.toString().trim() ?? '', question: e['question']?.toString() ?? '', answer: e['answer']?.toString(), ); }).toList(); // ✅ Build tree final tree = buildFaqTree(apiFaqs); setState(() { faqTree = tree; // ✅ Set default tab safely if (faqTree.isNotEmpty) { selectedTab = faqTree.keys.first; } }); } else { ToastHelper.showErrorToast(context, 'No FAQs found'); } } catch (e) { logDebug('FAQ Error: $e'); ToastHelper.showErrorToast(context, 'Failed to load FAQs'); } finally { if (mounted) { setState(() => isLoading = false); } } } /// ---------------- TREE BUILDER ---------------- Map buildFaqTree(List faqs) { final Map root = {}; for (var faq in faqs) { final parts = faq.category.split('/').map((e) => e.trim()).toList(); final main = parts.first; root.putIfAbsent(main, () => FaqNode(title: main)); FaqNode current = root[main]!; for (int i = 1; i < parts.length; i++) { final part = parts[i]; final existing = current.children.where((e) => e.title == part).toList(); if (existing.isEmpty) { final node = FaqNode(title: part); current.children.add(node); current = node; } else { current = existing.first; } } current.faqs.add(faq); } return root; } TextStyle _faqSectionTitleStyle(BuildContext context) { return GoogleFonts.poppins( fontSize: Responsive.isDesktop(context) ? 16 : 15, fontWeight: FontWeight.w600, color: const Color(0xFF000000), ); } TextStyle _faqSubSectionTitleStyle(BuildContext context) { return GoogleFonts.poppins( fontSize: Responsive.isDesktop(context) ? 15 : 14, fontWeight: FontWeight.w500, color: const Color(0xFF000000), ); } TextStyle _faqQuestionStyle(BuildContext context, {bool isOpen = false}) { return GoogleFonts.poppins( fontSize: Responsive.isDesktop(context) ? 14 : 13, fontWeight: isOpen ? FontWeight.w600 : FontWeight.w500, color: const Color(0xFF000000), ); } Widget _buildFaqsHeader(BuildContext context) { return InkWell( onTap: () => context.pop(), borderRadius: BorderRadius.circular(8), child: Row( children: [ const Icon( Icons.chevron_left, color: Color(0xFF000000), size: 30, ), const SizedBox(width: 5), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'FAQs', style: GoogleFonts.poppins( fontSize: Responsive.isDesktop(context) ? 20 : 16, fontWeight: FontWeight.w600, color: const Color(0xFF000000), ), ), Text( 'Frequently Asked Questions', style: GoogleFonts.poppins( fontSize: Responsive.isDesktop(context) ? 13 : 12, fontWeight: FontWeight.w400, color: const Color(0xFF777777), ), ), ], ), ], ), ); } Widget _buildTabsContainer(BuildContext context) { return Container( width: double.infinity, padding: EdgeInsets.all(Responsive.isDesktop(context) ? 12 : 10), decoration: BoxDecoration( color: const Color(0xFFECF2FF), borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFFD9D9D9)), ), child: Responsive.isDesktop(context) ? buildTabs() : SingleChildScrollView( scrollDirection: Axis.horizontal, child: buildTabs(), ), ); } Widget _buildFaqListCard(BuildContext context, Widget child) { const radius = 12.0; return Container( width: double.infinity, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(radius), border: Border.all(color: const Color(0xFFD9D9D9)), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.04), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(radius), child: child, ), ); } BorderRadius? _faqItemBorderRadius({ required bool isFirst, required bool isLast, }) { if (isFirst && isLast) { return BorderRadius.circular(11); } if (isFirst) { return const BorderRadius.vertical(top: Radius.circular(11)); } if (isLast) { return const BorderRadius.vertical(bottom: Radius.circular(11)); } return null; } Widget _buildExpandableHeader({ required BuildContext context, required String title, required bool isOpen, required VoidCallback onTap, required TextStyle titleStyle, double horizontalPadding = 16, Color? backgroundColor, bool isFirst = false, bool isLast = false, bool showBottomBorder = true, }) { final borderRadius = _faqItemBorderRadius( isFirst: isFirst, isLast: isLast && !isOpen, ); return Material( color: backgroundColor ?? Colors.white, borderRadius: borderRadius, clipBehavior: Clip.antiAlias, child: InkWell( onTap: onTap, borderRadius: borderRadius, child: Container( padding: EdgeInsets.symmetric( horizontal: horizontalPadding, vertical: 14, ), decoration: BoxDecoration( color: backgroundColor, border: showBottomBorder && !(isLast && !isOpen) ? const Border( bottom: BorderSide(color: Color(0xFFE8E8E8)), ) : null, ), child: Row( children: [ Expanded( child: Text( title, style: titleStyle, ), ), AnimatedRotation( turns: isOpen ? 0.5 : 0, duration: const Duration(milliseconds: 450), curve: Curves.easeInOutCubic, child: const Icon( Icons.keyboard_arrow_down, color: Color(0xFFE26728), ), ), ], ), ), ), ); } @override Widget build(BuildContext context) { return PopScope( canPop: false, onPopInvokedWithResult: (didPop, result) { if (didPop) return; context.pop(); }, child: Scaffold( backgroundColor: Colors.white, appBar: CustomAppBar(), body: Stack(children: [ SingleChildScrollView( child: Container( padding: Responsive.isDesktop(context) ? EdgeInsets.symmetric( horizontal: MediaQuery.of(context).size.width * 0.1, // 30% of screen width as horizontal padding vertical: MediaQuery.of(context).size.height * 0.03, // 5% of screen height as vertical padding ) : EdgeInsets.all(10), color: Colors.white, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildFaqsHeader(context), const SizedBox(height: 16), _buildTabsContainer(context), const SizedBox(height: 16), _buildFaqListCard(context, buildContent()), SizedBox(height: Responsive.isDesktop(context) ? 40 : 80), ], ), )), 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, // Make the footer full width 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: 2, // Initial index of the bottom navigation bar ), )); } /// ---------------- MAIN TABS ---------------- Widget buildTabs() { final bool isDesktop = Responsive.isDesktop(context); return Row( crossAxisAlignment: CrossAxisAlignment.center, children: faqTree.keys.map((tab) { final active = selectedTab == tab; final tabWidget = GestureDetector( onTap: () { setState(() { selectedTab = tab; openSection = null; openSubSection = null; openQuestionId = null; }); }, child: active ? Material( elevation: 4, borderRadius: BorderRadius.circular(10), color: Colors.white, child: Container( alignment: Alignment.center, padding: EdgeInsets.symmetric( horizontal: isDesktop ? 24 : 16, vertical: isDesktop ? 12 : 10, ), child: Text( tab, textAlign: TextAlign.center, style: GoogleFonts.poppins( fontSize: isDesktop ? 15 : 13, color: const Color(0xFF000000), fontWeight: FontWeight.w600, ), ), ), ) : Container( alignment: Alignment.center, padding: EdgeInsets.symmetric( horizontal: isDesktop ? 24 : 16, vertical: isDesktop ? 12 : 10, ), child: Text( tab, textAlign: TextAlign.center, style: GoogleFonts.poppins( fontSize: isDesktop ? 15 : 13, color: const Color(0xFF636363), fontWeight: FontWeight.w500, ), ), ), ); if (isDesktop) { return Expanded( child: Padding( padding: const EdgeInsets.only(right: 8), child: tabWidget, ), ); } return Padding( padding: const EdgeInsets.only(right: 8), child: SizedBox( width: 100, child: tabWidget, ), ); }).toList(), ); } /// ---------------- CONTENT ---------------- Widget buildContent() { final node = faqTree[selectedTab]; if (node == null) { return const Center(child: Text('No FAQs')); } // Health / Travel without hierarchy if (node.children.isEmpty) { final faqs = node.faqs; return Column( children: [ for (int i = 0; i < faqs.length; i++) buildQuestion( faqs[i], isFirst: i == 0, isLast: i == faqs.length - 1, ), ], ); } // Others → Fire Insurance → General final sections = node.children; return Column( children: [ for (int i = 0; i < sections.length; i++) buildSection( sections[i], isFirst: i == 0, isLast: i == sections.length - 1, ), ], ); } /// ---------------- SECTION ---------------- Widget buildSection( FaqNode node, { bool isFirst = false, bool isLast = false, }) { final bool isOpen = openSection == node.title; const Duration kExpandDuration = Duration(milliseconds: 450); const Curve kExpandCurve = Curves.easeInOutCubic; return Column( children: [ _buildExpandableHeader( context: context, title: node.title, isOpen: isOpen, onTap: () { setState(() { openSection = isOpen ? null : node.title; openSubSection = null; openQuestionId = null; }); }, titleStyle: _faqSectionTitleStyle(context), backgroundColor: isOpen ? const Color(0xFFECF2FF) : Colors.white, isFirst: isFirst, isLast: isLast, ), AnimatedSize( duration: kExpandDuration, curve: kExpandCurve, child: isOpen ? Column( children: [ if (node.children.isNotEmpty) ...node.children.asMap().entries.map((entry) { final index = entry.key; final child = entry.value; return buildSubSection( child, isLast: isLast && index == node.children.length - 1, ); }), if (node.children.isEmpty) ...node.faqs.asMap().entries.map((entry) { final index = entry.key; final faq = entry.value; return buildQuestion( faq, isLast: isLast && index == node.faqs.length - 1, ); }), ], ) : const SizedBox.shrink(), ), ], ); } /// ---------------- SUB SECTION ---------------- Widget buildSubSection( FaqNode node, { bool isLast = false, }) { final bool isOpen = openSubSection == node.title; const Duration kExpandDuration = Duration(milliseconds: 450); const Curve kExpandCurve = Curves.easeInOutCubic; return Column( children: [ _buildExpandableHeader( context: context, title: node.title, isOpen: isOpen, onTap: () { setState(() { openSubSection = isOpen ? null : node.title; openQuestionId = null; }); }, titleStyle: _faqSubSectionTitleStyle(context), horizontalPadding: 20, backgroundColor: isOpen ? const Color(0xFFF6FAFF) : Colors.white, isLast: isLast, ), AnimatedSize( duration: kExpandDuration, curve: kExpandCurve, child: isOpen ? Column( children: [ for (int i = 0; i < node.faqs.length; i++) buildQuestion( node.faqs[i], isLast: isLast && i == node.faqs.length - 1, ), ], ) : const SizedBox.shrink(), ), ], ); } /// ---------------- QUESTION ---------------- Widget buildQuestion( FaqItem faq, { bool isFirst = false, bool isLast = false, }) { final bool isOpen = openQuestionId == faq.id; const Duration kExpandDuration = Duration(milliseconds: 450); const Curve kExpandCurve = Curves.easeInOutCubic; final borderRadius = _faqItemBorderRadius( isFirst: isFirst, isLast: isLast && !isOpen, ); return Column( children: [ Material( color: Colors.white, borderRadius: borderRadius, clipBehavior: Clip.antiAlias, child: InkWell( onTap: () { setState(() { openQuestionId = isOpen ? null : faq.id; }); }, borderRadius: borderRadius, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( border: isLast && !isOpen ? null : const Border( bottom: BorderSide(color: Color(0xFFE8E8E8)), ), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.only(top: 2), child: Icon( Icons.help_outline, size: 18, color: isOpen ? const Color(0xFFE26728) : const Color(0xFF999999), ), ), const SizedBox(width: 10), Expanded( child: Text( faq.question, style: _faqQuestionStyle(context, isOpen: isOpen), ), ), AnimatedRotation( turns: isOpen ? 0.5 : 0, duration: kExpandDuration, curve: kExpandCurve, child: const Icon( Icons.keyboard_arrow_down, size: 20, color: Color(0xFFE26728), ), ), ], ), ), ), ), AnimatedSize( duration: kExpandDuration, curve: kExpandCurve, child: isOpen ? Container( width: double.infinity, padding: const EdgeInsets.fromLTRB(16, 12, 16, 16), decoration: BoxDecoration( color: const Color(0xFFFAFAFA), borderRadius: isLast ? const BorderRadius.vertical( bottom: Radius.circular(11), ) : null, ), child: Html( data: faq.answer ?? '', style: { "body": Style( margin: Margins.zero, padding: HtmlPaddings.zero, fontSize: FontSize( Responsive.isDesktop(context) ? 14 : 13, ), fontFamily: GoogleFonts.poppins().fontFamily, color: const Color(0xFF444444), lineHeight: LineHeight(1.5), ), "table": Style( border: Border.all(color: Colors.grey.shade300), ), "td": Style( padding: HtmlPaddings.all(8), border: Border.all(color: Colors.grey.shade300), ), }, ), ) : const SizedBox.shrink(), ), ], ); } /// ---------------- ARROW ---------------- Widget buildArrow(bool open) { return AnimatedRotation( turns: open ? 0.5 : 0, duration: const Duration(milliseconds: 300), child: const Icon( Icons.keyboard_arrow_down, color: Colors.deepOrange, ), ); } Widget NhanceVideoWrapper(BuildContext context) { final video = NetworkVideoPlayer( videoUrl: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", ); if (Responsive.isDesktop(context)) { return Center( child: SizedBox( width: 500, height: 250, child: video, ), ); } /// 📱 Mobile → default behavior return video; } }