This commit is contained in:
Surendiran 2025-12-20 13:51:28 +05:30
parent 6de1ea4a49
commit 45fcefeb81
25 changed files with 2291 additions and 1083 deletions

View File

@ -1,151 +0,0 @@
<!--<!DOCTYPE html>-->
<!--<html lang="en">-->
<!--<head>-->
<!-- <meta charset="UTF-8" />-->
<!-- <style>-->
<!-- #botmanWidgetRoot svg {-->
<!-- display: none !important;-->
<!-- }-->
<!-- </style>-->
<!-- <script>-->
<!-- const APIENDPOINT = 'https://venbait.in/nhance/dev';-->
<!-- const APIAPIENDPOINT = 'https://venbait.in/nhance/dev';-->
<!-- var emp_name = 'Kumar';-->
<!-- var uid = Math.floor(100000 + Math.random() * 900000);-->
<!-- console.log('CURRENT_USER' + uid);-->
<!-- window.botmanWidget = {-->
<!-- chatServer: APIENDPOINT + '/chat',-->
<!-- frameEndpoint: APIAPIENDPOINT + '/widget',-->
<!-- title: 'Ask ILA',-->
<!-- introMessage: '',-->
<!-- bubbleBackground: '#007bff',-->
<!-- mainColor: '#007bff',-->
<!-- placeholderText: 'Type your message here...',-->
<!-- aboutText: 'Insurance Assistant ILA',-->
<!-- enableAttachments: false,-->
<!-- parameters: {-->
<!-- employee_id: '12288',-->
<!-- session_id: 'XYZ789',-->
<!-- origin: 'mobile',-->
<!-- emp_code: 'HTL-007',-->
<!-- client_id: 159,-->
<!-- client_branch_id: 125,-->
<!-- },-->
<!-- };-->
<!-- </script>-->
<!-- <script src="https://cdn.jsdelivr.net/npm/botman-web-widget@0/build/js/widget.js"></script>-->
<!--</head>-->
<!--<body></body>-->
<!--</html>-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ask ILA</title>
<style>
#botmanWidgetRoot svg {
display: none !important;
}
</style>
</head>
<body>
<div id="botmanWidgetRoot"></div>
<!--<script>-->
<!-- function initBotman(params) {-->
<!--&lt;!&ndash; console.log('[Botman] Params:', params);&ndash;&gt;-->
<!-- console.log('[Botman] Received Params:', JSON.stringify(params, null, 2));-->
<!-- window.botmanWidget = {-->
<!-- chatServer: 'https://venbait.in/nhance/dev/chat',-->
<!-- frameEndpoint: 'https://venbait.in/nhance/dev/widget',-->
<!-- title: 'Ask ILA',-->
<!-- introMessage: '',-->
<!-- bubbleBackground: '#007bff',-->
<!-- mainColor: '#007bff',-->
<!-- placeholderText: 'Type your message here...',-->
<!-- aboutText: 'Insurance Assistant ILA',-->
<!-- enableAttachments: false,-->
<!-- parameters: params-->
<!-- };-->
<!-- const script = document.createElement('script');-->
<!-- script.src = "https://cdn.jsdelivr.net/npm/botman-web-widget@0/build/js/widget.js";-->
<!-- script.onload = () => {-->
<!-- const checkWidget = setInterval(() => {-->
<!-- if (typeof botmanChatWidget !== 'undefined') {-->
<!-- botmanChatWidget.open();-->
<!-- botmanChatWidget.sayAsBot(`Hi ${params.emp_name}, ILA here to assist you.`);-->
<!-- clearInterval(checkWidget);-->
<!-- }-->
<!-- }, 300);-->
<!-- };-->
<!-- document.body.appendChild(script);-->
<!-- }-->
<!--</script>-->
<script>
function initBotman(params) {
console.log('Params:', params);
console.log('[Botman] Received Params:', JSON.stringify(params, null, 2));
window.botmanWidget = {
chatServer: 'https://venbait.in/nhance/dev/chat',
frameEndpoint: 'https://venbait.in/nhance/dev/widget',
title: 'Ask ILA',
introMessage: '', // Must be empty if using sayAsBot()
bubbleBackground: '#007bff',
mainColor: '#007bff',
placeholderText: 'Type your message here...',
aboutText: 'Insurance Assistant ILA',
enableAttachments: false,
parameters: params
};
const script = document.createElement('script');
script.src = "https://cdn.jsdelivr.net/npm/botman-web-widget@0/build/js/widget.js";
script.onload = function () {
console.log("✅ Botman script loaded");
const waitForWidget = setInterval(() => {
if (typeof botmanChatWidget !== 'undefined' && botmanChatWidget.open) {
clearInterval(waitForWidget);
botmanChatWidget.open();
// Send default message after slight delay
setTimeout(() => {
botmanChatWidget.sayAsBot(`Hi ${params.emp_name || 'User'}, ILA here to assist you.`);
}, 800); // Allow chat to render
} else {
console.log("⏳ Waiting for botmanChatWidget...");
}
}, 300);
};
script.onerror = function () {
console.error("❌ Failed to load Botman widget.js");
};
document.body.appendChild(script);
}
</script>
<script>
const queryParams = new URLSearchParams(window.location.search);
const entries = Object.fromEntries(queryParams.entries());
document.write('<pre>' + JSON.stringify(entries, null, 2) + '</pre>');
</script>
</body>
</html>

BIN
assets/faqs.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 KiB

View File

