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:
@@ -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,17 +462,21 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
|
||||
const SizedBox(height: 14),
|
||||
// Name with pronouns and orange dot
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
name,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Fractul',
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.primaryDark,
|
||||
Flexible(
|
||||
child: Text(
|
||||
name,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Fractul',
|
||||
fontSize: 14,
|
||||
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),
|
||||
|
||||
Reference in New Issue
Block a user