post_enrollment_app/lib/pages/postEnrollment/faqs.dart
2026-07-06 09:55:31 +05:30

833 lines
25 KiB
Dart
Executable File

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<faqs> 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<FaqNode> children = [];
final List<FaqItem> faqs = [];
FaqNode({required this.title});
}
class _faqsState extends State<faqs> {
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<String, FaqNode> 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<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);
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<void> 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<FaqItem> apiFaqs =
list.where((e) => e['is_active'] == '1').map<FaqItem>((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<String, FaqNode> buildFaqTree(List<FaqItem> faqs) {
final Map<String, FaqNode> 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;
}
}