added faq

This commit is contained in:
Vaibhav Surve 2025-09-30 19:45:25 +05:30
parent 4feb2875f0
commit 55695ef176
9 changed files with 570 additions and 64 deletions

View File

@ -1,5 +1,5 @@
class ApiEndpoints {
static const String baseUrl = "https://stageapi.marcoaiot.com/api";
static const String baseUrl = "https://mapi.marcoaiot.com/api";
// static const String baseUrl = "https://api.marcoaiot.com/api";
// static const String baseUrl = "https://devapi.marcoaiot.com/api";

View File

@ -55,7 +55,7 @@ class _WelcomeScreenState extends State<WelcomeScreen>
context: context,
barrierDismissible: false,
builder: (_) => Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
insetPadding: const EdgeInsets.all(24),
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
@ -199,7 +199,7 @@ class _WelcomeScreenState extends State<WelcomeScreen>
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: Colors.orangeAccent,
borderRadius: BorderRadius.circular(6),
borderRadius: BorderRadius.circular(5),
),
child: MyText(
'BETA',
@ -232,7 +232,7 @@ class _WelcomeScreenState extends State<WelcomeScreen>
style: ElevatedButton.styleFrom(
backgroundColor: contentTheme.brandRed,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
elevation: 4,
shadowColor: Colors.black26,
),

View File

@ -49,7 +49,7 @@ class _LoginScreenState extends State<LoginScreen> with UIMixin {
padding: MySpacing.all(24),
decoration: BoxDecoration(
color: theme.colorScheme.primary.withOpacity(0.02),
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(5),
border: Border.all(
color: contentTheme.primary.withOpacity(0.5),
),
@ -77,7 +77,7 @@ class _LoginScreenState extends State<LoginScreen> with UIMixin {
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: Colors.blueAccent,
borderRadius: BorderRadius.circular(4),
borderRadius: BorderRadius.circular(5),
),
child: Text(
'BETA',
@ -148,7 +148,7 @@ class _LoginScreenState extends State<LoginScreen> with UIMixin {
value: controller.isChecked.value,
onChanged: controller.onChangeCheckBox,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
borderRadius: BorderRadius.circular(5),
),
fillColor: MaterialStateProperty
.resolveWith<Color>(
@ -192,7 +192,7 @@ class _LoginScreenState extends State<LoginScreen> with UIMixin {
onPressed: controller.onLogin,
elevation: 2,
padding: MySpacing.xy(24, 16),
borderRadiusAll: 16,
borderRadiusAll: 5,
backgroundColor: Colors.blueAccent,
child: MyText.labelMedium(
'Login',
@ -242,7 +242,7 @@ class _LoginScreenState extends State<LoginScreen> with UIMixin {
return Material(
elevation: 2,
shadowColor: contentTheme.secondary.withAlpha(30),
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(5),
child: TextFormField(
controller: controller,
validator: validator,
@ -255,7 +255,7 @@ class _LoginScreenState extends State<LoginScreen> with UIMixin {
filled: true,
fillColor: theme.cardColor,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(2),
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide.none,
),
prefixIcon: Icon(icon, size: 18),

View File

@ -110,7 +110,7 @@ class _MPINAuthScreenState extends State<MPINAuthScreen>
horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: Colors.orangeAccent,
borderRadius: BorderRadius.circular(6),
borderRadius: BorderRadius.circular(5),
),
child: MyText(
'BETA',
@ -145,7 +145,7 @@ class _MPINAuthScreenState extends State<MPINAuthScreen>
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(5),
boxShadow: const [
BoxShadow(
color: Colors.black12,
@ -264,7 +264,7 @@ class _MPINAuthScreenState extends State<MPINAuthScreen>
filled: true,
fillColor: Colors.grey.shade100,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide.none,
),
),
@ -279,7 +279,7 @@ class _MPINAuthScreenState extends State<MPINAuthScreen>
onPressed: controller.isLoading.value ? null : controller.onSubmitMPIN,
elevation: 2,
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
borderRadiusAll: 10,
borderRadiusAll: 5,
backgroundColor: controller.isLoading.value
? contentTheme.brandRed.withOpacity(0.6)
: contentTheme.brandRed,

View File

@ -197,10 +197,10 @@ class _OrganizationFormState extends State<_OrganizationForm> with UIMixin {
style: OutlinedButton.styleFrom(
side: const BorderSide(color: Colors.red),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(5),
),
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 14),
horizontal: 28, vertical: 5),
),
),
ElevatedButton.icon(
@ -222,10 +222,10 @@ class _OrganizationFormState extends State<_OrganizationForm> with UIMixin {
style: ElevatedButton.styleFrom(
backgroundColor: Colors.indigo,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(5),
),
padding: const EdgeInsets.symmetric(
horizontal: 28, vertical: 14),
horizontal: 28, vertical: 5),
),
),
],
@ -291,19 +291,19 @@ class _OrganizationFormState extends State<_OrganizationForm> with UIMixin {
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(color: Colors.grey[400]!),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(color: Colors.grey[300]!),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(color: contentTheme.brandRed, width: 1.5),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(color: Colors.red),
),
),
@ -367,15 +367,15 @@ class _OrganizationFormState extends State<_OrganizationForm> with UIMixin {
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(color: Colors.grey[400]!),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(color: Colors.grey[300]!),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(5),
borderSide:
BorderSide(color: contentTheme.brandRed, width: 1.5),
),

View File

@ -9,7 +9,6 @@ import 'package:marco/helpers/widgets/my_text.dart';
import 'package:marco/model/directory/contact_model.dart';
import 'package:marco/helpers/widgets/avatar.dart';
import 'package:marco/helpers/utils/launcher_utils.dart';
import 'package:tab_indicator_styler/tab_indicator_styler.dart';
import 'package:marco/helpers/widgets/Directory/comment_editor_card.dart';
import 'package:flutter_quill_delta_from_html/flutter_quill_delta_from_html.dart';
import 'package:marco/model/directory/add_comment_bottom_sheet.dart';
@ -190,16 +189,9 @@ class _ContactDetailScreenState extends State<ContactDetailScreen> {
),
]),
TabBar(
labelColor: Colors.red,
unselectedLabelColor: Colors.black,
indicator: MaterialIndicator(
color: Colors.red,
height: 4,
topLeftRadius: 8,
topRightRadius: 8,
bottomLeftRadius: 8,
bottomRightRadius: 8,
),
labelColor: Colors.black,
unselectedLabelColor: Colors.grey,
indicatorColor: Colors.red,
tabs: const [
Tab(text: "Details"),
Tab(text: "Notes"),
@ -360,12 +352,6 @@ class _ContactDetailScreenState extends State<ContactDetailScreen> {
[...activeComments, ...inactiveComments].reversed.toList();
final editingId = directoryController.editingCommentId.value;
if (comments.isEmpty) {
return Center(
child: MyText.bodyLarge("No notes yet.", color: Colors.grey),
);
}
return Stack(
children: [
MyRefreshIndicator(
@ -377,14 +363,19 @@ class _ContactDetailScreenState extends State<ContactDetailScreen> {
},
child: Padding(
padding: MySpacing.xy(12, 12),
child: ListView.separated(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.only(bottom: 100),
itemCount: comments.length,
separatorBuilder: (_, __) => MySpacing.height(14),
itemBuilder: (_, index) =>
_buildCommentItem(comments[index], editingId, contactId),
),
child: comments.isEmpty
? Center(
child:
MyText.bodyLarge("No notes yet.", color: Colors.grey),
)
: ListView.separated(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.only(bottom: 100),
itemCount: comments.length,
separatorBuilder: (_, __) => MySpacing.height(14),
itemBuilder: (_, index) => _buildCommentItem(
comments[index], editingId, contactId),
),
),
),
if (editingId == null)

View File

@ -0,0 +1,280 @@
import 'package:flutter/material.dart';
import 'package:flutter_lucide/flutter_lucide.dart';
import 'package:marco/helpers/widgets/my_text.dart';
import 'package:marco/helpers/widgets/my_spacing.dart';
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
class FAQScreen extends StatefulWidget {
const FAQScreen({super.key});
@override
State<FAQScreen> createState() => _FAQScreenState();
}
class _FAQScreenState extends State<FAQScreen> with UIMixin {
final List<Map<String, String>> faqs = [
{
"question": "How do I perform Check-in and Check-out?",
"answer":
"To Check-in, go to the Dashboard and tap the 'Check-in' button. For Check-out, return to the Dashboard and tap 'Check-out'. Ensure GPS and internet are enabled."
},
{
"question": "How do I login to the app?",
"answer":
"Enter your registered email and password on the login screen. If you forget your password, use the 'Forgot Password' option to reset it."
},
{
"question": "What is MPIN and how do I use it?",
"answer":
"MPIN is a 4-digit security PIN used for quick login and authorization. Set it under 'Settings > Security'. Use it instead of typing your password every time."
},
{
"question": "How do I log expenses?",
"answer":
"Go to the 'Expenses' section, click 'Add Expense', fill in the details like amount, category, and description, and then save. You can view all past expenses in the same section."
},
{
"question": "Can I edit or delete an expense?",
"answer":
"Yes, tap on an expense from the list and choose 'Edit' or 'Delete'. Changes are synced automatically with your account."
},
{
"question": "What if I face login issues?",
"answer":
"Ensure your internet is working and the app is updated. If problems persist, contact support via email or phone."
},
];
late List<bool> _expanded;
final TextEditingController searchController = TextEditingController();
String _searchQuery = "";
@override
void initState() {
super.initState();
_expanded = List.generate(faqs.length, (_) => false);
}
Widget _buildAppBar() {
return AppBar(
backgroundColor: const Color(0xFFF5F5F5),
elevation: 0.5,
automaticallyImplyLeading: false,
titleSpacing: 0,
title: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
IconButton(
icon: const Icon(Icons.arrow_back_ios_new,
color: Colors.black, size: 20),
onPressed: () => Navigator.pop(context),
),
MySpacing.width(8),
Expanded(
child: MyText.titleLarge('FAQ',
fontWeight: 700, color: Colors.black),
),
],
),
),
);
}
Widget _buildFAQCard(int index, Map<String, String> faq) {
final isExpanded = _expanded[index];
return GestureDetector(
onTap: () {
setState(() {
_expanded[index] = !isExpanded;
});
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
padding: const EdgeInsets.all(20),
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 12,
offset: Offset(0, 6),
),
],
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
shape: BoxShape.circle,
),
child: const Icon(LucideIcons.badge_help,
color: Colors.blue, size: 24),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MyText.bodyMedium(
faq["question"] ?? "",
fontWeight: 600,
color: Colors.black87,
fontSize: 14,
),
const SizedBox(height: 8),
AnimatedCrossFade(
firstChild: const SizedBox.shrink(),
secondChild: MyText.bodySmall(
faq["answer"] ?? "",
color: Colors.black54,
),
crossFadeState: isExpanded
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: const Duration(milliseconds: 300),
),
],
),
),
Icon(
isExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down,
color: Colors.grey[600],
),
],
),
),
);
}
Widget _buildEmptyState() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.help_outline, size: 60, color: Colors.grey),
MySpacing.height(18),
MyText.titleMedium(
'No matching FAQs found.',
fontWeight: 600,
color: Colors.grey,
),
MySpacing.height(10),
MyText.bodySmall(
'Try adjusting your search or clear the search bar.',
color: Colors.grey,
),
],
),
);
}
@override
Widget build(BuildContext context) {
final filteredFaqs = faqs
.asMap()
.entries
.where((entry) =>
entry.value["question"]!
.toLowerCase()
.contains(_searchQuery.toLowerCase()) ||
entry.value["answer"]!
.toLowerCase()
.contains(_searchQuery.toLowerCase()))
.toList();
return Scaffold(
backgroundColor: Colors.grey[100],
appBar: PreferredSize(
preferredSize: const Size.fromHeight(72),
child: _buildAppBar(),
),
body: Column(
children: [
// Search bar
Padding(
padding: const EdgeInsets.all(12.0),
child: SizedBox(
height: 40,
child: TextField(
controller: searchController,
onChanged: (value) {
setState(() {
_searchQuery = value;
});
},
decoration: InputDecoration(
contentPadding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 0),
prefixIcon:
const Icon(Icons.search, size: 20, color: Colors.grey),
suffixIcon: ValueListenableBuilder<TextEditingValue>(
valueListenable: searchController,
builder: (context, value, _) {
if (value.text.isEmpty) return const SizedBox.shrink();
return IconButton(
icon: const Icon(Icons.clear,
size: 20, color: Colors.grey),
onPressed: () {
searchController.clear();
setState(() {
_searchQuery = "";
});
},
);
},
),
hintText: 'Search FAQs...',
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Colors.grey.shade300),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Colors.grey.shade300),
),
),
),
),
),
Expanded(
child: filteredFaqs.isEmpty
? _buildEmptyState()
: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Introductory text
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 12),
child: MyText.bodyMedium(
'Here are some frequently asked questions to help you get started:',
fontWeight: 500,
color: Colors.black87,
),
),
...filteredFaqs
.map((entry) =>
_buildFAQCard(entry.key, entry.value))
.toList(),
const SizedBox(height: 24),
],
),
),
),
],
),
);
}
}