@ -117,12 +117,25 @@ class _CustomAppBarState extends State<CustomAppBar> {
context.push('/claims');
}),
),
NavBarItem(
text: "FAQs",
onTap: () => handleMenuTap(() {
context.push('/faqs');
}),
),
NavBarItem(
text: "Profile",
onTap: () => handleMenuTap(() {
context.push('/profile');
}),
),
NavBarItem(
text: "Help",
onTap: () => handleMenuTap(() {
context.push('/help');
}),
),
// NavBarItem(
// text: "Wellness",
// onTap: () => handleMenuTap(() {
@ -133,12 +146,6 @@ class _CustomAppBarState extends State<CustomAppBar> {
// );
// }),
// ),
NavBarItem(
text: "Profile",
onTap: () => handleMenuTap(() {
context.push('/profile');
}),
),
NavBarItem(
text: "Logout",
onTap: () {

View File

@ -14,6 +14,7 @@ import 'package:nhance_app_pwa/pages/postEnrollment/AddPolicyScreen.dart';
import 'package:nhance_app_pwa/pages/postEnrollment/botman_chat.dart';
import 'package:nhance_app_pwa/pages/postEnrollment/claimprocess.dart';
import 'package:nhance_app_pwa/pages/postEnrollment/claims.dart';
import 'package:nhance_app_pwa/pages/postEnrollment/faqs.dart';
import 'package:nhance_app_pwa/pages/postEnrollment/generalexclusionsdeductibles.dart';
import 'package:nhance_app_pwa/pages/postEnrollment/help.dart';
import 'package:nhance_app_pwa/pages/postEnrollment/home.dart';
@ -230,6 +231,10 @@ Future<void> startApp() async {
path: '/empDetails',
builder: (context, state) => empDetails(),
),
GoRoute(
path: '/faqs',
builder: (context, state) => faqs(),
),
GoRoute(
path: '/addOnsDetails', builder: (context, state) => addOnsDetails()),
GoRoute(

View File

@ -1502,15 +1502,15 @@ class _loginState extends State<login> {
value.isEmpty) {
return 'Please enter your password';
}
if (value.length < 6) {
return 'Password must be at least 6 characters';
}
// Optional strong password rule
if (!RegExp(
r'^(?=.*[A-Z])(?=.*[0-9]).{6,}$')
.hasMatch(value)) {
return 'Include at least 1 uppercase letter and 1 number';
}
// if (value.length < 6) {
// return 'Password must be at least 6 characters';
// }
// // Optional strong password rule
// if (!RegExp(
// r'^(?=.*[A-Z])(?=.*[0-9]).{6,}$')
// .hasMatch(value)) {
// return 'Include at least 1 uppercase letter and 1 number';
// }
return null;
},
),

View File

@ -367,17 +367,19 @@ class _AddPolicyScreenState extends State<AddPolicyScreen> {
icons: [
Icons.home_outlined,
Icons.sticky_note_2_outlined,
Icons.question_answer_outlined,
Icons.person_outline_outlined,
Icons.headset_mic_outlined,
Icons.health_and_safety_outlined,
],
labels: ["Home", "Claims", "Profile", "Help", "Wellness"],
initialIndex: 3,
labels: ["Home", "Claims", "FAQs", "Profile", "Help"],
initialIndex: 0,
onTabChanged: (index) {
if (index == 0) context.go('/home');
if (index == 1) context.go('/claims');
if (index == 2) context.go('/profile');
if (index == 3) context.go('/help');
if (index == 2) context.go('/faqs');
if (index == 3) context.go('/profile');
if (index == 4) {context.push('/help');
}
},
),
),

File diff suppressed because it is too large Load Diff

View File

@ -584,7 +584,7 @@ class _claimsState extends State<claims> {
.isDesktop(
context)
? 140
: 50,
: 10 ,
vertical: Responsive
.isDesktop(
context)
@ -593,7 +593,7 @@ class _claimsState extends State<claims> {
// primary: Color(0xFF000000),
),
child: Text(
'Your Plan',
'Register a Claim',
style: GoogleFonts.poppins(
fontSize: Responsive
.isDesktop(
@ -609,7 +609,7 @@ class _claimsState extends State<claims> {
),
)
: Text(
'Your Plan',
'Register a Claim',
style:
GoogleFonts.poppins(
fontSize: Responsive
@ -766,8 +766,10 @@ class _claimsState extends State<claims> {
} else if (index == 1) {
context.push('/claims');
} else if (index == 2) {
context.push('/profile');
context.push('/faqs');
} else if (index == 3) {
context.push('/profile');
} else if (index == 4) {
context.push('/help');
}
// else if (index == 4) {
@ -784,12 +786,12 @@ class _claimsState extends State<claims> {
icons: [
Icons.home_outlined,
Icons.sticky_note_2_outlined,
Icons.question_answer_outlined,
Icons.person_outline_outlined,
Icons.headset_mic_outlined,
// Icons.health_and_safety_outlined,
],
labels: ["Home", "Claims", "Profile", "Help"],
initialIndex: 0, // Initial index of the bottom navigation bar
labels: ["Home", "Claims", "FAQs", "Profile","Help"],
initialIndex: 1, // Initial index of the bottom navigation bar
),
));
}

View File

@ -0,0 +1,692 @@
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';
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 {
print('_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);
print(decodedToken);
empMobileNo = session.mobileNo ?? '';
empEmailID = session.empEmailCorporate ?? '';
client_branch_id = session.empClientBranchId;
empCodeString = session.empCodeString;
print(empCodeString); // Check if emp_code is correct
empPrimaryId = session.empPrimaryId;
client_id = session.client_id;
print(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) {
debugPrint('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;
}
@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(children: [
Container(
padding: Responsive.isDesktop(context)
? EdgeInsets.only(
top: 15, bottom: 15, left: 25, right: 25)
: EdgeInsets.only(top: 0, bottom: 0, left: 0, right: 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
flex: 12,
child: InkWell(
onTap: () {
context.pop();
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Icon(
Icons
.chevron_left, // Replace with your desired icon
color: Color(0xFF000000),
size: 30,
),
SizedBox(
width:
5), // Adjust space between icon and text
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'FAQs',
textAlign: TextAlign.start,
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF000000),
),
),
Text(
'Frequently Asked Questions',
textAlign: TextAlign.start,
style: GoogleFonts.poppins(
fontSize:
12, // Adjust the font size as needed
fontWeight: FontWeight.w400,
color: Color(0xFF000000),
),
),
],
),
],
),
),
),
],
),
Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Container(
width: double.infinity,
decoration: BoxDecoration(
color: Color(0xFFEEF5FF),
// Set background color for the container
borderRadius: BorderRadius.circular(
5), // Set border radius for the container
),
padding: Responsive.isDesktop(context)
? EdgeInsets.only(
top: 10, bottom: 10, left: 25, right: 25)
: EdgeInsets.only(
top: 10, bottom: 10, left: 10, right: 10),
child: Align(
alignment: Alignment.centerLeft, // LEFT ALIGN TABS
child: Responsive.isDesktop(context)
? buildTabs() // no scroll on web
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: buildTabs(),
),
),
),
const SizedBox(height: 20),
buildContent(),
],
),
),
// NhanceVideoWrapper(context)
],
),
),
]),
)),
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 = InkWell(
onTap: () {
setState(() {
selectedTab = tab;
openSection = null;
openSubSection = null;
openQuestionId = null;
});
},
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: active ? Colors.deepOrange : Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: active ? Colors.deepOrange : const Color(0xFFE0E0E0),
),
),
child: Text(
tab,
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
color: active ? Colors.white : Colors.deepOrange,
fontWeight: FontWeight.w600,
),
),
),
);
/// 🔥 DESKTOP FULL WIDTH TAB
if (isDesktop) {
return Expanded(
child: Padding(
padding: const EdgeInsets.only(right: 10),
child: tabWidget,
),
);
}
/// 📱 MOBILE CONTENT WIDTH TAB
return Padding(
padding: const EdgeInsets.only(right: 8),
child: SizedBox(
width: 110,
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) {
return Column(
children: node.faqs.map(buildQuestion).toList(),
);
}
// Others Fire Insurance General
return Column(
children: node.children.map(buildSection).toList(),
);
}
/// ---------------- SECTION ----------------
Widget buildSection(FaqNode node) {
final bool isOpen = openSection == node.title;
const Duration kExpandDuration = Duration(milliseconds: 450);
const Curve kExpandCurve = Curves.easeInOutCubic;
return Column(
children: [
InkWell(
onTap: () {
setState(() {
openSection = isOpen ? null : node.title;
openSubSection = null;
openQuestionId = null;
});
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(color: Color(0xFFE0E0E0)),
),
),
child: Row(
children: [
Expanded(
child: Text(
node.title,
style: GoogleFonts.poppins(
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
),
AnimatedRotation(
turns: isOpen ? 0.5 : 0,
duration: kExpandDuration,
curve: kExpandCurve,
child: const Icon(
Icons.keyboard_arrow_down,
color: Colors.deepOrange,
),
),
],
),
),
),
/// SECTION CONTENT
AnimatedSize(
duration: kExpandDuration,
curve: kExpandCurve,
child: isOpen
? Column(
children: [
if (node.children.isNotEmpty)
...node.children.map(buildSubSection),
if (node.children.isEmpty) ...node.faqs.map(buildQuestion),
],
)
: const SizedBox.shrink(),
),
],
);
}
/// ---------------- SUB SECTION ----------------
Widget buildSubSection(FaqNode node) {
final bool isOpen = openSubSection == node.title;
const Duration kExpandDuration = Duration(milliseconds: 450);
const Curve kExpandCurve = Curves.easeInOutCubic;
return Column(
children: [
InkWell(
onTap: () {
setState(() {
openSubSection = isOpen ? null : node.title;
openQuestionId = null;
});
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(color: Color(0xFFEDEDED)),
),
),
child: Row(
children: [
Expanded(
child: Text(
node.title,
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
),
AnimatedRotation(
turns: isOpen ? 0.5 : 0,
duration: kExpandDuration,
curve: kExpandCurve,
child: const Icon(
Icons.keyboard_arrow_down,
size: 20,
color: Colors.deepOrange,
),
),
],
),
),
),
AnimatedSize(
duration: kExpandDuration,
curve: kExpandCurve,
child: isOpen
? Column(
children: node.faqs.map(buildQuestion).toList(),
)
: const SizedBox.shrink(),
),
],
);
}
/// ---------------- QUESTION ----------------
Widget buildQuestion(FaqItem faq) {
final bool isOpen = openQuestionId == faq.id;
const Duration kExpandDuration = Duration(milliseconds: 450);
const Curve kExpandCurve = Curves.easeInOutCubic;
return Column(
children: [
InkWell(
onTap: () {
setState(() {
openQuestionId = isOpen ? null : faq.id;
});
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(color: Color(0xFFF0F0F0)),
),
),
child: Row(
children: [
Expanded(
child: Text(
faq.question,
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w400,
),
),
),
AnimatedRotation(
turns: isOpen ? 0.5 : 0,
duration: kExpandDuration,
curve: kExpandCurve,
child: const Icon(
Icons.keyboard_arrow_down,
size: 18,
color: Colors.deepOrange,
),
),
],
),
),
),
AnimatedSize(
duration: kExpandDuration,
curve: kExpandCurve,
child: isOpen
? Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(16, 12, 16, 16),
color: const Color(0xFFFAFAFA),
child: Html(
data: faq.answer ?? '',
style: {
"body": Style(
margin: Margins.zero,
padding: HtmlPaddings.zero,
fontSize: FontSize(13),
color: Colors.black87,
),
"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;
}
}

View File

@ -806,7 +806,7 @@ class _generalExclusionsDeductiblesState
: FloatingActionButtonLocation.miniEndFloat,
bottomNavigationBar: Responsive.isDesktop(context)
? null
: CustomBottomNavigationBar(
: CustomBottomNavigationBar(
onTabChanged: (index) {
// Add your navigation logic here
// repositionBotman();
@ -815,8 +815,10 @@ class _generalExclusionsDeductiblesState
} else if (index == 1) {
context.push('/claims');
} else if (index == 2) {
context.push('/profile');
context.push('/faqs');
} else if (index == 3) {
context.push('/profile');
} else if (index == 4) {
context.push('/help');
}
// else if (index == 4) {
@ -833,11 +835,11 @@ class _generalExclusionsDeductiblesState
icons: [
Icons.home_outlined,
Icons.sticky_note_2_outlined,
Icons.question_answer_outlined,
Icons.person_outline_outlined,
Icons.headset_mic_outlined,
// Icons.health_and_safety_outlined,
],
labels: ["Home", "Claims", "Profile", "Help"],
labels: ["Home", "Claims", "FAQs", "Profile","Help"],
initialIndex: 0, // Initial index of the bottom navigation bar
),
));

