feat: Implement optimistic UI for agent availability toggle, add email and phone visibility toggles, and improve the logout process with immediate socket disconnection and best-effort API calls.

This commit is contained in:
pradeepkumar
2026-03-15 00:35:39 +05:30
parent 54fdd66270
commit 21f669fca5
6 changed files with 104 additions and 14 deletions

View File

@@ -196,6 +196,38 @@ class AgentProfile {
return tags;
}
AgentProfile copyWith({bool? isAvailable}) {
return AgentProfile(
id: id,
userId: userId,
firstName: firstName,
lastName: lastName,
headline: headline,
bio: bio,
avatar: avatar,
companyName: companyName,
phone: phone,
website: website,
city: city,
state: state,
country: country,
averageRating: averageRating,
totalReviews: totalReviews,
isVerified: isVerified,
isFeatured: isFeatured,
isActive: isActive,
isAvailable: isAvailable ?? this.isAvailable,
email: email,
agentType: agentType,
user: user,
fieldValues: fieldValues,
createdAt: createdAt,
legacySpecializations: legacySpecializations,
languages: languages,
serviceAreas: serviceAreas,
);
}
factory AgentProfile.fromJson(Map<String, dynamic> json) {
return AgentProfile(
id: json['id'] as String,

View File

@@ -433,6 +433,13 @@ class AgentDetailNotifier extends StateNotifier<AgentDetailState> {
return success;
}
void updateAvailability(bool isAvailable) {
if (state.agent == null) return;
state = state.copyWith(
agent: state.agent!.copyWith(isAvailable: isAvailable),
);
}
Future<bool> cancelConnection() async {
final requestId = state.connectionRequestId;
if (requestId == null) return false;

View File

@@ -87,6 +87,8 @@ class _AgentHomeContent extends ConsumerStatefulWidget {
class _AgentHomeContentState extends ConsumerState<_AgentHomeContent> {
bool _isUploadingAvatar = false;
bool _isTogglingAvailability = false;
bool _showEmail = false;
bool _showPhone = false;
File? _localAvatarFile;
Future<void> _pickAndUploadAvatar() async {
@@ -179,13 +181,21 @@ class _AgentHomeContentState extends ConsumerState<_AgentHomeContent> {
if (_isTogglingAvailability) return;
setState(() => _isTogglingAvailability = true);
// Optimistically update the UI immediately
ref
.read(agentDetailProvider(widget.agentProfileId).notifier)
.updateAvailability(newValue);
try {
final repo = ProfileRepository();
await repo.updateProfile('AGENT', {'isAvailable': newValue});
// Refresh agent detail to reflect new status
ref.invalidate(agentDetailProvider(widget.agentProfileId));
} catch (e) {
// Revert on failure
if (mounted) {
ref
.read(agentDetailProvider(widget.agentProfileId).notifier)
.updateAvailability(!newValue);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to update availability: $e')),
);
@@ -475,15 +485,32 @@ class _AgentHomeContentState extends ConsumerState<_AgentHomeContent> {
const SizedBox(width: 16),
Expanded(
child: Text(
email.isNotEmpty ? state.maskEmail(email) : '-',
email.isNotEmpty
? (_showEmail ? email : state.maskEmail(email))
: '-',
style: const TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 14,
color: AppColors.primaryDark),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const Icon(Icons.visibility_off_outlined,
size: 18, color: AppColors.primaryDark),
if (email.isNotEmpty)
GestureDetector(
onTap: () => setState(() => _showEmail = !_showEmail),
behavior: HitTestBehavior.opaque,
child: Padding(
padding: const EdgeInsets.all(4),
child: Icon(
_showEmail
? Icons.visibility_outlined
: Icons.visibility_off_outlined,
size: 18,
color: AppColors.primaryDark,
),
),
),
],
),
const SizedBox(height: 14),
@@ -498,15 +525,32 @@ class _AgentHomeContentState extends ConsumerState<_AgentHomeContent> {
const SizedBox(width: 16),
Expanded(
child: Text(
phone.isNotEmpty ? state.maskPhone(phone) : '-',
phone.isNotEmpty
? (_showPhone ? phone : state.maskPhone(phone))
: '-',
style: const TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 14,
color: AppColors.primaryDark),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const Icon(Icons.visibility_off_outlined,
size: 18, color: AppColors.primaryDark),
if (phone.isNotEmpty)
GestureDetector(
onTap: () => setState(() => _showPhone = !_showPhone),
behavior: HitTestBehavior.opaque,
child: Padding(
padding: const EdgeInsets.all(4),
child: Icon(
_showPhone
? Icons.visibility_outlined
: Icons.visibility_off_outlined,
size: 18,
color: AppColors.primaryDark,
),
),
),
],
),
],