refactor: improve specialization card layout and unify chat screen header and navigation with home screen components.

This commit is contained in:
pradeepkumar
2026-03-08 02:01:03 +05:30
parent bba9cd3936
commit 219acfc013
14 changed files with 2498 additions and 236 deletions

View File

@@ -56,18 +56,24 @@ class AppTheme {
), ),
inputDecorationTheme: InputDecorationTheme( inputDecorationTheme: InputDecorationTheme(
filled: true, filled: true,
fillColor: AppColors.inputFill, fillColor: Colors.white,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 24, horizontal: 24,
vertical: 20, vertical: 20,
), ),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
borderSide: BorderSide.none, borderSide: const BorderSide(
color: Color(0xFFE0E0E0),
width: 1,
),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
borderSide: BorderSide.none, borderSide: const BorderSide(
color: Color(0xFFE0E0E0),
width: 1,
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),

View File

@@ -1067,6 +1067,7 @@ class _AgentDetailScreenState extends ConsumerState<AgentDetailScreen> {
context.push('/login'); context.push('/login');
return; return;
} }
context.go('/messages');
}, },
), ),
_buildNavItem( _buildNavItem(
@@ -1078,6 +1079,7 @@ class _AgentDetailScreenState extends ConsumerState<AgentDetailScreen> {
context.push('/login'); context.push('/login');
return; return;
} }
context.push('/profile');
}, },
), ),
], ],
@@ -1162,9 +1164,10 @@ class _ExpandableChipsSectionState extends State<_ExpandableChipsSection> {
spacing: 10, spacing: 10,
runSpacing: 10, runSpacing: 10,
children: [ children: [
...showItems.map((item) => Container( ...showItems.map((item) => IntrinsicWidth(
child: Container(
height: 28, height: 28,
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.center, alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(15),
@@ -1181,13 +1184,15 @@ class _ExpandableChipsSectionState extends State<_ExpandableChipsSection> {
color: AppColors.primaryDark, color: AppColors.primaryDark,
), ),
), ),
),
)), )),
if (hasMore && !_expanded) if (hasMore && !_expanded)
GestureDetector( GestureDetector(
onTap: () => setState(() => _expanded = true), onTap: () => setState(() => _expanded = true),
child: IntrinsicWidth(
child: Container( child: Container(
height: 28, height: 28,
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.center, alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(15),
@@ -1206,6 +1211,7 @@ class _ExpandableChipsSectionState extends State<_ExpandableChipsSection> {
), ),
), ),
), ),
),
], ],
), ),
const SizedBox(height: 14), const SizedBox(height: 14),
@@ -1230,7 +1236,7 @@ class _SpecializationCardWidget extends StatefulWidget {
class _SpecializationCardWidgetState extends State<_SpecializationCardWidget> { class _SpecializationCardWidgetState extends State<_SpecializationCardWidget> {
bool _expanded = false; bool _expanded = false;
static const _initialCount = 5; static const _initialCount = 3;
IconData get _icon { IconData get _icon {
final slug = widget.card.slug.toLowerCase(); final slug = widget.card.slug.toLowerCase();

View File

@@ -414,6 +414,7 @@ class _AgentSearchScreenState extends ConsumerState<AgentSearchScreen> {
context.push('/login'); context.push('/login');
return; return;
} }
context.push('/profile');
}, },
), ),
], ],

View File

