feat: Implement contact form clearing, refactor chat avatar display to use S3Image, add unread count to notification filter, and enhance agent detail layout.

This commit is contained in:
pradeepkumar
2026-03-15 00:03:38 +05:30
parent de4a7b8040
commit fed89cbef2
4 changed files with 97 additions and 67 deletions

View File

@@ -479,6 +479,8 @@ class _AgentDetailScreenState extends ConsumerState<AgentDetailScreen> {
fontSize: 14,
color: AppColors.primaryDark,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 8),
@@ -502,13 +504,14 @@ class _AgentDetailScreenState extends ConsumerState<AgentDetailScreen> {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Name + Verified badge + (Verified Expert) — badge after last name
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
Wrap(
alignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 4,
runSpacing: 4,
children: [
Text(
agent.firstName,
'${agent.firstName} ${agent.lastName}',
style: const TextStyle(
fontFamily: 'Fractul',
fontSize: 18,
@@ -516,28 +519,17 @@ class _AgentDetailScreenState extends ConsumerState<AgentDetailScreen> {
color: AppColors.primaryDark,
),
),
const SizedBox(width: 4),
Text(
agent.lastName,
style: const TextStyle(
fontFamily: 'Fractul',
fontSize: 18,
fontWeight: FontWeight.w400,
color: AppColors.primaryDark,
),
),
if (agent.isVerified) ...[
const SizedBox(width: 6),
const Padding(
padding: EdgeInsets.only(bottom: 1),
child: Icon(
Row(
mainAxisSize: MainAxisSize.min,
children: const [
Icon(
Icons.verified,
size: 16,
color: Color(0xFF2196F3),
),
),
const SizedBox(width: 6),
const Text(
SizedBox(width: 4),
Text(
'(Verified Expert)',
style: TextStyle(
fontFamily: 'SourceSerif4',
@@ -547,6 +539,8 @@ class _AgentDetailScreenState extends ConsumerState<AgentDetailScreen> {
),
),
],
),
],
],
),
// Title

View File

@@ -241,7 +241,12 @@ class _ContactScreenState extends State<ContactScreen> {
children: [
Expanded(
child: GestureDetector(
onTap: () => context.pop(),
onTap: () {
_nameController.clear();
_emailController.clear();
_phoneController.clear();
_messageController.clear();
},
child: Container(
height: 46,
decoration: BoxDecoration(

View File

@@ -2,7 +2,6 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.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/core/constants/app_colors.dart';
import 'package:real_estate_mobile/core/utils/image_url_resolver.dart';
import 'package:real_estate_mobile/core/widgets/s3_image.dart';
@@ -149,18 +148,6 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
return '${parts[0][0]}${parts[1][0]}'.toUpperCase();
}
String _resolveAvatarUrl(String? avatarKey) {
if (avatarKey == null || avatarKey.isEmpty) return '';
if (avatarKey.startsWith('http://') || avatarKey.startsWith('https://')) {
return avatarKey;
}
final base = AppConfig.storageBaseUrl;
if (avatarKey.startsWith('/uploads')) {
return '$base$avatarKey';
}
return '$base/$avatarKey';
}
String _formatFileSize(int? bytes) {
if (bytes == null || bytes == 0) return '';
if (bytes < 1024) return '$bytes B';
@@ -209,6 +196,7 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
_buildHeader(
name: otherPartyName,
isOnline: isOnline,
avatar: otherPartyAvatar,
),
Expanded(
child: messagingState.isLoadingMessages && messages.isEmpty
@@ -242,7 +230,10 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
Widget _buildHeader({
required String name,
required bool isOnline,
String? avatar,
}) {
final initials = _getInitials(name);
return Padding(
padding: const EdgeInsets.fromLTRB(12, 10, 12, 6),
child: Row(
@@ -252,7 +243,7 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
onTap: _handleBack,
behavior: HitTestBehavior.opaque,
child: const Padding(
padding: EdgeInsets.only(right: 16),
padding: EdgeInsets.only(right: 12),
child: Icon(
Icons.arrow_back,
size: 22,
@@ -260,6 +251,38 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
),
),
),
// Avatar
Stack(
children: [
ClipOval(
child: (avatar != null && avatar.isNotEmpty)
? S3Image(
imageUrl: avatar,
width: 40,
height: 40,
fit: BoxFit.cover,
errorWidget: (_) =>
_AvatarFallback(letter: initials, size: 40),
)
: _AvatarFallback(letter: initials, size: 40),
),
if (isOnline)
Positioned(
right: 0,
bottom: 0,
child: Container(
width: 10,
height: 10,
decoration: BoxDecoration(
color: const Color(0xFF4CAF50),
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
),
),
),
],
),
const SizedBox(width: 10),
// Name and active status
Expanded(
child: Column(
@@ -398,7 +421,6 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
String? headline,
required bool isOnline,
}) {
final avatarUrl = _resolveAvatarUrl(avatar);
final initials = _getInitials(name);
return Padding(
@@ -410,13 +432,13 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
Stack(
children: [
ClipOval(
child: avatarUrl.isNotEmpty
? CachedNetworkImage(
imageUrl: avatarUrl,
child: (avatar != null && avatar.isNotEmpty)
? S3Image(
imageUrl: avatar,
width: 80,
height: 80,
fit: BoxFit.cover,
errorWidget: (_, e, s) =>
errorWidget: (_) =>
_AvatarFallback(letter: initials, size: 80),
)
: _AvatarFallback(letter: initials, size: 80),
@@ -440,8 +462,10 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
const SizedBox(height: 14),
// Name with pronouns and orange dot
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
Flexible(
child: Text(
name,
style: const TextStyle(
fontFamily: 'Fractul',
@@ -449,8 +473,10 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
fontWeight: FontWeight.w600,
color: AppColors.primaryDark,
),
overflow: TextOverflow.ellipsis,
),
const SizedBox(width: 4),
),
const SizedBox(width: 6),
const Text(
'(He/Him)',
style: TextStyle(
@@ -1041,17 +1067,16 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
// -- Message Avatar --
Widget _buildMessageAvatar(String? avatarKey, String name, double size) {
final avatarUrl = _resolveAvatarUrl(avatarKey);
final initials = _getInitials(name);
return ClipOval(
child: avatarUrl.isNotEmpty
? CachedNetworkImage(
imageUrl: avatarUrl,
child: (avatarKey != null && avatarKey.isNotEmpty)
? S3Image(
imageUrl: avatarKey,
width: size,
height: size,
fit: BoxFit.cover,
errorWidget: (_, e, s) =>
errorWidget: (_) =>
_AvatarFallback(letter: initials, size: size),
)
: _AvatarFallback(letter: initials, size: size),

View File

@@ -83,7 +83,13 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
children: [
_buildFilterChip('All', 'all'),
const SizedBox(width: 8),
_buildFilterChip('Unread', 'unread'),
Builder(builder: (context) {
final unreadCount = ref.watch(unreadCountProvider);
final label = unreadCount > 0
? 'Unread ($unreadCount)'
: 'Unread';
return _buildFilterChip(label, 'unread');
}),
const Spacer(),
GestureDetector(
onTap: () {