enhance: Enhances reception search with highlighting and navigation

Adds a text highlighting feature to search results, making it easier for users to identify matching terms within reception names.

Enables direct navigation from a selected search result to its corresponding reception details page, improving workflow efficiency.
This commit is contained in:
mandreshope 2025-08-04 15:45:11 +03:00
parent 099334942a
commit d7587bab32

View File

@ -1,7 +1,9 @@
import 'dart:async';
import 'package:e_scan/backend/objectbox/entities/stock_picking/stock_picking_record_entity.dart';
import 'package:e_scan/components/components.dart';
import 'package:e_scan/pages/operation/reception/reception_page_model.dart';
import 'package:e_scan/pages/operation/reception/reception_search_page_model.dart';
import 'package:e_scan/router/go_secure_router_builder.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:e_scan/themes/app_theme.dart';
import 'package:flutter/material.dart';
@ -16,14 +18,17 @@ class ReceptionSearchPage extends ConsumerStatefulWidget {
class _ReceptionSearchPageState extends ConsumerState<ReceptionSearchPage> {
Timer? _debounce;
String _query = '';
void _onSearchChanged(String value) {
_debounce?.cancel();
_query = value;
_debounce = Timer(const Duration(milliseconds: 500), () {
ref
.read(receptionSearchPageModelProvider.notifier)
.allByName(name: value);
});
setState(() {}); // pour rafraîchir le highlight immédiatement
}
@override
@ -32,6 +37,47 @@ class _ReceptionSearchPageState extends ConsumerState<ReceptionSearchPage> {
super.dispose();
}
/// Crée un TextSpan avec le texte les parties correspondant à la requête sont surlignées.
TextSpan _buildHighlightedText(
String? text,
String query,
TextStyle defaultStyle,
) {
if (text == null || query.isEmpty) {
return TextSpan(text: text ?? '', style: defaultStyle);
}
final lcText = text.toLowerCase();
final lcQuery = query.toLowerCase();
final spans = <TextSpan>[];
int start = 0;
while (true) {
final index = lcText.indexOf(lcQuery, start);
if (index < 0) {
spans.add(TextSpan(text: text.substring(start), style: defaultStyle));
break;
}
if (index > start) {
spans.add(
TextSpan(text: text.substring(start, index), style: defaultStyle),
);
}
spans.add(
TextSpan(
text: text.substring(index, index + query.length),
style: defaultStyle.copyWith(
fontWeight: FontWeight.bold,
color: AppTheme.of(context).primaryText,
),
),
);
start = index + query.length;
}
return TextSpan(children: spans);
}
@override
Widget build(BuildContext context) {
final state = ref.watch(receptionSearchPageModelProvider);
@ -68,12 +114,26 @@ class _ReceptionSearchPageState extends ConsumerState<ReceptionSearchPage> {
? const Center(child: Text('Aucun résultat trouvé'))
: SingleChildScrollView(
child: Column(
children: receptions
.map(
(reception) =>
ListTile(title: Text(reception.name ?? '')),
)
.toList(),
children: receptions.map((reception) {
final name = reception.name ?? '';
return ListTile(
onTap: () {
final id = reception.id;
ReceptionDetailsRoute(
receptionId: id,
).pushReplacement(context);
},
title: RichText(
text: _buildHighlightedText(
name,
_query,
AppTheme.of(context).bodyLarge.override(
color: AppTheme.of(context).secondaryText,
),
),
),
);
}).toList(),
),
),
),