@@ -0,0 +1,691 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
import 'package:real_estate_mobile/core/constants/app_colors.dart';
import 'package:real_estate_mobile/features/auth/presentation/providers/auth_provider.dart';
import 'package:real_estate_mobile/features/home/presentation/widgets/home_header.dart';
class ContactScreen extends ConsumerStatefulWidget {
const ContactScreen({super.key});
@override
ConsumerState<ContactScreen> createState() => _ContactScreenState();
}
class _ContactScreenState extends ConsumerState<ContactScreen> {
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _phoneController = TextEditingController();
final _messageController = TextEditingController();
bool _isSending = false;
@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
_phoneController.dispose();
_messageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Column(
children: [
SafeArea(
bottom: false,
child: const HomeHeader(),
),
Expanded(
child: ListView(
padding: EdgeInsets.zero,
children: [
const SizedBox(height: 18),
_buildTitle(),
const SizedBox(height: 10),
_buildDescription(),
const SizedBox(height: 40),
_buildContactForm(),
const SizedBox(height: 20),
_buildContactDetails(),
const SizedBox(height: 40),
_buildFindAgentSection(),
const SizedBox(height: 30),
],
),
),
],
),
bottomNavigationBar: _buildBottomNavBar(),
);
}
// -- Title --
Widget _buildTitle() {
return const Center(
child: Text(
'Get In Touch',
style: TextStyle(
fontFamily: 'Fractul',
fontSize: 25,
fontWeight: FontWeight.w700,
color: AppColors.primaryDark,
),
),
);
}
// -- Description --
Widget _buildDescription() {
return const Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Text(
'Have a question about a property or need assistance? Fill out the form below and our team will get back to you shortly.',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 14,
fontWeight: FontWeight.w400,
color: AppColors.primaryDark,
),
),
);
}
// -- Contact Form --
Widget _buildContactForm() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 23),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLabel('Your Name'),
const SizedBox(height: 8),
_buildTextField(
controller: _nameController,
hintText: 'Brain Neeland',
),
const SizedBox(height: 24),
_buildLabel('Email Address'),
const SizedBox(height: 8),
_buildTextField(
controller: _emailController,
hintText: '123@gmail.com',
keyboardType: TextInputType.emailAddress,
prefixIcon: Icons.email,
),
const SizedBox(height: 24),
_buildLabel('Phone Number'),
const SizedBox(height: 8),
_buildTextField(
controller: _phoneController,
hintText: '12345678990',
keyboardType: TextInputType.phone,
prefixIcon: Icons.call,
),
const SizedBox(height: 24),
_buildLabel('Message'),
const SizedBox(height: 8),
_buildMessageField(),
const SizedBox(height: 30),
_buildFormButtons(),
],
),
);
}
Widget _buildLabel(String text) {
return Text(
text,
style: const TextStyle(
fontFamily: 'Fractul',
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.primaryDark,
),
);
}
Widget _buildTextField({
required TextEditingController controller,
required String hintText,
TextInputType? keyboardType,
IconData? prefixIcon,
}) {
final borderSide = BorderSide(
color: AppColors.primaryDark.withValues(alpha: 0.1),
width: 1,
);
final border = OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
borderSide: borderSide,
);
return SizedBox(
height: 52,
child: TextField(
controller: controller,
keyboardType: keyboardType,
style: const TextStyle(
fontFamily: 'Fractul',
fontSize: 14,
fontWeight: FontWeight.w200,
color: Colors.black,
),
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 14,
),
hintText: hintText,
hintStyle: TextStyle(
fontFamily: 'Fractul',
fontSize: 14,
fontWeight: FontWeight.w200,
color: Colors.black.withValues(alpha: 0.4),
),
border: border,
enabledBorder: border,
focusedBorder: border,
disabledBorder: border,
prefixIcon: prefixIcon != null
? Icon(prefixIcon, size: 22, color: AppColors.primaryDark)
: null,
prefixIconConstraints: prefixIcon != null
? const BoxConstraints(minWidth: 48)
: null,
),
),
);
}
Widget _buildMessageField() {
final borderSide = BorderSide(
color: AppColors.primaryDark.withValues(alpha: 0.1),
width: 1,
);
final border = OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
borderSide: borderSide,
);
return SizedBox(
height: 183,
child: TextField(
controller: _messageController,
maxLines: null,
expands: true,
textAlignVertical: TextAlignVertical.top,
style: const TextStyle(
fontFamily: 'Fractul',
fontSize: 14,
fontWeight: FontWeight.w200,
color: Colors.black,
),
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
),
hintText: 'Write your message here...',
hintStyle: TextStyle(
fontFamily: 'Fractul',
fontSize: 14,
fontWeight: FontWeight.w200,
color: Colors.black.withValues(alpha: 0.4),
),
border: border,
enabledBorder: border,
focusedBorder: border,
disabledBorder: border,
),
),
);
}
Widget _buildFormButtons() {
return Row(
children: [
Expanded(
child: GestureDetector(
onTap: () => context.pop(),
child: Container(
height: 46,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
border: Border.all(
color: AppColors.primaryDark.withValues(alpha: 0.1),
width: 1,
),
),
alignment: Alignment.center,
child: const Text(
'Cancel',
style: TextStyle(
fontFamily: 'Fractul',
fontSize: 14,
fontWeight: FontWeight.w700,
color: AppColors.primaryDark,
),
),
),
),
),
const SizedBox(width: 23),
Expanded(
child: GestureDetector(
onTap: _isSending ? null : _sendMessage,
child: Container(
height: 46,
decoration: BoxDecoration(
color: AppColors.accentOrange,
borderRadius: BorderRadius.circular(15),
),
alignment: Alignment.center,
child: _isSending
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: AppColors.primaryDark,
),
)
: const Text(
'Send Message',
style: TextStyle(
fontFamily: 'Fractul',
fontSize: 14,
fontWeight: FontWeight.w700,
color: AppColors.primaryDark,
),
),
),
),
),
],
);
}
// -- Contact Details Section --
Widget _buildContactDetails() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: AppColors.primaryDark.withValues(alpha: 0.1),
width: 1,
),
),
child: Column(
children: [
const SizedBox(height: 17),
// Title
const Text(
'Contact Details',
style: TextStyle(
fontFamily: 'Fractul',
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.primaryDark,
),
),
const SizedBox(height: 12),
_buildDetailDivider(),
// Email Support
_buildContactDetailRow(
icon: Icons.email_outlined,
label: 'Email Support',
value: '123support@gmail.com',
),
_buildDetailDivider(),
// Phone
_buildContactDetailRow(
icon: Icons.call_outlined,
label: 'Phone',
value: '1234567890',
subtitle: 'Mon-Fri 9am-6pm',
),
_buildDetailDivider(),
// Office
_buildContactDetailRow(
icon: Icons.location_on_outlined,
label: 'Office',
value: '123 Market Street\nNew York CA 234737',
),
const SizedBox(height: 16),
// Map placeholder
Padding(
padding: const EdgeInsets.symmetric(horizontal: 36),
child: Container(
height: 166,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: AppColors.primaryDark.withValues(alpha: 0.1),
width: 1,
),
color: const Color(0xFFF5F5F5),
),
alignment: Alignment.center,
child: Icon(
Icons.map_outlined,
size: 48,
color: AppColors.primaryDark.withValues(alpha: 0.3),
),
),
),
const SizedBox(height: 16),
// Get Directions button
GestureDetector(
onTap: () {},
child: Container(
width: 120,
height: 35,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
border: Border.all(
color: AppColors.accentOrange,
width: 1,
),
),
alignment: Alignment.center,
child: const Text(
'Get Directions',
style: TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 14,
fontWeight: FontWeight.w400,
color: AppColors.accentOrange,
),
),
),
),
const SizedBox(height: 20),
],
),
),
);
}
Widget _buildDetailDivider() {
return Divider(
color: AppColors.primaryDark.withValues(alpha: 0.1),
height: 1,
thickness: 1,
indent: 1,
endIndent: 1,
);
}
Widget _buildContactDetailRow({
required IconData icon,
required String label,
required String value,
String? subtitle,
}) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
icon,
size: 22,
color: AppColors.primaryDark,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 14,
fontWeight: FontWeight.w300,
color: AppColors.primaryDark,
),
),
const SizedBox(height: 4),
Text(
value,
style: const TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 14,
fontWeight: FontWeight.w400,
color: AppColors.primaryDark,
),
),
if (subtitle != null) ...[
const SizedBox(height: 4),
Text(
subtitle,
style: const TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 10,
fontWeight: FontWeight.w400,
color: AppColors.primaryDark,
),
),
],
],
),
),
],
),
);
}
// -- Find Agent Section --
Widget _buildFindAgentSection() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 30),
child: Column(
children: [
const Text(
'Ready to find an agent?',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'Fractul',
fontSize: 25,
fontWeight: FontWeight.w700,
color: AppColors.primaryDark,
),
),
const SizedBox(height: 16),
const Text(
'Discover trusted agents and start your property journey with confidence.',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 14,
fontWeight: FontWeight.w400,
color: AppColors.primaryDark,
),
),
const SizedBox(height: 24),
// Illustration placeholder
Container(
width: 254,
height: 254,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFFFFF5EB),
),
alignment: Alignment.center,
child: Icon(
Icons.real_estate_agent,
size: 80,
color: AppColors.accentOrange.withValues(alpha: 0.6),
),
),
const SizedBox(height: 24),
// Start Your Search button
GestureDetector(
onTap: () => context.push('/agents/search'),
child: Container(
height: 46,
width: 200,
decoration: BoxDecoration(
color: AppColors.accentOrange,
borderRadius: BorderRadius.circular(15),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Start Your Search',
style: TextStyle(
fontFamily: 'Fractul',
fontSize: 16,
fontWeight: FontWeight.w700,
color: AppColors.primaryDark,
),
),
const SizedBox(width: 8),
const Icon(
Icons.arrow_forward,
size: 18,
color: AppColors.primaryDark,
),
],
),
),
),
],
),
);
}
// -- Send Message --
void _sendMessage() {
final name = _nameController.text.trim();
final email = _emailController.text.trim();
final message = _messageController.text.trim();
if (name.isEmpty || email.isEmpty || message.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please fill in all required fields'),
backgroundColor: Colors.red,
),
);
return;
}
setState(() => _isSending = true);
// Simulate sending — replace with actual API call
Future.delayed(const Duration(seconds: 2), () {
if (!mounted) return;
setState(() => _isSending = false);
_nameController.clear();
_emailController.clear();
_phoneController.clear();
_messageController.clear();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Message sent successfully!'),
backgroundColor: Color(0xFF638559),
),
);
});
}
// -- Bottom Navigation Bar --
Widget _buildBottomNavBar() {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
border: Border(
top: BorderSide(color: Color(0xFFE8E8E8), width: 1),
),
),
child: SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildNavItem(
svgPath: 'assets/icons/nav_home_icon.svg',
fallbackIcon: Icons.home,
isSelected: false,
onTap: () => context.go('/home'),
),
_buildNavItem(
svgPath: 'assets/icons/nav_list_icon.svg',
fallbackIcon: Icons.list,
isSelected: false,
onTap: () => context.push('/agents/search'),
),
_buildNavItem(
svgPath: 'assets/icons/nav_messages_icon.svg',
fallbackIcon: Icons.chat_bubble,
isSelected: false,
onTap: () {
final authState = ref.read(authProvider);
if (authState.status != AuthStatus.authenticated) {
context.push('/login');
return;
}
context.go('/messages');
},
),
_buildNavItem(
svgPath: 'assets/icons/nav_profile_icon.svg',
fallbackIcon: Icons.person_outline,
isSelected: false,
onTap: () {
final authState = ref.read(authProvider);
if (authState.status != AuthStatus.authenticated) {
context.push('/login');
return;
}
context.push('/profile');
},
),
],
),
),
),
);
}
Widget _buildNavItem({
required String svgPath,
required IconData fallbackIcon,
required bool isSelected,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: SvgPicture.asset(
svgPath,
width: 24,
height: 24,
colorFilter: isSelected
? const ColorFilter.mode(AppColors.primaryDark, BlendMode.srcIn)
: const ColorFilter.mode(
AppColors.accentOrange, BlendMode.srcIn),
placeholderBuilder: (_) => Icon(
fallbackIcon,
size: 24,
color: isSelected ? AppColors.primaryDark : AppColors.accentOrange,
),
),
),
);
}
}

