142 lines
4.3 KiB
Dart
142 lines
4.3 KiB
Dart
|
|
import 'package:flutter/material.dart';
|
||
|
|
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
|
||
|
|
|
||
|
|
/// A dialog for searching text within a PDF using SfPdfViewer's built-in search.
|
||
|
|
class PdfTextSearchDialog extends StatefulWidget {
|
||
|
|
final PdfViewerController viewerController;
|
||
|
|
|
||
|
|
const PdfTextSearchDialog({super.key, required this.viewerController});
|
||
|
|
|
||
|
|
@override
|
||
|
|
State<PdfTextSearchDialog> createState() => _PdfTextSearchDialogState();
|
||
|
|
}
|
||
|
|
|
||
|
|
class _PdfTextSearchDialogState extends State<PdfTextSearchDialog> {
|
||
|
|
final TextEditingController _queryController = TextEditingController();
|
||
|
|
PdfTextSearchResult? _searchResult;
|
||
|
|
String _statusText = '';
|
||
|
|
|
||
|
|
@override
|
||
|
|
void dispose() {
|
||
|
|
_queryController.dispose();
|
||
|
|
_searchResult?.clear();
|
||
|
|
super.dispose();
|
||
|
|
}
|
||
|
|
|
||
|
|
void _search() {
|
||
|
|
final query = _queryController.text.trim();
|
||
|
|
if (query.isEmpty) return;
|
||
|
|
|
||
|
|
final result = widget.viewerController.searchText(query);
|
||
|
|
setState(() {
|
||
|
|
_searchResult = result;
|
||
|
|
_updateStatus();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
void _nextMatch() {
|
||
|
|
_searchResult?.nextInstance();
|
||
|
|
_updateStatus();
|
||
|
|
}
|
||
|
|
|
||
|
|
void _previousMatch() {
|
||
|
|
_searchResult?.previousInstance();
|
||
|
|
_updateStatus();
|
||
|
|
}
|
||
|
|
|
||
|
|
void _updateStatus() {
|
||
|
|
final result = _searchResult;
|
||
|
|
if (result == null || result.totalInstanceCount == 0) {
|
||
|
|
setState(() => _statusText = 'No matches');
|
||
|
|
} else {
|
||
|
|
setState(() {
|
||
|
|
_statusText =
|
||
|
|
'${result.currentInstanceIndex} of ${result.totalInstanceCount} matches';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
return Dialog(
|
||
|
|
child: ConstrainedBox(
|
||
|
|
constraints: const BoxConstraints(maxWidth: 400),
|
||
|
|
child: Padding(
|
||
|
|
padding: const EdgeInsets.all(16),
|
||
|
|
child: Column(
|
||
|
|
mainAxisSize: MainAxisSize.min,
|
||
|
|
children: [
|
||
|
|
Row(
|
||
|
|
children: [
|
||
|
|
Expanded(
|
||
|
|
child: TextField(
|
||
|
|
controller: _queryController,
|
||
|
|
autofocus: true,
|
||
|
|
decoration: const InputDecoration(
|
||
|
|
hintText: 'Search in PDF...',
|
||
|
|
prefixIcon: Icon(Icons.search),
|
||
|
|
isDense: true,
|
||
|
|
border: OutlineInputBorder(),
|
||
|
|
),
|
||
|
|
onSubmitted: (_) => _search(),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
const SizedBox(width: 8),
|
||
|
|
IconButton(
|
||
|
|
icon: const Icon(Icons.search),
|
||
|
|
tooltip: 'Search',
|
||
|
|
onPressed: _search,
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
const SizedBox(height: 12),
|
||
|
|
Row(
|
||
|
|
children: [
|
||
|
|
Text(
|
||
|
|
_statusText,
|
||
|
|
style: Theme.of(context).textTheme.bodySmall,
|
||
|
|
),
|
||
|
|
const Spacer(),
|
||
|
|
IconButton(
|
||
|
|
icon: const Icon(Icons.keyboard_arrow_up),
|
||
|
|
tooltip: 'Previous match',
|
||
|
|
onPressed:
|
||
|
|
_searchResult != null &&
|
||
|
|
_searchResult!.totalInstanceCount > 0
|
||
|
|
? _previousMatch
|
||
|
|
: null,
|
||
|
|
iconSize: 20,
|
||
|
|
),
|
||
|
|
IconButton(
|
||
|
|
icon: const Icon(Icons.keyboard_arrow_down),
|
||
|
|
tooltip: 'Next match',
|
||
|
|
onPressed:
|
||
|
|
_searchResult != null &&
|
||
|
|
_searchResult!.totalInstanceCount > 0
|
||
|
|
? _nextMatch
|
||
|
|
: null,
|
||
|
|
iconSize: 20,
|
||
|
|
),
|
||
|
|
IconButton(
|
||
|
|
icon: const Icon(Icons.close),
|
||
|
|
tooltip: 'Clear search',
|
||
|
|
onPressed: () {
|
||
|
|
_searchResult?.clear();
|
||
|
|
setState(() {
|
||
|
|
_queryController.clear();
|
||
|
|
_searchResult = null;
|
||
|
|
_statusText = '';
|
||
|
|
});
|
||
|
|
},
|
||
|
|
iconSize: 20,
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|