feat: Enhance agent profile data model with new fields and improved data derivation, and update agent search screen UI and icon.
This commit is contained in:
@@ -23,6 +23,9 @@ class AgentProfile {
|
|||||||
final AgentUser? user;
|
final AgentUser? user;
|
||||||
final List<AgentFieldValue> fieldValues;
|
final List<AgentFieldValue> fieldValues;
|
||||||
final String? createdAt;
|
final String? createdAt;
|
||||||
|
final List<String> legacySpecializations;
|
||||||
|
final List<String> languages;
|
||||||
|
final List<String> serviceAreas;
|
||||||
|
|
||||||
const AgentProfile({
|
const AgentProfile({
|
||||||
required this.id,
|
required this.id,
|
||||||
@@ -47,6 +50,9 @@ class AgentProfile {
|
|||||||
this.user,
|
this.user,
|
||||||
this.fieldValues = const [],
|
this.fieldValues = const [],
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
|
this.legacySpecializations = const [],
|
||||||
|
this.languages = const [],
|
||||||
|
this.serviceAreas = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
String get fullName => '$firstName $lastName'.trim();
|
String get fullName => '$firstName $lastName'.trim();
|
||||||
@@ -69,6 +75,16 @@ class AgentProfile {
|
|||||||
/// Get avatar URL — check agent avatar first, then user avatar
|
/// Get avatar URL — check agent avatar first, then user avatar
|
||||||
String? get avatarUrl => avatar ?? user?.avatar;
|
String? get avatarUrl => avatar ?? user?.avatar;
|
||||||
|
|
||||||
|
/// Get description — check fieldValues 'description' first, then bio (matches web)
|
||||||
|
String get description {
|
||||||
|
for (final fv in fieldValues) {
|
||||||
|
if (fv.fieldSlug == 'description' && fv.textValue != null && fv.textValue!.isNotEmpty) {
|
||||||
|
return fv.textValue!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bio ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
String get location {
|
String get location {
|
||||||
// Direct fields first
|
// Direct fields first
|
||||||
if (city != null && state != null) return '$city, $state';
|
if (city != null && state != null) return '$city, $state';
|
||||||
@@ -89,6 +105,9 @@ class AgentProfile {
|
|||||||
final parts = [fvCity, fvState].where((s) => s != null && s.isNotEmpty);
|
final parts = [fvCity, fvState].where((s) => s != null && s.isNotEmpty);
|
||||||
if (parts.isNotEmpty) return parts.join(', ');
|
if (parts.isNotEmpty) return parts.join(', ');
|
||||||
|
|
||||||
|
if (country != null && country!.isNotEmpty) return country!;
|
||||||
|
if (serviceAreas.isNotEmpty) return serviceAreas.first;
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,6 +178,17 @@ class AgentProfile {
|
|||||||
if (!tags.contains(display)) tags.add(display);
|
if (!tags.contains(display)) tags.add(display);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add from legacy specializations array
|
||||||
|
for (final spec in legacySpecializations) {
|
||||||
|
if (spec.isNotEmpty && !tags.contains(spec)) tags.add(spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add from languages array
|
||||||
|
for (final lang in languages) {
|
||||||
|
if (lang.isNotEmpty && !tags.contains(lang)) tags.add(lang);
|
||||||
|
}
|
||||||
|
|
||||||
return tags;
|
return tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,6 +223,18 @@ class AgentProfile {
|
|||||||
.toList() ??
|
.toList() ??
|
||||||
const [],
|
const [],
|
||||||
createdAt: json['createdAt'] as String?,
|
createdAt: json['createdAt'] as String?,
|
||||||
|
legacySpecializations: (json['specializations'] as List<dynamic>?)
|
||||||
|
?.map((e) => e.toString())
|
||||||
|
.toList() ??
|
||||||
|
const [],
|
||||||
|
languages: (json['languages'] as List<dynamic>?)
|
||||||
|
?.map((e) => e.toString())
|
||||||
|
.toList() ??
|
||||||
|
const [],
|
||||||
|
serviceAreas: (json['serviceAreas'] as List<dynamic>?)
|
||||||
|
?.map((e) => e.toString())
|
||||||
|
.toList() ??
|
||||||
|
const [],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,11 +283,14 @@ class AgentFieldValue {
|
|||||||
});
|
});
|
||||||
|
|
||||||
factory AgentFieldValue.fromJson(Map<String, dynamic> json) {
|
factory AgentFieldValue.fromJson(Map<String, dynamic> json) {
|
||||||
|
// API returns field info nested: { field: { slug, name, fieldType } }
|
||||||
|
// Fallback to top-level fieldSlug/fieldName/fieldType for compatibility
|
||||||
|
final field = json['field'] as Map<String, dynamic>?;
|
||||||
return AgentFieldValue(
|
return AgentFieldValue(
|
||||||
id: json['id'] as String? ?? '',
|
id: json['id'] as String? ?? '',
|
||||||
fieldSlug: json['fieldSlug'] as String? ?? '',
|
fieldSlug: field?['slug'] as String? ?? json['fieldSlug'] as String? ?? '',
|
||||||
fieldName: json['fieldName'] as String? ?? '',
|
fieldName: field?['name'] as String? ?? json['fieldName'] as String? ?? '',
|
||||||
fieldType: json['fieldType'] as String? ?? '',
|
fieldType: field?['fieldType'] as String? ?? json['fieldType'] as String? ?? '',
|
||||||
textValue: json['textValue'] as String?,
|
textValue: json['textValue'] as String?,
|
||||||
numberValue: json['numberValue'] as num?,
|
numberValue: json['numberValue'] as num?,
|
||||||
booleanValue: json['booleanValue'] as bool?,
|
booleanValue: json['booleanValue'] as bool?,
|
||||||
|
|||||||
@@ -86,39 +86,49 @@ class _AgentSearchScreenState extends ConsumerState<AgentSearchScreen> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: AppColors.primaryDark,
|
color: AppColors.primaryDark,
|
||||||
width: 0.1,
|
width: 0.1,
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(7),
|
borderRadius: BorderRadius.circular(7),
|
||||||
),
|
),
|
||||||
child: TextField(
|
child: Theme(
|
||||||
controller: _searchController,
|
data: Theme.of(context).copyWith(
|
||||||
style: const TextStyle(
|
inputDecorationTheme: const InputDecorationTheme(
|
||||||
fontFamily: 'Fractul',
|
filled: false,
|
||||||
fontSize: 14,
|
border: InputBorder.none,
|
||||||
fontWeight: FontWeight.w500,
|
),
|
||||||
color: AppColors.primaryDark,
|
|
||||||
),
|
),
|
||||||
decoration: InputDecoration(
|
child: TextField(
|
||||||
hintText: 'Search Agents',
|
controller: _searchController,
|
||||||
hintStyle: TextStyle(
|
style: const TextStyle(
|
||||||
fontFamily: 'Fractul',
|
fontFamily: 'Fractul',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Colors.black.withValues(alpha: 0.5),
|
color: AppColors.primaryDark,
|
||||||
),
|
),
|
||||||
border: InputBorder.none,
|
decoration: InputDecoration(
|
||||||
enabledBorder: InputBorder.none,
|
hintText: 'Search Agents',
|
||||||
focusedBorder: InputBorder.none,
|
hintStyle: TextStyle(
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
fontFamily: 'Fractul',
|
||||||
horizontal: 14,
|
fontSize: 14,
|
||||||
vertical: 10,
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.black.withValues(alpha: 0.5),
|
||||||
|
),
|
||||||
|
filled: false,
|
||||||
|
border: InputBorder.none,
|
||||||
|
enabledBorder: InputBorder.none,
|
||||||
|
focusedBorder: InputBorder.none,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 14,
|
||||||
|
vertical: 10,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
onSubmitted: (value) {
|
||||||
|
ref.read(searchAgentsProvider.notifier).search(value);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
onSubmitted: (value) {
|
|
||||||
ref.read(searchAgentsProvider.notifier).search(value);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -140,7 +150,13 @@ class _AgentSearchScreenState extends ConsumerState<AgentSearchScreen> {
|
|||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(7),
|
borderRadius: BorderRadius.circular(7),
|
||||||
),
|
),
|
||||||
child: const Icon(Icons.search, color: Colors.white, size: 24),
|
child: Center(
|
||||||
|
child: SvgPicture.string(
|
||||||
|
'<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" fill="white"/></svg>',
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -413,7 +429,7 @@ class _AgentCardState extends State<_AgentCard> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final agent = widget.agent;
|
final agent = widget.agent;
|
||||||
final specializations = agent.specializations;
|
final specializations = agent.specializations;
|
||||||
final bio = agent.bio ?? '';
|
final bio = agent.description;
|
||||||
final matchPercent = agent.averageRating != null
|
final matchPercent = agent.averageRating != null
|
||||||
? '${(agent.averageRating! * 20).round()}%'
|
? '${(agent.averageRating! * 20).round()}%'
|
||||||
: null;
|
: null;
|
||||||
|
|||||||
Reference in New Issue
Block a user