View File

@@ -275,6 +275,7 @@ class FaqScreen extends ConsumerWidget {
context.push('/login'); context.push('/login');
return; return;
} }
context.go('/messages');
}, },
), ),
_buildNavItem( _buildNavItem(
@@ -288,6 +289,7 @@ class FaqScreen extends ConsumerWidget {
context.push('/login'); context.push('/login');
return; return;
} }
context.push('/profile');
}, },
), ),
], ],

View File

@@ -165,6 +165,8 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
context.push('/login'); context.push('/login');
return; return;
} }
context.push('/profile');
return;
} }
setState(() => _selectedIndex = index); setState(() => _selectedIndex = index);
}, },

View File

@@ -163,7 +163,10 @@ class _MenuDrawer extends ConsumerWidget {
context, context,
icon: Icons.mail_outline, icon: Icons.mail_outline,
label: 'Contact', label: 'Contact',
onTap: () => Navigator.pop(context), onTap: () {
Navigator.pop(context);
context.push('/contact');
},
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Divider(color: AppColors.divider, height: 1), const Divider(color: AppColors.divider, height: 1),
@@ -174,13 +177,19 @@ class _MenuDrawer extends ConsumerWidget {
context, context,
icon: Icons.person_outline, icon: Icons.person_outline,
label: 'Account Settings', label: 'Account Settings',
onTap: () => Navigator.pop(context), onTap: () {
Navigator.pop(context);
context.push('/profile');
},
), ),
_buildMenuItem( _buildMenuItem(
context, context,
icon: Icons.notifications_outlined, icon: Icons.notifications_outlined,
label: 'Notification Settings', label: 'Notification Settings',
onTap: () => Navigator.pop(context), onTap: () {
Navigator.pop(context);
context.push('/profile');
},
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Divider(color: AppColors.divider, height: 1), const Divider(color: AppColors.divider, height: 1),

View File

@@ -1,6 +1,7 @@
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:real_estate_mobile/config/app_config.dart'; import 'package:real_estate_mobile/config/app_config.dart';
import 'package:real_estate_mobile/core/constants/app_colors.dart'; import 'package:real_estate_mobile/core/constants/app_colors.dart';
import 'package:real_estate_mobile/features/auth/presentation/providers/auth_provider.dart'; import 'package:real_estate_mobile/features/auth/presentation/providers/auth_provider.dart';
@@ -91,7 +92,7 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
void _handleBack() { void _handleBack() {
ref.read(messagingProvider.notifier).setActiveConversation(null); ref.read(messagingProvider.notifier).setActiveConversation(null);
Navigator.pop(context); context.go('/messages');
} }
// ============================================================ // ============================================================
@@ -183,11 +184,11 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
final otherPartyName = conversation?.otherParty.name ?? 'Chat'; final otherPartyName = conversation?.otherParty.name ?? 'Chat';
final otherPartyAvatar = conversation?.otherParty.avatar; final otherPartyAvatar = conversation?.otherParty.avatar;
final isOnline = conversation?.otherParty.isOnline ?? false; final isOnline = conversation?.otherParty.isOnline ?? false;
return Scaffold( final otherPartyHeadline = conversation?.otherParty.headline;
backgroundColor: Colors.white,
body: SafeArea( return Column(
child: Column(
children: [ children: [
// Chat header with name + status
_buildHeader( _buildHeader(
name: otherPartyName, name: otherPartyName,
isOnline: isOnline, isOnline: isOnline,
@@ -200,25 +201,13 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
strokeWidth: 2, strokeWidth: 2,
), ),
) )
: messages.isEmpty : _buildChatBody(
? Center(
child: Text(
'No messages yet.\nSay hello!',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 14,
fontWeight: FontWeight.w400,
color: AppColors.hintText,
),
),
)
: _buildMessagesList(
messages: messages, messages: messages,
currentUserId: currentUserId ?? '', currentUserId: currentUserId ?? '',
currentUserName: 'You',
otherPartyName: otherPartyName, otherPartyName: otherPartyName,
otherPartyAvatar: otherPartyAvatar, otherPartyAvatar: otherPartyAvatar,
otherPartyHeadline: otherPartyHeadline,
isOnline: isOnline,
isTyping: isTyping, isTyping: isTyping,
isLoadingMore: messagingState.isLoadingMessages && isLoadingMore: messagingState.isLoadingMessages &&
messages.isNotEmpty, messages.isNotEmpty,
@@ -226,8 +215,6 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
), ),
_buildInputArea(), _buildInputArea(),
], ],
),
),
); );
} }
@@ -240,16 +227,7 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
required bool isOnline, required bool isOnline,
}) { }) {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), padding: const EdgeInsets.fromLTRB(12, 10, 12, 6),
child: Container(
height: 55,
decoration: BoxDecoration(
border: Border.all(
color: AppColors.primaryDark,
width: 0.5,
),
borderRadius: BorderRadius.circular(7),
),
child: Row( child: Row(
children: [ children: [
// Back arrow // Back arrow
@@ -257,10 +235,10 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
onTap: _handleBack, onTap: _handleBack,
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
child: const Padding( child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 14), padding: EdgeInsets.only(right: 16),
child: Icon( child: Icon(
Icons.arrow_back, Icons.arrow_back,
size: 23, size: 22,
color: AppColors.primaryDark, color: AppColors.primaryDark,
), ),
), ),
@@ -268,7 +246,6 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
// Name and active status // Name and active status
Expanded( Expanded(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
@@ -328,7 +305,7 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
onTap: () {}, onTap: () {},
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
child: Padding( child: Padding(
padding: const EdgeInsets.only(right: 14), padding: const EdgeInsets.only(right: 4),
child: Icon( child: Icon(
Icons.star_rounded, Icons.star_rounded,
size: 24, size: 24,
@@ -338,6 +315,189 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
), ),
], ],
), ),
);
}
// ============================================================
// CHAT BODY (profile section + messages)
// ============================================================
Widget _buildChatBody({
required List<ChatMessage> messages,
required String currentUserId,
required String otherPartyName,
String? otherPartyAvatar,
String? otherPartyHeadline,
required bool isOnline,
required bool isTyping,
required bool isLoadingMore,
}) {
if (messages.isEmpty) {
return SingleChildScrollView(
child: Column(
children: [
_buildProfileSection(
name: otherPartyName,
avatar: otherPartyAvatar,
headline: otherPartyHeadline,
isOnline: isOnline,
),
const SizedBox(height: 60),
Text(
'No messages yet.\nSay hello!',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 14,
fontWeight: FontWeight.w400,
color: AppColors.hintText,
),
),
],
),
);
}
return _buildMessagesList(
messages: messages,
currentUserId: currentUserId,
currentUserName: 'You',
otherPartyName: otherPartyName,
otherPartyAvatar: otherPartyAvatar,
otherPartyHeadline: otherPartyHeadline,
isOnline: isOnline,
isTyping: isTyping,
isLoadingMore: isLoadingMore,
);
}
// ============================================================
// PROFILE SECTION (large avatar, name, specialties)
// ============================================================
Widget _buildProfileSection({
required String name,
String? avatar,
String? headline,
required bool isOnline,
}) {
final avatarUrl = _resolveAvatarUrl(avatar);
final initials = _getInitials(name);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 22, vertical: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Large avatar with online dot
Stack(
children: [
ClipOval(
child: avatarUrl.isNotEmpty
? CachedNetworkImage(
imageUrl: avatarUrl,
width: 80,
height: 80,
fit: BoxFit.cover,
errorWidget: (_, __, ___) =>
_AvatarFallback(letter: initials, size: 80),
)
: _AvatarFallback(letter: initials, size: 80),
),
if (isOnline)
Positioned(
right: 2,
bottom: 2,
child: Container(
width: 14,
height: 14,
decoration: BoxDecoration(
color: const Color(0xFF4CAF50),
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2.5),
),
),
),
],
),
const SizedBox(height: 14),
// Name with pronouns and orange dot
Row(
children: [
Text(
name,
style: const TextStyle(
fontFamily: 'Fractul',
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.primaryDark,
),
),
const SizedBox(width: 4),
const Text(
'(He/Him)',
style: TextStyle(
fontFamily: 'Fractul',
fontSize: 14,
fontWeight: FontWeight.w400,
color: AppColors.primaryDark,
),
),
const SizedBox(width: 6),
Container(
width: 10,
height: 10,
decoration: const BoxDecoration(
color: AppColors.accentOrange,
shape: BoxShape.circle,
),
),
],
),
const SizedBox(height: 12),
// Specialties row
if (headline != null && headline.isNotEmpty)
_buildSpecialtiesRow(headline)
else
_buildSpecialtiesRow('Sales,Analytics,Inspection,Residential,Commercial'),
const SizedBox(height: 12),
// Divider
Divider(
color: AppColors.primaryDark.withValues(alpha: 0.1),
thickness: 0.5,
),
],
),
);
}
Widget _buildSpecialtiesRow(String headline) {
// Split by comma, pipe, or similar separators
final specialties = headline.split(RegExp(r'[,|;]'))
.map((s) => s.trim())
.where((s) => s.isNotEmpty)
.toList();
if (specialties.isEmpty) return const SizedBox.shrink();
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
for (int i = 0; i < specialties.length; i++) ...[
Text(
specialties[i],
style: const TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 14,
fontWeight: FontWeight.w400,
color: AppColors.primaryDark,
),
),
if (i < specialties.length - 1)
const SizedBox(width: 16),
],
],
), ),
); );
} }
@@ -352,16 +512,19 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
required String currentUserName, required String currentUserName,
required String otherPartyName, required String otherPartyName,
String? otherPartyAvatar, String? otherPartyAvatar,
String? otherPartyHeadline,
required bool isOnline,
required bool isTyping, required bool isTyping,
required bool isLoadingMore, required bool isLoadingMore,
}) { }) {
// +1 for the profile section header at the end of reversed list
final itemCount = final itemCount =
messages.length + (isLoadingMore ? 1 : 0) + (isTyping ? 1 : 0); messages.length + 1 + (isLoadingMore ? 1 : 0) + (isTyping ? 1 : 0);
return ListView.builder( return ListView.builder(
controller: _scrollController, controller: _scrollController,
reverse: true, reverse: true,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 0),
itemCount: itemCount, itemCount: itemCount,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (isTyping && index == 0) { if (isTyping && index == 0) {
@@ -370,6 +533,17 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
final adjustedIndex = isTyping ? index - 1 : index; final adjustedIndex = isTyping ? index - 1 : index;
// Profile section at the top (last index in reversed list)
final profileIndex = messages.length + (isLoadingMore ? 1 : 0);
if (adjustedIndex == profileIndex) {
return _buildProfileSection(
name: otherPartyName,
avatar: otherPartyAvatar,
headline: otherPartyHeadline,
isOnline: isOnline,
);
}
if (isLoadingMore && adjustedIndex == messages.length) { if (isLoadingMore && adjustedIndex == messages.length) {
return const Padding( return const Padding(
padding: EdgeInsets.symmetric(vertical: 16), padding: EdgeInsets.symmetric(vertical: 16),
@@ -404,7 +578,9 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
: otherPartyName; : otherPartyName;
final senderAvatar = isMine ? null : otherPartyAvatar; final senderAvatar = isMine ? null : otherPartyAvatar;
return Column( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [ children: [
if (showDateSeparator) if (showDateSeparator)
_buildDateSeparator( _buildDateSeparator(
@@ -417,6 +593,7 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
: _buildReceivedMessage(message, senderName, senderAvatar), : _buildReceivedMessage(message, senderName, senderAvatar),
), ),
], ],
),
); );
}, },
); );
@@ -495,6 +672,16 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
), ),
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
const Text(
'He/Him',
style: TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 10,
fontWeight: FontWeight.w400,
color: AppColors.primaryDark,
),
),
const SizedBox(width: 6),
Container( Container(
width: 8, width: 8,
height: 8, height: 8,
@@ -567,6 +754,16 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
), ),
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
const Text(
'He/Him',
style: TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 10,
fontWeight: FontWeight.w400,
color: AppColors.primaryDark,
),
),
const SizedBox(width: 6),
Text( Text(
timestamp, timestamp,
style: const TextStyle( style: const TextStyle(
@@ -667,21 +864,12 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
// Plus icon in circle border // Plus icon (plain, no border)
GestureDetector( GestureDetector(
onTap: _showAttachmentOptions, onTap: _showAttachmentOptions,
child: Container( behavior: HitTestBehavior.opaque,
width: 36, child: const Padding(
height: 36, padding: EdgeInsets.only(right: 10),
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
border: Border.all(
color: AppColors.primaryDark,
width: 0.5,
),
),
child: const Center(
child: Icon( child: Icon(
Icons.add, Icons.add,
size: 24, size: 24,
@@ -689,19 +877,17 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
), ),
), ),
), ),
),
const SizedBox(width: 10),
// Text input // Text input pill with send icon inside
Expanded( Expanded(
child: Container( child: Container(
height: 38, height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all( border: Border.all(
color: AppColors.primaryDark, color: AppColors.primaryDark,
width: 0.5, width: 1.5,
), ),
), ),
child: Row( child: Row(
@@ -736,7 +922,7 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
), ),
), ),
), ),
// Send icon inside the input field // Send arrow inside the pill
GestureDetector( GestureDetector(
onTap: _sendMessage, onTap: _sendMessage,
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
@@ -755,25 +941,21 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
// Mic icon in circle border // Mic icon in filled dark circle
GestureDetector( GestureDetector(
onTap: () => _showComingSoon('Speech to text'), onTap: () => _showComingSoon('Speech to text'),
child: Container( child: Container(
width: 36, width: 36,
height: 36, height: 36,
decoration: BoxDecoration( decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
border: Border.all(
color: AppColors.primaryDark, color: AppColors.primaryDark,
width: 0.5, shape: BoxShape.circle,
),
), ),
child: const Center( child: const Center(
child: Icon( child: Icon(
Icons.mic_none, Icons.mic_none,
size: 20, size: 20,
color: AppColors.primaryDark, color: Colors.white,
), ),
), ),
), ),
@@ -873,6 +1055,7 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
); );
} }
} }
// -- Avatar Fallback -- // -- Avatar Fallback --