View File

@ -10,7 +10,8 @@ import 'package:marco/helpers/widgets/avatar.dart';
import 'package:marco/model/employees/employee_info.dart';
import 'package:marco/controller/auth/mpin_controller.dart';
import 'package:marco/view/employees/employee_profile_screen.dart';
import 'package:marco/view/support/support_screen.dart';
import 'package:marco/view/faq/faq_screen.dart';
class UserProfileBar extends StatefulWidget {
final bool isCondensed;
@ -32,7 +33,6 @@ class _UserProfileBarState extends State<UserProfileBar>
_initData();
}
Future<void> _initData() async {
employeeInfo = LocalStorage.getEmployeeInfo()!;
hasMpin = await LocalStorage.getIsMpin();
@ -45,7 +45,7 @@ class _UserProfileBarState extends State<UserProfileBar>
return Padding(
padding: const EdgeInsets.only(left: 14),
child: ClipRRect(
borderRadius: BorderRadius.circular(22),
borderRadius: BorderRadius.circular(5),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 18, sigmaY: 18),
child: AnimatedContainer(
@ -61,7 +61,7 @@ class _UserProfileBarState extends State<UserProfileBar>
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(22),
borderRadius: BorderRadius.circular(5),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.06),
@ -108,7 +108,6 @@ class _UserProfileBarState extends State<UserProfileBar>
);
}
Widget _userProfileSection(bool condensed) {
final padding = MySpacing.fromLTRB(
condensed ? 16 : 26,
@ -181,13 +180,15 @@ class _UserProfileBarState extends State<UserProfileBar>
),
SizedBox(height: spacingHeight),
_menuItemRow(
icon: LucideIcons.settings,
label: 'Settings',
icon: LucideIcons.badge_help,
label: 'Support',
onTap: _onSupportTap,
),
SizedBox(height: spacingHeight),
_menuItemRow(
icon: LucideIcons.badge_help,
label: 'Support',
icon: LucideIcons.info,
label: 'FAQ', // <-- New FAQ menu item
onTap: _onFaqTap, // <-- Handle tap
),
SizedBox(height: spacingHeight),
_menuItemRow(
@ -202,6 +203,14 @@ class _UserProfileBarState extends State<UserProfileBar>
);
}
void _onFaqTap() {
Get.to(() => const FAQScreen());
}
void _onSupportTap() {
Get.to(() => const SupportScreen());
}
Widget _menuItemRow({
required IconData icon,
required String label,
@ -211,12 +220,12 @@ class _UserProfileBarState extends State<UserProfileBar>
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(5),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.9),
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(5),
border: Border.all(color: Colors.grey.withOpacity(0.2), width: 1),
),
child: Row(
@ -276,7 +285,7 @@ class _UserProfileBarState extends State<UserProfileBar>
horizontal: condensed ? 14 : 22,
),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
),
),
),
@ -293,7 +302,7 @@ class _UserProfileBarState extends State<UserProfileBar>
Widget _buildLogoutDialog(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
elevation: 10,
backgroundColor: Colors.white,
child: Padding(
@ -337,7 +346,7 @@ class _UserProfileBarState extends State<UserProfileBar>
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14)),
borderRadius: BorderRadius.circular(5)),
),
child: const Text("Logout"),
),

