feat: implement debounced live search with clear button, refactor UI components, and adjust layout spacing and image cropping across screens.

This commit is contained in:
pradeepkumar
2026-05-08 18:41:06 +05:30
parent 4be171c0a7
commit 50c20ce0ce
5 changed files with 72 additions and 41 deletions

View File

@@ -1,3 +1,5 @@
import 'dart:async';
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:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
@@ -31,6 +33,7 @@ class AgentSearchScreen extends ConsumerStatefulWidget {
class _AgentSearchScreenState extends ConsumerState<AgentSearchScreen> { class _AgentSearchScreenState extends ConsumerState<AgentSearchScreen> {
late TextEditingController _searchController; late TextEditingController _searchController;
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
Timer? _searchDebounce;
@override @override
void initState() { void initState() {
@@ -67,11 +70,22 @@ class _AgentSearchScreenState extends ConsumerState<AgentSearchScreen> {
@override @override
void dispose() { void dispose() {
_searchDebounce?.cancel();
_searchController.dispose(); _searchController.dispose();
_scrollController.dispose(); _scrollController.dispose();
super.dispose(); super.dispose();
} }
/// Debounced live search — fires search() while the user types so results
/// update incrementally. Empty value resets to the default listing.
void _onSearchChanged(String value) {
_searchDebounce?.cancel();
_searchDebounce = Timer(const Duration(milliseconds: 300), () {
if (!mounted) return;
ref.read(searchAgentsProvider.notifier).search(value.trim());
});
}
void _onScroll() { void _onScroll() {
if (_scrollController.position.pixels >= if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) { _scrollController.position.maxScrollExtent - 200) {
@@ -185,10 +199,35 @@ class _AgentSearchScreenState extends ConsumerState<AgentSearchScreen> {
horizontal: 14, horizontal: 14,
vertical: 10, vertical: 10,
), ),
// Clear (×) button — resets the listing instantly.
suffixIcon: _searchController.text.isNotEmpty
? IconButton(
icon: const Icon(
Icons.close,
size: 18,
color: AppColors.hintText,
), ),
onPressed: () {
_searchDebounce?.cancel();
_searchController.clear();
setState(() {});
ref
.read(searchAgentsProvider.notifier)
.search('');
},
)
: null,
),
onChanged: (value) {
// Toggle suffix-icon visibility on each keystroke.
setState(() {});
_onSearchChanged(value);
},
onSubmitted: (value) { onSubmitted: (value) {
if (value.trim().isEmpty) return; _searchDebounce?.cancel();
ref.read(searchAgentsProvider.notifier).search(value); ref
.read(searchAgentsProvider.notifier)
.search(value.trim());
}, },
), ),
), ),

View File

@@ -29,7 +29,7 @@ class _ContactScreenState extends State<ContactScreen> {
String _contactOfficeAddress = '123 Market Street'; String _contactOfficeAddress = '123 Market Street';
String _contactOfficeCity = 'New York CA 234737'; String _contactOfficeCity = 'New York CA 234737';
String _pageTitle = 'Get In Touch'; String _pageTitle = 'Get In Touch';
String _pageDescription = 'Have a question about a property or need assistance? Fill out the form below and our team will get back to you shortly.'; String _pageDescription = 'Need assistance? Fill out the form below and our team will get back to you shortly.';
String _directionsUrl = 'https://maps.google.com/?q=123+Market+Street+New+York+CA+234737'; String _directionsUrl = 'https://maps.google.com/?q=123+Market+Street+New+York+CA+234737';
@override @override

View File

@@ -375,36 +375,17 @@ class _MenuDrawer extends ConsumerWidget {
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
height: 54, height: 54,
child: ElevatedButton( child: ElevatedButton.icon(
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(context);
context.push('/login'); context.push('/login');
}, },
style: ElevatedButton.styleFrom( icon: const Icon(
backgroundColor: AppColors.accentOrange,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 20,
width: 20,
child: Center(
child: Icon(
Icons.login, Icons.login,
color: Colors.white, color: Colors.white,
size: 18, size: 20,
), ),
), label: const Text(
),
const SizedBox(width: 8),
const Text(
'Log In', 'Log In',
style: TextStyle( style: TextStyle(
fontFamily: 'Fractul', fontFamily: 'Fractul',
@@ -413,7 +394,15 @@ class _MenuDrawer extends ConsumerWidget {
color: Colors.white, color: Colors.white,
), ),
), ),
], style: ElevatedButton.styleFrom(
backgroundColor: AppColors.accentOrange,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(
horizontal: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
), ),
), ),
), ),

View File

@@ -105,9 +105,10 @@ class _TopProfessionalsSectionState
_buildEmptyState(_activeTab) _buildEmptyState(_activeTab)
else ...[ else ...[
// Professional Cards - horizontal scroll // Professional Cards - horizontal scroll
// Use screen-relative height so cards fit on all devices // Use screen-relative height so cards fit on all devices.
// Bumped to 0.72 to fit the taller image (300) + content area.
SizedBox( SizedBox(
height: MediaQuery.of(context).size.height * 0.64, height: MediaQuery.of(context).size.height * 0.72,
child: ListView.builder( child: ListView.builder(
// Unique key per tab forces Flutter to rebuild (not reuse) // Unique key per tab forces Flutter to rebuild (not reuse)
// the list items, preventing stale images from the other tab. // the list items, preventing stale images from the other tab.
@@ -318,10 +319,12 @@ class _TopProfessionalsSectionState
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// Image (226px per Figma) // Image — taller box for portrait photos so faces aren't cropped.
// Uses topCenter alignment as a fallback when the photo is much
// taller than the box (crops bottom, keeps the face visible).
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
height: 226, height: 300,
child: ClipRRect( child: ClipRRect(
borderRadius: const BorderRadius.vertical( borderRadius: const BorderRadius.vertical(
top: Radius.circular(15), top: Radius.circular(15),
@@ -334,9 +337,9 @@ class _TopProfessionalsSectionState
key: ValueKey('${_activeTab}_$imageUrl'), key: ValueKey('${_activeTab}_$imageUrl'),
imageUrl: imageUrl, imageUrl: imageUrl,
width: double.infinity, width: double.infinity,
height: 226, height: 300,
fit: BoxFit.cover, fit: BoxFit.cover,
alignment: Alignment.topCenter, alignment: const Alignment(0, -0.4),
errorWidget: (_) => _buildImagePlaceholder(index), errorWidget: (_) => _buildImagePlaceholder(index),
) )
: _buildImagePlaceholder(index), : _buildImagePlaceholder(index),
@@ -569,7 +572,7 @@ class _TopProfessionalsSectionState
children: [ children: [
// Image placeholder // Image placeholder
Container( Container(
height: 226, height: 300,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(15)), borderRadius: BorderRadius.vertical(top: Radius.circular(15)),

View File

@@ -23,7 +23,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
iconHeight: 100, iconHeight: 100,
title: 'Connect with Top Professionals', title: 'Connect with Top Professionals',
subtitle: subtitle:
'Professionals can list properties and find qualified buyers faster than ever.', 'Agents can highlight their specializations for buyers to find faster than ever',
), ),
_OnboardingData( _OnboardingData(
bgAsset: 'assets/images/onboarding_2_bg.svg', bgAsset: 'assets/images/onboarding_2_bg.svg',
@@ -32,7 +32,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
iconHeight: 75, iconHeight: 75,
title: 'Real help. Real professionals. Instantly.', title: 'Real help. Real professionals. Instantly.',
subtitle: subtitle:
'Chat with professionals and get answers about properties instantly.', 'Chat with agents instantly and find your service provider with ease',
), ),
]; ];