View File

@@ -52,16 +52,11 @@ class _ConversationsScreenState extends ConsumerState<ConversationsScreen> {
final state = ref.watch(messagingProvider); final state = ref.watch(messagingProvider);
final filtered = _filteredConversations(state.conversations); final filtered = _filteredConversations(state.conversations);
return Scaffold( return Column(
backgroundColor: Colors.white,
body: SafeArea(
child: Column(
children: [ children: [
_buildHeader(), _buildHeader(),
Expanded(child: _buildConversationList(state, filtered)), Expanded(child: _buildConversationList(state, filtered)),
], ],
),
),
); );
} }
@@ -317,12 +312,13 @@ class _ConversationsScreenState extends ConsumerState<ConversationsScreen> {
return _ConversationTile( return _ConversationTile(
conversation: filtered[index], conversation: filtered[index],
onTap: () => onTap: () =>
context.push('/messages/chat/${filtered[index].id}'), context.go('/messages/chat/${filtered[index].id}'),
); );
}, },
), ),
); );
} }
} }
// -- Conversation Tile Widget -- // -- Conversation Tile Widget --

View File

@@ -0,0 +1,117 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
import 'package:real_estate_mobile/core/constants/app_colors.dart';
import 'package:real_estate_mobile/features/auth/presentation/providers/auth_provider.dart';
import 'package:real_estate_mobile/features/home/presentation/widgets/home_header.dart';
class MessagingShell extends ConsumerWidget {
final Widget child;
const MessagingShell({super.key, required this.child});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
backgroundColor: Colors.white,
body: Column(
children: [
SafeArea(
bottom: false,
child: const HomeHeader(),
),
Expanded(child: child),
],
),
bottomNavigationBar: _buildBottomNavBar(context, ref),
);
}
Widget _buildBottomNavBar(BuildContext context, WidgetRef ref) {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
border: Border(
top: BorderSide(color: Color(0xFFE8E8E8), width: 1),
),
),
child: SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildNavItem(
context: context,
svgPath: 'assets/icons/nav_home_icon.svg',
fallbackIcon: Icons.home,
isSelected: false,
onTap: () => context.go('/home'),
),
_buildNavItem(
context: context,
svgPath: 'assets/icons/nav_list_icon.svg',
fallbackIcon: Icons.list,
isSelected: false,
onTap: () => context.push('/agents/search'),
),
_buildNavItem(
context: context,
svgPath: 'assets/icons/nav_messages_icon.svg',
fallbackIcon: Icons.chat_bubble,
isSelected: true,
onTap: () => context.go('/messages'),
),
_buildNavItem(
context: context,
svgPath: 'assets/icons/nav_profile_icon.svg',
fallbackIcon: Icons.person_outline,
isSelected: false,
onTap: () {
final authState = ref.read(authProvider);
if (authState.status != AuthStatus.authenticated) {
context.push('/login');
return;
}
context.push('/profile');
},
),
],
),
),
),
);
}
Widget _buildNavItem({
required BuildContext context,
required String svgPath,
required IconData fallbackIcon,
required bool isSelected,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: SvgPicture.asset(
svgPath,
width: 24,
height: 24,
colorFilter: isSelected
? const ColorFilter.mode(AppColors.primaryDark, BlendMode.srcIn)
: const ColorFilter.mode(
AppColors.accentOrange, BlendMode.srcIn),
placeholderBuilder: (_) => Icon(
fallbackIcon,
size: 24,
color: isSelected ? AppColors.primaryDark : AppColors.accentOrange,
),
),
),
);
}
}