View File

@ -0,0 +1,226 @@
import 'package:flutter/material.dart';
import 'package:flutter_lucide/flutter_lucide.dart';
import 'package:marco/helpers/widgets/my_text.dart';
import 'package:marco/helpers/widgets/my_spacing.dart';
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
import 'package:url_launcher/url_launcher.dart';
class SupportScreen extends StatefulWidget {
const SupportScreen({super.key});
@override
State<SupportScreen> createState() => _SupportScreenState();
}
class _SupportScreenState extends State<SupportScreen> with UIMixin {
final List<Map<String, dynamic>> contacts = [
{
"type": "email",
"label": "info@marcoaiot.com",
"subLabel": "Email us your queries",
"icon": LucideIcons.mail,
"action": "mailto:info@marcoaiot.com?subject=Support Request"
},
{
"type": "phone",
"label": "+91-8055099750",
"subLabel": "Call our support team",
"icon": LucideIcons.phone,
"action": "tel:+91-8055099750"
},
];
void _launchAction(String action) async {
final Uri uri = Uri.parse(action);
if (await canLaunchUrl(uri)) {
// Use LaunchMode.externalApplication for mailto/tel
await launchUrl(
uri,
mode: LaunchMode.externalApplication,
);
} else {
// Fallback if no app found
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('No app found to open this link.')),
);
}
}
Widget _buildAppBar() {
return AppBar(
backgroundColor: const Color(0xFFF5F5F5),
elevation: 0.5,
automaticallyImplyLeading: false,
titleSpacing: 0,
title: Padding(
padding: MySpacing.xy(16, 0),
child: Row(
children: [
IconButton(
icon: const Icon(Icons.arrow_back_ios_new,
color: Colors.black, size: 20),
onPressed: () => Navigator.pop(context),
),
MySpacing.width(8),
Expanded(
child: MyText.titleLarge('Support',
fontWeight: 700, color: Colors.black),
),
],
),
),
);
}
Widget _buildContactCard(Map<String, dynamic> contact) {
return GestureDetector(
onTap: () => _launchAction(contact["action"]),
child: Container(
padding: const EdgeInsets.all(20),
margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 12,
offset: Offset(0, 6),
),
],
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(contact["icon"], color: Colors.red, size: 24),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MyText.bodyMedium(
contact["label"],
fontWeight: 700,
color: Colors.black87,
fontSize: 16,
),
const SizedBox(height: 4),
MyText.bodySmall(
contact["subLabel"],
color: Colors.black54,
),
],
),
),
],
),
),
);
}
Widget _buildInfoCard(String title, String subtitle, IconData icon) {
return Container(
padding: const EdgeInsets.all(20),
margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 12,
offset: Offset(0, 6),
),
],
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(icon, color: Colors.red, size: 28),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MyText.bodyMedium(title,
fontWeight: 700, color: Colors.black87, fontSize: 16),
const SizedBox(height: 4),
MyText.bodySmall(subtitle, color: Colors.black54),
],
),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(72),
child: _buildAppBar(),
),
body: SafeArea(
child: RefreshIndicator(
onRefresh: () async {
// Optional: Implement refresh logic
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MySpacing.height(24),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: MyText.titleLarge(
"Need Help?",
fontWeight: 700,
color: Colors.red,
),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: MyText.bodySmall(
"Our support team is ready to assist you. Reach out via email or phone.",
color: Colors.grey[800],
),
),
const SizedBox(height: 24),
// Contact cards
...contacts.map((contact) => _buildContactCard(contact)),
const SizedBox(height: 16),
// Info card
_buildInfoCard(
"Working Hours",
"Monday - Friday: 9 AM - 6 PM\nSaturday: 10 AM - 2 PM",
LucideIcons.clock,
),
const SizedBox(height: 24),
],
),
),
),
),
);
}
}