2026-03-07 23:28:21 +05:30
import ' package:flutter/material.dart ' ;
import ' package:flutter_riverpod/flutter_riverpod.dart ' ;
import ' package:flutter_svg/flutter_svg.dart ' ;
import ' package:real_estate_mobile/core/constants/app_colors.dart ' ;
import ' package:real_estate_mobile/core/network/api_client.dart ' ;
// ── Data models ──
class FaqItem {
final int id ;
final String icon ;
final String question ;
final String answer ;
final String category ;
const FaqItem ( {
required this . id ,
required this . icon ,
required this . question ,
required this . answer ,
required this . category ,
} ) ;
factory FaqItem . fromJson ( Map < String , dynamic > json ) {
return FaqItem (
id: json [ ' id ' ] as int ? ? ? 0 ,
icon: json [ ' icon ' ] as String ? ? ? ' ' ,
question: json [ ' question ' ] as String ? ? ? ' ' ,
answer: json [ ' answer ' ] as String ? ? ? ' ' ,
category: json [ ' category ' ] as String ? ? ? ' ' ,
) ;
}
}
// ── Default data (same as web) ──
const _defaultFaqs = < FaqItem > [
FaqItem (
id: 1 ,
icon: ' assets/icons/home_faq_icon.svg ' ,
question: ' How do I update my listing status? ' ,
answer:
' To update your listing status, log in to your Agent Dashboard. Locate the property in your "Active Listings" tab, click the three-dot menu on the right, and select "Update Status." You can change it to Pending, Sold, or Temporarily Off Market. Changes are reflected immediately. ' ,
category: ' agent ' ,
) ,
FaqItem (
id: 2 ,
icon: ' assets/icons/home_faq_icon.svg ' ,
question: ' What is the pre-approval process? ' ,
answer:
' The pre-approval process involves submitting your financial documents to a lender who will review your credit history, income, assets, and debts. Once approved, you \' ll receive a pre-approval letter stating how much you can borrow, which strengthens your position when making offers on properties. ' ,
category: ' buying ' ,
) ,
FaqItem (
id: 3 ,
icon: ' assets/icons/home_faq_icon.svg ' ,
question: ' How do I schedule an open house? ' ,
answer:
' To schedule an open house, navigate to your Agent Dashboard and select the property you want to showcase. Click on "Schedule Open House" and choose your preferred date and time. ' ,
category: ' agent ' ,
) ,
FaqItem (
id: 4 ,
icon: ' assets/icons/home_faq_icon.svg ' ,
question: ' How do I reset my password? ' ,
answer:
' To reset your password, click on the "Login" button and then select "Forgot Password." Enter your registered email address and we \' ll send you a password reset link. Follow the link to create a new password. ' ,
category: ' platform ' ,
) ,
FaqItem (
id: 5 ,
icon: ' assets/icons/home_faq_icon.svg ' ,
question: ' How do I contact a listing agent directly? ' ,
answer:
' You can contact a listing agent by visiting the property page and clicking the "Contact Agent" button. You \' ll be able to send a message directly to the agent through our secure messaging system. ' ,
category: ' buying ' ,
) ,
FaqItem (
id: 6 ,
icon: ' assets/icons/home_faq_icon.svg ' ,
question: ' What if I find inaccurate data on a listing? ' ,
answer:
' If you find inaccurate information on a listing, please use the "Report Issue" button on the property page. Describe the inaccuracy and our team will review and correct it within 24-48 hours. ' ,
category: ' platform ' ,
) ,
] ;
// ── Provider ──
final faqProvider =
StateNotifierProvider . autoDispose < FaqNotifier , FaqState > ( ( ref ) {
return FaqNotifier ( ) ;
} ) ;
class FaqState {
final List < FaqItem > faqs ;
final bool isLoading ;
final int ? expandedId ;
const FaqState ( {
this . faqs = const [ ] ,
this . isLoading = true ,
this . expandedId ,
} ) ;
FaqState copyWith ( {
List < FaqItem > ? faqs ,
bool ? isLoading ,
int ? expandedId ,
bool clearExpanded = false ,
} ) {
return FaqState (
faqs: faqs ? ? this . faqs ,
isLoading: isLoading ? ? this . isLoading ,
expandedId: clearExpanded ? null : ( expandedId ? ? this . expandedId ) ,
) ;
}
}
class FaqNotifier extends StateNotifier < FaqState > {
FaqNotifier ( ) : super ( const FaqState ( ) ) {
_load ( ) ;
}
Future < void > _load ( ) async {
try {
final response = await ApiClient . instance . dio . get ( ' /cms/page/faq ' ) ;
final data = response . data [ ' data ' ] as List < dynamic > ? ;
if ( data ! = null ) {
for ( final record in data ) {
final map = record as Map < String , dynamic > ;
if ( map [ ' sectionKey ' ] = = ' faqContent ' ) {
final content = map [ ' content ' ] as Map < String , dynamic > ? ;
if ( content ! = null ) {
final faqsList = content [ ' faqs ' ] as List < dynamic > ? ;
if ( faqsList ! = null & & faqsList . isNotEmpty ) {
final faqs = faqsList
. map ( ( e ) = > FaqItem . fromJson ( e as Map < String , dynamic > ) )
. toList ( ) ;
state = state . copyWith (
faqs: faqs ,
isLoading: false ,
expandedId: faqs . first . id ,
) ;
return ;
}
}
}
}
}
} catch ( _ ) {
// Fall through to defaults
}
// Use defaults
state = state . copyWith (
faqs: _defaultFaqs ,
isLoading: false ,
expandedId: _defaultFaqs . first . id ,
) ;
}
void toggleFaq ( int id ) {
if ( state . expandedId = = id ) {
state = state . copyWith ( clearExpanded: true ) ;
} else {
state = state . copyWith ( expandedId: id ) ;
}
}
}
// ── Screen ──
class FaqScreen extends ConsumerWidget {
const FaqScreen ( { super . key } ) ;
@ override
Widget build ( BuildContext context , WidgetRef ref ) {
final state = ref . watch ( faqProvider ) ;
2026-03-08 16:05:08 +05:30
if ( state . isLoading ) {
return const Center (
child: CircularProgressIndicator ( color: AppColors . accentOrange ) ,
) ;
}
return ListView (
padding: const EdgeInsets . fromLTRB ( 0 , 20 , 0 , 30 ) ,
children: [
// Title
const Padding (
padding: EdgeInsets . symmetric ( horizontal: 24 ) ,
child: Text (
' Frequently Asked Questions ? ' ,
style: TextStyle (
fontFamily: ' Fractul ' ,
fontSize: 24 ,
fontWeight: FontWeight . w700 ,
color: AppColors . primaryDark ,
) ,
textAlign: TextAlign . center ,
2026-03-07 23:28:21 +05:30
) ,
2026-03-08 16:05:08 +05:30
) ,
const SizedBox ( height: 24 ) ,
2026-03-07 23:28:21 +05:30
2026-03-08 16:05:08 +05:30
// FAQ items
. . . state . faqs . map ( ( faq ) = > _FaqAccordion (
faq: faq ,
isExpanded: state . expandedId = = faq . id ,
onTap: ( ) = >
ref . read ( faqProvider . notifier ) . toggleFaq ( faq . id ) ,
) ) ,
2026-03-07 23:28:21 +05:30
2026-03-08 16:05:08 +05:30
const SizedBox ( height: 30 ) ,
2026-03-07 23:28:21 +05:30
2026-03-08 16:05:08 +05:30
// Still Need Help section
const _StillNeedHelpSection ( ) ,
] ,
2026-03-07 23:28:21 +05:30
) ;
}
}
// ── Accordion item ──
class _FaqAccordion extends StatelessWidget {
final FaqItem faq ;
final bool isExpanded ;
final VoidCallback onTap ;
const _FaqAccordion ( {
required this . faq ,
required this . isExpanded ,
required this . onTap ,
} ) ;
@ override
Widget build ( BuildContext context ) {
return Container (
margin: const EdgeInsets . symmetric ( horizontal: 0 , vertical: 0 ) ,
decoration: BoxDecoration (
border: Border . all (
color: AppColors . primaryDark . withValues ( alpha: 0.15 ) ,
width: 1 ,
) ,
borderRadius: BorderRadius . circular ( 7 ) ,
) ,
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
// Question row
GestureDetector (
onTap: onTap ,
behavior: HitTestBehavior . opaque ,
child: Padding (
padding: const EdgeInsets . symmetric ( horizontal: 20 , vertical: 18 ) ,
child: Row (
children: [
// Icon
SvgPicture . asset (
' assets/icons/home_faq_icon.svg ' ,
width: 22 ,
height: 22 ,
placeholderBuilder: ( _ ) = > const Icon (
Icons . home_outlined ,
color: AppColors . accentOrange ,
size: 22 ,
) ,
) ,
const SizedBox ( width: 14 ) ,
// Question text
Expanded (
child: Text (
faq . question ,
style: TextStyle (
fontFamily: isExpanded ? ' Fractul ' : ' SourceSerif4 ' ,
fontSize: 16 ,
fontWeight:
isExpanded ? FontWeight . w500 : FontWeight . w600 ,
color: AppColors . primaryDark ,
) ,
) ,
) ,
const SizedBox ( width: 12 ) ,
// Chevron
Icon (
isExpanded
? Icons . keyboard_arrow_down
: Icons . keyboard_arrow_right ,
color: AppColors . primaryDark ,
size: 20 ,
) ,
] ,
) ,
) ,
) ,
// Answer (expanded)
if ( isExpanded ) . . . [
// Divider
Padding (
padding: const EdgeInsets . symmetric ( horizontal: 20 ) ,
child: Divider (
height: 1 ,
color: AppColors . primaryDark . withValues ( alpha: 0.15 ) ,
) ,
) ,
Padding (
padding: const EdgeInsets . fromLTRB ( 56 , 14 , 20 , 18 ) ,
child: Text (
faq . answer ,
style: const TextStyle (
fontFamily: ' SourceSerif4 ' ,
fontSize: 14 ,
fontWeight: FontWeight . w400 ,
color: AppColors . primaryDark ,
height: 1.5 ,
) ,
) ,
) ,
] ,
] ,
) ,
) ;
}
}
// ── Still Need Help section ──
class _StillNeedHelpSection extends StatelessWidget {
const _StillNeedHelpSection ( ) ;
@ override
Widget build ( BuildContext context ) {
return Container (
margin: const EdgeInsets . symmetric ( horizontal: 0 ) ,
decoration: BoxDecoration (
border: Border . all (
color: AppColors . primaryDark . withValues ( alpha: 0.15 ) ,
width: 1 ,
) ,
borderRadius: BorderRadius . circular ( 7 ) ,
) ,
padding: const EdgeInsets . fromLTRB ( 24 , 24 , 20 , 24 ) ,
child: Row (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
// Left side: text
Expanded (
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
const Text (
' Still Need Help ? ' ,
style: TextStyle (
fontFamily: ' Fractul ' ,
fontSize: 14 ,
fontWeight: FontWeight . w700 ,
color: AppColors . primaryDark ,
) ,
) ,
const SizedBox ( height: 10 ) ,
const Text (
' Our Support Team Is Available \n Mon-Fri, 9am-6pm IST ' ,
style: TextStyle (
fontFamily: ' SourceSerif4 ' ,
fontSize: 14 ,
fontWeight: FontWeight . w400 ,
color: AppColors . primaryDark ,
height: 1.4 ,
) ,
) ,
] ,
) ,
) ,
// Right side: buttons
Column (
children: [
// Start Live Chat
_buildSupportButton (
icon: Icons . chat_bubble_outline ,
label: ' Start Live Chat ' ,
onTap: ( ) { } ,
) ,
const SizedBox ( height: 12 ) ,
// Email Support
_buildSupportButton (
icon: Icons . email_outlined ,
label: ' Email Support ' ,
onTap: ( ) { } ,
) ,
] ,
) ,
] ,
) ,
) ;
}
Widget _buildSupportButton ( {
required IconData icon ,
required String label ,
required VoidCallback onTap ,
} ) {
return GestureDetector (
onTap: onTap ,
child: Container (
width: 162 ,
height: 38 ,
decoration: BoxDecoration (
borderRadius: BorderRadius . circular ( 7 ) ,
border: Border . all (
color: AppColors . primaryDark . withValues ( alpha: 0.15 ) ,
width: 1 ,
) ,
) ,
child: Row (
mainAxisAlignment: MainAxisAlignment . center ,
children: [
Icon ( icon , size: 18 , color: AppColors . primaryDark ) ,
const SizedBox ( width: 6 ) ,
Text (
label ,
style: const TextStyle (
fontFamily: ' SourceSerif4 ' ,
fontSize: 14 ,
fontWeight: FontWeight . w400 ,
color: AppColors . primaryDark ,
) ,
) ,
] ,
) ,
) ,
) ;
}
}