View File

@@ -0,0 +1,66 @@
import 'package:dio/dio.dart';
import 'package:real_estate_mobile/core/network/api_client.dart';
import 'package:real_estate_mobile/core/network/api_exceptions.dart';
class ProfileRepository {
final Dio _dio = ApiClient.instance.dio;
/// Fetch profile based on role.
/// Agent: GET /agents/profile/me
/// User: GET /users/profile/me
Future<Map<String, dynamic>> getMyProfile(String role) async {
try {
final endpoint = role == 'AGENT'
? '/agents/profile/me'
: '/users/profile/me';
final response = await _dio.get(endpoint);
return response.data['data'] as Map<String, dynamic>;
} on DioException catch (e) {
if (e.error is ApiException) throw e.error as ApiException;
throw ApiException(
message: e.message ?? 'Failed to fetch profile',
statusCode: e.response?.statusCode,
);
}
}
/// Update profile based on role.
/// Agent: PUT /agents/profile
/// User: PATCH /users/profile/me
Future<Map<String, dynamic>> updateProfile(
String role,
Map<String, dynamic> data,
) async {
try {
final Response response;
if (role == 'AGENT') {
response = await _dio.put('/agents/profile', data: data);
} else {
response = await _dio.patch('/users/profile/me', data: data);
}
return response.data['data'] as Map<String, dynamic>;
} on DioException catch (e) {
if (e.error is ApiException) throw e.error as ApiException;
throw ApiException(
message: e.message ?? 'Failed to update profile',
statusCode: e.response?.statusCode,
);
}
}
Future<String> getAvatarUploadUrl(String role, String fileName) async {
try {
final endpoint = role == 'AGENT'
? '/upload/avatar-presigned-url'
: '/upload/user-avatar-presigned-url';
final response = await _dio.post(endpoint, data: {'fileName': fileName});
return response.data['data']['url'] as String;
} on DioException catch (e) {
if (e.error is ApiException) throw e.error as ApiException;
throw ApiException(
message: e.message ?? 'Failed to get upload URL',
statusCode: e.response?.statusCode,
);
}
}
}