View File

@ -583,6 +583,90 @@ class _helpState extends State<help> {
),
SizedBox(height: 20),
InkWell(
onTap: () {
context.go('/faqs');
},
child: Container(
width: double.infinity,
height: Responsive.isDesktop(context) ? 140 : 120,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: const Color(0xFF2F80ED),
width: 2,
),
),
child: Row(
children: [
/// LEFT IMAGE BLOCK
Container(
width: Responsive.isDesktop(context) ? 180 : 120,
height: double.infinity,
color: Colors.white,
padding: const EdgeInsets.all(12),
child: Image.asset(
'assets/faqs.jpg',
fit: BoxFit.contain,
),
),
/// RIGHT CONTENT BLOCK
Expanded(
child: Container(
color: const Color(0xFF93C01F),
padding: EdgeInsets.symmetric(
horizontal: Responsive.isDesktop(context) ? 32 : 16,
vertical: Responsive.isDesktop(context) ? 24 : 16,
),
child: Row(
children: [
/// TEXT CONTENT
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'FAQs',
style: GoogleFonts.poppins(
fontSize:
Responsive.isDesktop(context) ? 36 : 20,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
const SizedBox(height: 8),
Text(
'Find answers to commonly asked questions',
style: GoogleFonts.poppins(
fontSize:
Responsive.isDesktop(context) ? 18 : 14,
fontWeight: FontWeight.w400,
color: Colors.white,
),
),
],
),
),
/// RIGHT ARROW
Icon(
Icons.chevron_right,
size: Responsive.isDesktop(context) ? 36 : 28,
color: Colors.white,
),
],
),
),
),
],
),
),
),
// SizedBox(height: 15),
// Container(
// decoration: BoxDecoration(
@ -1045,7 +1129,7 @@ class _helpState extends State<help> {
: FloatingActionButtonLocation.miniEndFloat,
bottomNavigationBar: Responsive.isDesktop(context)
? null
: CustomBottomNavigationBar(
: CustomBottomNavigationBar(
onTabChanged: (index) {
// Add your navigation logic here
// repositionBotman();
@ -1054,8 +1138,10 @@ class _helpState extends State<help> {
} else if (index == 1) {
context.push('/claims');
} else if (index == 2) {
context.push('/profile');
context.push('/faqs');
} else if (index == 3) {
context.push('/profile');
} else if (index == 4) {
context.push('/help');
}
// else if (index == 4) {
@ -1072,12 +1158,12 @@ class _helpState extends State<help> {
icons: [
Icons.home_outlined,
Icons.sticky_note_2_outlined,
Icons.question_answer_outlined,
Icons.person_outline_outlined,
Icons.headset_mic_outlined,
// Icons.health_and_safety_outlined,
],
labels: ["Home", "Claims", "Profile", "Help"],
initialIndex: 0, // Initial index of the bottom navigation bar
labels: ["Home", "Claims", "FAQs", "Profile","Help"],
initialIndex: 4, // Initial index of the bottom navigation bar
),
));
}

View File

@ -276,19 +276,19 @@ class _HomeState extends State<Home> {
emp_name = response['emp_name'];
policyList = List.from(response['data'] ?? []);
retailPolicyDetails = List.from(response['retail_policy_data'] ?? []);
print('policyList $policyList');
pre_policy_count = response['pre_policy_count'] ?? '';
final empNotEnrolledCount = response['emp_not_enrolled_count'] ?? '';
final wellnessStatus = response['wellness_data']['status'] ?? 'failed';
print('wellnessStatus $wellnessStatus');
if (wellnessStatus == 'success') {
wellnessURL = response['wellness_data']['data'];
}
print('policyList $policyList');
pre_policy_count = response['pre_policy_count'] ?? '';
final empNotEnrolledCount = response['emp_not_enrolled_count'] ?? '';
final wellnessStatus = response['wellness_data']['status'] ?? 'failed';
print('wellnessStatus $wellnessStatus');
if (wellnessStatus == 'success') {
wellnessURL = response['wellness_data']['data'];
}
setState(() {
isTokenAvailable = pre_policy_count != 0;
emp_not_enrolled_count = empNotEnrolledCount != 0;
});
setState(() {
isTokenAvailable = pre_policy_count != 0;
emp_not_enrolled_count = empNotEnrolledCount != 0;
});
if (emp_not_enrolled_count) {
final prefs = await SharedPreferences.getInstance();
@ -630,7 +630,7 @@ class _HomeState extends State<Home> {
},
),
SizedBox(height: 15),
if(!isRetailLoggedIn)
if(!isRetailLoggedIn && isTokenAvailable)
Card(
elevation: 2,
color: Color(0xFFDDF3FF),
@ -1189,40 +1189,42 @@ class _HomeState extends State<Home> {
: FloatingActionButtonLocation.miniEndFloat,
bottomNavigationBar: Responsive.isDesktop(context)
? null
: CustomBottomNavigationBar(
onTabChanged: (index) {
// Add your navigation logic here
// repositionBotman();
if (index == 0) {
if(!isRetailLoggedIn) context.push('/home');
} else if (index == 1) {
if(!isRetailLoggedIn) context.push('/claims');
} else if (index == 2) {
if(!isRetailLoggedIn) context.push('/profile');
} else if (index == 3) {
if(!isRetailLoggedIn) 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.person_outline_outlined,
Icons.headset_mic_outlined,
// Icons.health_and_safety_outlined,
],
labels: ["Home", "Claims", "Profile", "Help"],
initialIndex: 0, // Initial index of the bottom navigation bar
),
: 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
),
));
}
@ -1882,6 +1884,11 @@ class _HomeState extends State<Home> {
}
Widget _buildCarouselSlider(List<Widget> cards) {
final int totalCards = cards.length;
final bool hasAddCard = isActive; // because you add AddCard only when isActive
final int displayCardCount =
hasAddCard ? totalCards - 1 : totalCards;
return Stack(
alignment: Alignment.center,
children: [
@ -1897,13 +1904,17 @@ class _HomeState extends State<Home> {
aspectRatio: 2.0,
initialPage: 0,
onPageChanged: (index, reason) {
setState(() => currentIndex = index);
setState(() {
currentIndex = index >= displayCardCount
? displayCardCount - 1
: index;
});
},
),
),
// 🔵 CARD COUNTER (4/10)
if(isActive)
if(isActive && displayCardCount > 0)
Positioned(
bottom: 10,
child: Container(
@ -1913,7 +1924,7 @@ class _HomeState extends State<Home> {
borderRadius: BorderRadius.circular(12),
),
child: Text(
"${currentIndex + 1} / ${cards.length}",
"${(currentIndex + 1).clamp(1, displayCardCount)} / $displayCardCount",
style: TextStyle(
color: Colors.white,
fontSize: 12,
@ -2158,7 +2169,7 @@ class _HomeState extends State<Home> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"OPT & Wellness",
"OPD & Wellness",
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w600,

View File

@ -1455,7 +1455,7 @@ SizedBox(height: 15),
: FloatingActionButtonLocation.miniEndFloat,
bottomNavigationBar: Responsive.isDesktop(context)
? null
: CustomBottomNavigationBar(
: CustomBottomNavigationBar(
onTabChanged: (index) {
// Add your navigation logic here
// repositionBotman();
@ -1464,8 +1464,10 @@ SizedBox(height: 15),
} else if (index == 1) {
context.push('/claims');
} else if (index == 2) {
context.push('/profile');
context.push('/faqs');
} else if (index == 3) {
context.push('/profile');
} else if (index == 4) {
context.push('/help');
}
// else if (index == 4) {
@ -1482,11 +1484,11 @@ SizedBox(height: 15),
icons: [
Icons.home_outlined,
Icons.sticky_note_2_outlined,
Icons.question_answer_outlined,
Icons.person_outline_outlined,
Icons.headset_mic_outlined,
// Icons.health_and_safety_outlined,
],
labels: ["Home", "Claims", "Profile", "Help"],
labels: ["Home", "Claims", "FAQs", "Profile","Help"],
initialIndex: 0, // Initial index of the bottom navigation bar
),
)

View File

@ -1105,7 +1105,7 @@ class _policiesState extends State<policies> {
: FloatingActionButtonLocation.miniEndFloat,
bottomNavigationBar: Responsive.isDesktop(context)
? null
: CustomBottomNavigationBar(
: CustomBottomNavigationBar(
onTabChanged: (index) {
// Add your navigation logic here
// repositionBotman();
@ -1114,8 +1114,10 @@ class _policiesState extends State<policies> {
} else if (index == 1) {
context.push('/claims');
} else if (index == 2) {
context.push('/profile');
context.push('/faqs');
} else if (index == 3) {
context.push('/profile');
} else if (index == 4) {
context.push('/help');
}
// else if (index == 4) {
@ -1132,11 +1134,11 @@ class _policiesState extends State<policies> {
icons: [
Icons.home_outlined,
Icons.sticky_note_2_outlined,
Icons.question_answer_outlined,
Icons.person_outline_outlined,
Icons.headset_mic_outlined,
// Icons.health_and_safety_outlined,
],
labels: ["Home", "Claims", "Profile", "Help"],
labels: ["Home", "Claims", "FAQs", "Profile","Help"],
initialIndex: 0, // Initial index of the bottom navigation bar
),
)

View File

@ -888,7 +888,7 @@ class _profileState extends State<profile> {
: FloatingActionButtonLocation.miniEndFloat,
bottomNavigationBar: Responsive.isDesktop(context)
? null
: CustomBottomNavigationBar(
: CustomBottomNavigationBar(
onTabChanged: (index) {
// Add your navigation logic here
// repositionBotman();
@ -897,8 +897,10 @@ class _profileState extends State<profile> {
} else if (index == 1) {
context.push('/claims');
} else if (index == 2) {
context.push('/profile');
context.push('/faqs');
} else if (index == 3) {
context.push('/profile');
} else if (index == 4) {
context.push('/help');
}
// else if (index == 4) {
@ -915,12 +917,12 @@ class _profileState extends State<profile> {
icons: [
Icons.home_outlined,
Icons.sticky_note_2_outlined,
Icons.question_answer_outlined,
Icons.person_outline_outlined,
Icons.headset_mic_outlined,
// Icons.health_and_safety_outlined,
],
labels: ["Home", "Claims", "Profile", "Help"],
initialIndex: 0, // Initial index of the bottom navigation bar
labels: ["Home", "Claims", "FAQs", "Profile","Help"],
initialIndex: 3, // Initial index of the bottom navigation bar
),
));
}

View File

@ -303,7 +303,7 @@ class _raisedTicketHistoryState extends State<raisedTicketHistory> {
: FloatingActionButtonLocation.miniEndFloat,
bottomNavigationBar: Responsive.isDesktop(context)
? null
: CustomBottomNavigationBar(
: CustomBottomNavigationBar(
onTabChanged: (index) {
// Add your navigation logic here
// repositionBotman();
@ -312,8 +312,10 @@ class _raisedTicketHistoryState extends State<raisedTicketHistory> {
} else if (index == 1) {
context.push('/claims');
} else if (index == 2) {
context.push('/profile');
context.push('/faqs');
} else if (index == 3) {
context.push('/profile');
} else if (index == 4) {
context.push('/help');
}
// else if (index == 4) {
@ -330,11 +332,11 @@ class _raisedTicketHistoryState extends State<raisedTicketHistory> {
icons: [
Icons.home_outlined,
Icons.sticky_note_2_outlined,
Icons.question_answer_outlined,
Icons.person_outline_outlined,
Icons.headset_mic_outlined,
// Icons.health_and_safety_outlined,
],
labels: ["Home", "Claims", "Profile", "Help"],
labels: ["Home", "Claims", "FAQs", "Profile","Help"],
initialIndex: 0, // Initial index of the bottom navigation bar
),
)

View File

@ -468,7 +468,7 @@ class _retailClaimFormsState extends State<retailClaimForm> {
: FloatingActionButtonLocation.miniEndFloat,
bottomNavigationBar: Responsive.isDesktop(context)
? null
: CustomBottomNavigationBar(
: CustomBottomNavigationBar(
onTabChanged: (index) {
// Add your navigation logic here
// repositionBotman();
@ -477,8 +477,10 @@ class _retailClaimFormsState extends State<retailClaimForm> {
} else if (index == 1) {
context.push('/claims');
} else if (index == 2) {
context.push('/profile');
context.push('/faqs');
} else if (index == 3) {
context.push('/profile');
} else if (index == 4) {
context.push('/help');
}
// else if (index == 4) {
@ -495,11 +497,11 @@ class _retailClaimFormsState extends State<retailClaimForm> {
icons: [
Icons.home_outlined,
Icons.sticky_note_2_outlined,
Icons.question_answer_outlined,
Icons.person_outline_outlined,
Icons.headset_mic_outlined,
// Icons.health_and_safety_outlined,
],
labels: ["Home", "Claims", "Profile", "Help"],
labels: ["Home", "Claims", "FAQs", "Profile","Help"],
initialIndex: 0, // Initial index of the bottom navigation bar
),
));

View File

@ -93,6 +93,19 @@ class ApiService {
return response;
}
Future<Map<String, dynamic>> getFAQsApiData() async {
print(_postToken);
if (_postToken == null) {
await _initializeToken();
}
final url = Uri.parse('${Environment.apiUrl}FAQ');
final headers = {
'Authorization': 'Bearer $_postToken' ?? '',
};
final response = await _makeGetRequest(url, headers);
return response;
}
Future<Map<String, dynamic>> getTrackClaimsList(String empPrimaryId,mobileNo,emailID) async {
print(_postToken);
if (_postToken == null) {

View File

@ -603,7 +603,7 @@ class _ticketsState extends State<tickets> {
: FloatingActionButtonLocation.miniEndFloat,
bottomNavigationBar: Responsive.isDesktop(context)
? null
: CustomBottomNavigationBar(
: CustomBottomNavigationBar(
onTabChanged: (index) {
// Add your navigation logic here
// repositionBotman();
@ -612,8 +612,10 @@ class _ticketsState extends State<tickets> {
} else if (index == 1) {
context.push('/claims');
} else if (index == 2) {
context.push('/profile');
context.push('/faqs');
} else if (index == 3) {
context.push('/profile');
} else if (index == 4) {
context.push('/help');
}
// else if (index == 4) {
@ -630,11 +632,11 @@ class _ticketsState extends State<tickets> {
icons: [
Icons.home_outlined,
Icons.sticky_note_2_outlined,
Icons.question_answer_outlined,
Icons.person_outline_outlined,
Icons.headset_mic_outlined,
// Icons.health_and_safety_outlined,
],
labels: ["Home", "Claims", "Profile", "Help"],
labels: ["Home", "Claims", "FAQs", "Profile","Help"],
initialIndex: 0, // Initial index of the bottom navigation bar
),
));

View File

@ -674,7 +674,7 @@ class _tickettracklistState extends State<tickettracklist> {
: FloatingActionButtonLocation.miniEndFloat,
bottomNavigationBar: Responsive.isDesktop(context)
? null
: CustomBottomNavigationBar(
: CustomBottomNavigationBar(
onTabChanged: (index) {
// Add your navigation logic here
// repositionBotman();
@ -683,8 +683,10 @@ class _tickettracklistState extends State<tickettracklist> {
} else if (index == 1) {
context.push('/claims');
} else if (index == 2) {
context.push('/profile');
context.push('/faqs');
} else if (index == 3) {
context.push('/profile');
} else if (index == 4) {
context.push('/help');
}
// else if (index == 4) {
@ -701,11 +703,11 @@ class _tickettracklistState extends State<tickettracklist> {
icons: [
Icons.home_outlined,
Icons.sticky_note_2_outlined,
Icons.question_answer_outlined,
Icons.person_outline_outlined,
Icons.headset_mic_outlined,
// Icons.health_and_safety_outlined,
],
labels: ["Home", "Claims", "Profile", "Help"],
labels: ["Home", "Claims", "FAQs", "Profile","Help"],
initialIndex: 0, // Initial index of the bottom navigation bar
),
)

View File

@ -364,7 +364,7 @@ class _wellnessState extends State<wellness> {
: FloatingActionButtonLocation.miniEndFloat,
bottomNavigationBar: Responsive.isDesktop(context)
? null
: CustomBottomNavigationBar(
: CustomBottomNavigationBar(
onTabChanged: (index) {
// Add your navigation logic here
// repositionBotman();
@ -373,8 +373,10 @@ class _wellnessState extends State<wellness> {
} else if (index == 1) {
context.push('/claims');
} else if (index == 2) {
context.push('/profile');
context.push('/faqs');
} else if (index == 3) {
context.push('/profile');
} else if (index == 4) {
context.push('/help');
}
// else if (index == 4) {
@ -391,11 +393,11 @@ class _wellnessState extends State<wellness> {
icons: [
Icons.home_outlined,
Icons.sticky_note_2_outlined,
Icons.question_answer_outlined,
Icons.person_outline_outlined,
Icons.headset_mic_outlined,
// Icons.health_and_safety_outlined,
],
labels: ["Home", "Claims", "Profile", "Help"],
labels: ["Home", "Claims", "FAQs", "Profile","Help"],
initialIndex: 0, // Initial index of the bottom navigation bar
),
)

View File

@ -0,0 +1,560 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
class NhanceMultiVideoWrapper extends StatefulWidget {
final List<String> videoUrls;
const NhanceMultiVideoWrapper({
super.key,
required this.videoUrls,
});
@override
State<NhanceMultiVideoWrapper> createState() =>
_NhanceMultiVideoWrapperState();
}
class _NhanceMultiVideoWrapperState extends State<NhanceMultiVideoWrapper> {
final PageController _pageController = PageController();
final List<VideoPlayerController> _controllers = [];
int _currentIndex = 0;
bool _isMuted = false;
/// Optional static cover image
final String coverImage =
"https://picsum.photos/900/500"; // replace with API thumbnail if available
@override
void initState() {
super.initState();
_initControllers();
}
Future<void> _initControllers() async {
for (final url in widget.videoUrls) {
final controller = VideoPlayerController.networkUrl(Uri.parse(url));
await controller.initialize();
controller.setLooping(true);
controller.setVolume(1);
_controllers.add(controller);
}
if (mounted) setState(() {});
}
@override
void dispose() {
for (final c in _controllers) {
c.dispose();
}
_pageController.dispose();
super.dispose();
}
void _next() {
if (_currentIndex < _controllers.length - 1) {
_pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
}
}
void _previous() {
if (_currentIndex > 0) {
_pageController.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
}
}
void _seek(VideoPlayerController c, int seconds) {
final pos = c.value.position + Duration(seconds: seconds);
c.seekTo(
pos < Duration.zero ? Duration.zero : pos,
);
}
void _toggleMute(VideoPlayerController c) {
setState(() {
_isMuted = !_isMuted;
c.setVolume(_isMuted ? 0 : 1);
});
}
void _openFullscreen(VideoPlayerController controller) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => Scaffold(
backgroundColor: Colors.black,
body: Center(
child: AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: VideoPlayer(controller),
),
),
),
),
);
}
@override
Widget build(BuildContext context) {
if (_controllers.isEmpty) {
return const SizedBox(
height: 250,
child: Center(child: CircularProgressIndicator()),
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// const Text(
// "Videos",
// style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
// ),
// const SizedBox(height: 10),
SizedBox(
height: kIsWeb ? 340 : 240,
child: Stack(
alignment: Alignment.center,
children: [
PageView.builder(
controller: _pageController,
itemCount: _controllers.length,
onPageChanged: (index) {
setState(() {
_controllers[_currentIndex].pause();
_currentIndex = index;
});
},
itemBuilder: (context, index) {
final controller = _controllers[index];
return Center(
child: SizedBox(
width: kIsWeb ? 520 : double.infinity,
child: AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: Stack(
alignment: Alignment.center,
children: [
/// 🎥 VIDEO
VideoPlayer(controller),
/// 🖼 COVER IMAGE
if (!controller.value.isPlaying)
// Positioned.fill(
// child: Image.network(
// coverImage,
// fit: BoxFit.cover,
// ),
// ),
/// PLAY / PAUSE
IconButton(
iconSize: 64,
icon: Icon(
controller.value.isPlaying
? Icons.pause_circle
: Icons.play_circle,
color: Colors.white,
),
onPressed: () {
setState(() {
controller.value.isPlaying
? controller.pause()
: controller.play();
});
},
),
/// 🎛 BOTTOM CONTROLS
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 6),
color: Colors.black.withOpacity(0.55),
child: Row(
children: [
IconButton(
icon: const Icon(Icons.replay_10,
color: Colors.white),
onPressed: () =>
_seek(controller, -10),
),
IconButton(
icon: const Icon(Icons.forward_10,
color: Colors.white),
onPressed: () =>
_seek(controller, 10),
),
IconButton(
icon: Icon(
_isMuted
? Icons.volume_off
: Icons.volume_up,
color: Colors.white,
),
onPressed: () =>
_toggleMute(controller),
),
Expanded(
child: VideoProgressIndicator(
controller,
allowScrubbing: true,
colors: VideoProgressColors(
playedColor:
const Color(0xFF593AFF),
),
),
),
IconButton(
icon: const Icon(Icons.fullscreen,
color: Colors.white),
onPressed: () =>
_openFullscreen(controller),
),
],
),
),
),
],
),
),
),
);
},
),
/// PREVIOUS (WEB)
if (kIsWeb)
Positioned(
left: 12,
child: _ArrowButton(
icon: Icons.chevron_left,
enabled: _currentIndex > 0,
onTap: _previous,
),
),
/// NEXT (WEB)
if (kIsWeb)
Positioned(
right: 12,
child: _ArrowButton(
icon: Icons.chevron_right,
enabled: _currentIndex < _controllers.length - 1,
onTap: _next,
),
),
],
),
),
const SizedBox(height: 8),
/// 🔘 DOT INDICATOR
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
_controllers.length,
(i) => AnimatedContainer(
duration: const Duration(milliseconds: 250),
margin: const EdgeInsets.symmetric(horizontal: 4),
height: 8,
width: _currentIndex == i ? 20 : 8,
decoration: BoxDecoration(
color: _currentIndex == i
? const Color(0xFF593AFF)
: Colors.grey.shade400,
borderRadius: BorderRadius.circular(4),
),
),
),
),
],
);
}
}
/// 🔘 Arrow Button
class _ArrowButton extends StatelessWidget {
final IconData icon;
final bool enabled;
final VoidCallback onTap;
const _ArrowButton({
required this.icon,
required this.enabled,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return IgnorePointer(
ignoring: !enabled,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 200),
opacity: enabled ? 1 : 0.3,
child: Material(
color: Colors.black.withOpacity(0.6),
shape: const CircleBorder(),
elevation: 4,
child: InkWell(
customBorder: const CircleBorder(),
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(10),
child: Icon(icon, size: 36, color: Colors.white),
),
),
),
),
);
}
}
// import 'package:flutter/foundation.dart';
// import 'package:flutter/material.dart';
// import 'package:video_player/video_player.dart';
//
// class NhanceMultiVideoWrapper extends StatefulWidget {
// final List<String> videoUrls;
//
// const NhanceMultiVideoWrapper({
// super.key,
// required this.videoUrls,
// });
//
// @override
// State<NhanceMultiVideoWrapper> createState() =>
// _NhanceMultiVideoWrapperState();
// }
//
// class _NhanceMultiVideoWrapperState extends State<NhanceMultiVideoWrapper> {
// final PageController _pageController = PageController();
// int _currentIndex = 0;
//
// final List<VideoPlayerController> _controllers = [];
//
// @override
// void initState() {
// super.initState();
// _initControllers();
// }
//
// Future<void> _initControllers() async {
// for (final url in widget.videoUrls) {
// final controller = VideoPlayerController.networkUrl(Uri.parse(url));
// await controller.initialize();
// controller.setLooping(true);
// controller.setVolume(1);
// _controllers.add(controller);
// }
//
// if (mounted) setState(() {});
// }
//
// @override
// void dispose() {
// for (final c in _controllers) {
// c.dispose();
// }
// _pageController.dispose();
// super.dispose();
// }
//
// void _next() {
// if (_currentIndex < widget.videoUrls.length - 1) {
// _pageController.nextPage(
// duration: const Duration(milliseconds: 300),
// curve: Curves.easeInOut,
// );
// }
// }
//
// void _previous() {
// if (_currentIndex > 0) {
// _pageController.previousPage(
// duration: const Duration(milliseconds: 300),
// curve: Curves.easeInOut,
// );
// }
// }
//
// @override
// Widget build(BuildContext context) {
// if (_controllers.isEmpty) {
// return const SizedBox(
// height: 250,
// child: Center(child: CircularProgressIndicator()),
// );
// }
//
// return Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// // Text(
// // "Videos",
// // style: Theme.of(context)
// // .textTheme
// // .titleMedium
// // ?.copyWith(fontWeight: FontWeight.w600),
// // ),
// // const SizedBox(height: 10),
//
// /// 🎥 VIDEO WITH WEB CONTROLS
// SizedBox(
// height: kIsWeb ? 320 : 220,
// child: Stack(
// alignment: Alignment.center,
// children: [
// PageView.builder(
// controller: _pageController,
// itemCount: _controllers.length,
// onPageChanged: (index) {
// setState(() {
// _controllers[_currentIndex].pause();
// _currentIndex = index;
// });
// },
// itemBuilder: (context, index) {
// final controller = _controllers[index];
//
// return Center(
// child: SizedBox(
// width: kIsWeb ? 520 : double.infinity,
// child: AspectRatio(
// aspectRatio: controller.value.aspectRatio,
// child: Stack(
// alignment: Alignment.center,
// children: [
// VideoPlayer(controller),
//
// IconButton(
// iconSize: 60,
// icon: Icon(
// controller.value.isPlaying
// ? Icons.pause_circle
// : Icons.play_circle,
// color: Colors.white,
// ),
// onPressed: () {
// setState(() {
// controller.value.isPlaying
// ? controller.pause()
// : controller.play();
// });
// },
// ),
// ],
// ),
// ),
// ),
// );
// },
// ),
//
// /// PREVIOUS (WEB ONLY)
// if (kIsWeb)
// Positioned(
// left: 16,
// child: _VideoArrowButton(
// icon: Icons.chevron_left,
// enabled: _currentIndex > 0,
// onTap: _previous,
// ),
// ),
//
//
// /// NEXT (WEB ONLY)
// if (kIsWeb)
// Positioned(
// right: 16,
// child: _VideoArrowButton(
// icon: Icons.chevron_right,
// enabled: _currentIndex < _controllers.length - 1,
// onTap: _next,
// ),
// ),
//
// ],
// ),
// ),
//
// const SizedBox(height: 8),
//
// /// 🔘 DOT INDICATOR
// Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: List.generate(
// _controllers.length,
// (index) => AnimatedContainer(
// duration: const Duration(milliseconds: 250),
// margin: const EdgeInsets.symmetric(horizontal: 4),
// height: 8,
// width: _currentIndex == index ? 20 : 8,
// decoration: BoxDecoration(
// color: _currentIndex == index
// ? const Color(0xFF593AFF)
// : Colors.grey.shade400,
// borderRadius: BorderRadius.circular(4),
// ),
// ),
// ),
// ),
// ],
// );
// }
//
//
// }
// class _VideoArrowButton extends StatelessWidget {
// final IconData icon;
// final bool enabled;
// final VoidCallback onTap;
//
// const _VideoArrowButton({
// required this.icon,
// required this.enabled,
// required this.onTap,
// });
//
// @override
// Widget build(BuildContext context) {
// return IgnorePointer(
// ignoring: !enabled,
// child: AnimatedOpacity(
// duration: const Duration(milliseconds: 200),
// opacity: enabled ? 1 : 0.3,
// child: Material(
// color: Colors.black.withOpacity(0.55),
// shape: const CircleBorder(),
// elevation: 4,
// child: InkWell(
// customBorder: const CircleBorder(),
// onTap: onTap,
// child: Padding(
// padding: const EdgeInsets.all(10),
// child: Icon(
// icon,
// size: 36,
// color: Colors.white,
// ),
// ),
// ),
// ),
// ),
// );
// }
// }

View File

@ -0,0 +1,81 @@
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
class NetworkVideoPlayer extends StatefulWidget {
final String videoUrl;
const NetworkVideoPlayer({
super.key,
required this.videoUrl,
});
@override
State<NetworkVideoPlayer> createState() => _NetworkVideoPlayerState();
}
class _NetworkVideoPlayerState extends State<NetworkVideoPlayer> {
late VideoPlayerController _controller;
bool isInitialized = false;
@override
void initState() {
super.initState();
_controller = VideoPlayerController.networkUrl(
Uri.parse(widget.videoUrl),
)..initialize().then((_) {
if (!mounted) return;
setState(() => isInitialized = true);
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: isInitialized
? _controller.value.aspectRatio
: 16 / 9,
child: isInitialized
? Stack(
alignment: Alignment.center,
children: [
VideoPlayer(_controller),
IconButton(
iconSize: 60,
icon: Icon(
_controller.value.isPlaying
? Icons.pause_circle
: Icons.play_circle,
color: Colors.white,
),
onPressed: () {
setState(() {
_controller.value.isPlaying
? _controller.pause()
: _controller.play();
});
},
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: VideoProgressIndicator(
_controller,
allowScrubbing: true,
),
),
],
)
: const Center(child: CircularProgressIndicator()),
);
}
}

View File

@ -0,0 +1,74 @@
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
class NetworkVideoPlayer extends StatefulWidget {
final String videoUrl;
const NetworkVideoPlayer({super.key, required this.videoUrl});
@override
State<NetworkVideoPlayer> createState() => _NetworkVideoPlayerState();
}
class _NetworkVideoPlayerState extends State<NetworkVideoPlayer> {
late VideoPlayerController _controller;
bool isInitialized = false;
@override
void initState() {
super.initState();
_controller = VideoPlayerController.networkUrl(
Uri.parse(widget.videoUrl),
)
..initialize().then((_) {
setState(() {
isInitialized = true;
});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio:
isInitialized ? _controller.value.aspectRatio : 16 / 9,
child: isInitialized
? Stack(
alignment: Alignment.bottomCenter,
children: [
VideoPlayer(_controller),
VideoProgressIndicator(
_controller,
allowScrubbing: true,
),
Center(
child: IconButton(
iconSize: 60,
icon: Icon(
_controller.value.isPlaying
? Icons.pause_circle
: Icons.play_circle,
color: Colors.white,
),
onPressed: () {
setState(() {
_controller.value.isPlaying
? _controller.pause()
: _controller.play();
});
},
),
),
],
)
: const Center(child: CircularProgressIndicator()),
);
}
}

View File

@ -72,6 +72,8 @@ dependencies:
carousel_slider: ^5.1.1
dropdown_search: ^6.0.2
ribbon_widget: ^1.0.5
flutter_html: ^3.0.0
video_player: ^2.10.1
dev_dependencies:
flutter_test:
@ -98,7 +100,6 @@ flutter:
# To add assets to your application, add an assets section, like this:
assets:
- assets/
- assets/botman_chat.html
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg