
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.
145 lines
4.7 KiB
Dart
145 lines
4.7 KiB
Dart
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';
|
|
|
|
class ReceptionSearchPage extends ConsumerStatefulWidget {
|
|
const ReceptionSearchPage({super.key});
|
|
|
|
@override
|
|
ConsumerState<ReceptionSearchPage> createState() =>
|
|
_ReceptionSearchPageState();
|
|
}
|
|
|
|
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
|
|
void dispose() {
|
|
_debounce?.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
/// Crée un TextSpan avec le texte où 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);
|
|
final receptions = state.valueOrNull ?? <StockPickingRecordEntity>[];
|
|
return Scaffold(
|
|
backgroundColor: AppTheme.of(context).primaryBackground,
|
|
appBar: MainAppbarComponent(
|
|
leading: BackButton(color: AppTheme.of(context).white),
|
|
title: "Rechercher une réception",
|
|
subTitle: "Opérations d'Entrepôt",
|
|
),
|
|
body: Column(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: TextFormField(
|
|
autofocus: true,
|
|
decoration: InputDecoration(
|
|
hintText: "Nom de la réception",
|
|
filled: true,
|
|
fillColor: AppTheme.of(context).secondaryBackground,
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
),
|
|
onChanged: _onSearchChanged,
|
|
),
|
|
),
|
|
Expanded(
|
|
child: state.isLoading
|
|
? const Center(child: LoadingProgressComponent())
|
|
: receptions.isEmpty
|
|
? const Center(child: Text('Aucun résultat trouvé'))
|
|
: SingleChildScrollView(
|
|
child: Column(
|
|
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(),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|