View File

@@ -0,0 +1,98 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:real_estate_mobile/features/auth/presentation/providers/auth_provider.dart';
import 'package:real_estate_mobile/features/profile/data/profile_repository.dart';
// Profile state
class ProfileState {
final String role;
final Map<String, dynamic> profile;
final bool isLoading;
final bool isSaving;
final String? errorMessage;
final String? successMessage;
const ProfileState({
this.role = 'USER',
this.profile = const {},
this.isLoading = true,
this.isSaving = false,
this.errorMessage,
this.successMessage,
});
bool get isAgent => role == 'AGENT';
ProfileState copyWith({
String? role,
Map<String, dynamic>? profile,
bool? isLoading,
bool? isSaving,
String? errorMessage,
String? successMessage,
bool clearError = false,
bool clearSuccess = false,
}) {
return ProfileState(
role: role ?? this.role,
profile: profile ?? this.profile,
isLoading: isLoading ?? this.isLoading,
isSaving: isSaving ?? this.isSaving,
errorMessage: clearError ? null : (errorMessage ?? this.errorMessage),
successMessage:
clearSuccess ? null : (successMessage ?? this.successMessage),
);
}
}
class ProfileNotifier extends StateNotifier<ProfileState> {
final ProfileRepository _repository;
final String _role;
ProfileNotifier(this._repository, this._role)
: super(ProfileState(role: _role)) {
loadProfile();
}
Future<void> loadProfile() async {
state = state.copyWith(isLoading: true, clearError: true);
try {
final profile = await _repository.getMyProfile(_role);
state = state.copyWith(profile: profile, isLoading: false);
} catch (e) {
state = state.copyWith(
isLoading: false,
errorMessage: e.toString(),
);
}
}
Future<void> updateProfile(Map<String, dynamic> data) async {
state =
state.copyWith(isSaving: true, clearError: true, clearSuccess: true);
try {
final updated = await _repository.updateProfile(_role, data);
state = state.copyWith(
profile: updated,
isSaving: false,
successMessage: 'Profile updated successfully',
);
} catch (e) {
state = state.copyWith(
isSaving: false,
errorMessage: 'Failed to update profile',
);
}
}
}
// Providers
final profileRepositoryProvider = Provider<ProfileRepository>((ref) {
return ProfileRepository();
});
final profileProvider =
StateNotifierProvider.autoDispose<ProfileNotifier, ProfileState>((ref) {
final authState = ref.watch(authProvider);
final role = authState.user?.role ?? 'USER';
return ProfileNotifier(ref.watch(profileRepositoryProvider), role);
});

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,9 @@ import 'package:real_estate_mobile/features/faq/presentation/screens/faq_screen.
import 'package:real_estate_mobile/features/home/presentation/screens/home_screen.dart'; import 'package:real_estate_mobile/features/home/presentation/screens/home_screen.dart';
import 'package:real_estate_mobile/features/messaging/presentation/screens/conversations_screen.dart'; import 'package:real_estate_mobile/features/messaging/presentation/screens/conversations_screen.dart';
import 'package:real_estate_mobile/features/messaging/presentation/screens/chat_screen.dart'; import 'package:real_estate_mobile/features/messaging/presentation/screens/chat_screen.dart';
import 'package:real_estate_mobile/features/messaging/presentation/screens/messaging_shell.dart';
import 'package:real_estate_mobile/features/contact/presentation/screens/contact_screen.dart';
import 'package:real_estate_mobile/features/profile/presentation/screens/profile_settings_screen.dart';
final routerProvider = Provider<GoRouter>((ref) { final routerProvider = Provider<GoRouter>((ref) {
final authRefreshNotifier = _AuthRefreshNotifier(); final authRefreshNotifier = _AuthRefreshNotifier();
@@ -23,7 +26,7 @@ final routerProvider = Provider<GoRouter>((ref) {
}); });
// Public routes accessible without authentication (matching web middleware) // Public routes accessible without authentication (matching web middleware)
const publicRoutes = ['/home', '/agents/search', '/agents/detail', '/faq']; const publicRoutes = ['/home', '/agents/search', '/agents/detail', '/faq', '/contact'];
const authRoutes = ['/login', '/signup']; const authRoutes = ['/login', '/signup'];
return GoRouter( return GoRouter(
@@ -81,6 +84,9 @@ final routerProvider = Provider<GoRouter>((ref) {
return AgentDetailScreen(agentId: id); return AgentDetailScreen(agentId: id);
}, },
), ),
ShellRoute(
builder: (context, state, child) => MessagingShell(child: child),
routes: [
GoRoute( GoRoute(
path: '/messages', path: '/messages',
builder: (context, state) => const ConversationsScreen(), builder: (context, state) => const ConversationsScreen(),
@@ -92,10 +98,20 @@ final routerProvider = Provider<GoRouter>((ref) {
return ChatScreen(conversationId: id); return ChatScreen(conversationId: id);
}, },
), ),
],
),
GoRoute( GoRoute(
path: '/faq', path: '/faq',
builder: (context, state) => const FaqScreen(), builder: (context, state) => const FaqScreen(),
), ),
GoRoute(
path: '/contact',
builder: (context, state) => const ContactScreen(),
),
GoRoute(
path: '/profile',
builder: (context, state) => const ProfileSettingsScreen(),
),
], ],
); );
}); });