227 lines
7.4 KiB
Dart
227 lines
7.4 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:dio/dio.dart';
|
|
import 'package:easy_localization/easy_localization.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
|
import 'package:percent_indicator/circular_percent_indicator.dart';
|
|
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'dart:io';
|
|
|
|
class FilePdf extends StatefulWidget {
|
|
final String pdf;
|
|
final String? searchTerm;
|
|
final String title;
|
|
|
|
const FilePdf({
|
|
super.key,
|
|
required this.pdf,
|
|
required this.title,
|
|
this.searchTerm,
|
|
});
|
|
|
|
@override
|
|
// ignore: library_private_types_in_public_api
|
|
_FilePdfState createState() => _FilePdfState();
|
|
}
|
|
|
|
class _FilePdfState extends State<FilePdf> {
|
|
late Future<String> _pdfPathFuture;
|
|
late PdfViewerController _pdfViewerController;
|
|
late PdfTextSearchResult _pdfTextSearchResult;
|
|
final _baseUrl = dotenv.env['BASE_URL']!;
|
|
final _token = dotenv.env['TOKEN']!;
|
|
bool isDownloading = true;
|
|
ValueNotifier<int> downloadProgressNotifier = ValueNotifier(0);
|
|
bool isSearching = false;
|
|
int downloadProgress = 0;
|
|
Timer? _debounceTimer;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_pdfPathFuture = _fetchPdf();
|
|
_pdfViewerController = PdfViewerController();
|
|
_pdfTextSearchResult = PdfTextSearchResult();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_pdfTextSearchResult.removeListener(() {});
|
|
_pdfTextSearchResult.dispose();
|
|
_pdfViewerController.dispose();
|
|
_debounceTimer?.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<String> _fetchPdf() async {
|
|
final String pdfFileName = widget.pdf;
|
|
final String baseUrl = '$_baseUrl/assets/$pdfFileName?access_token=$_token';
|
|
|
|
final appDir = await getApplicationDocumentsDirectory();
|
|
final pdfDir = Directory(
|
|
'${appDir.path}/LGCC_Search/${context.locale.toString()}/library/');
|
|
if (!await pdfDir.exists()) {
|
|
await pdfDir.create(recursive: true);
|
|
}
|
|
final String filePath = '${pdfDir.path}/$pdfFileName.pdf';
|
|
if (await File(filePath).exists()) {
|
|
return filePath;
|
|
} else {
|
|
try {
|
|
await Dio().download(baseUrl, filePath,
|
|
onReceiveProgress: (actualBytes, totalBytes) {
|
|
final progress = (actualBytes / totalBytes * 100).floor();
|
|
setState(() {
|
|
downloadProgress = progress;
|
|
downloadProgressNotifier.value = progress;
|
|
});
|
|
});
|
|
setState(() {
|
|
isDownloading = false;
|
|
});
|
|
return filePath;
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Error downloading file: $e');
|
|
}
|
|
setState(() {
|
|
isDownloading = false;
|
|
downloadProgress = 0;
|
|
downloadProgressNotifier.value = 0;
|
|
});
|
|
return '';
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> saveDoc() async {
|
|
final List<int> savedBytes = await _pdfViewerController.saveDocument();
|
|
_saveDocument(savedBytes);
|
|
}
|
|
|
|
void _performSearch(String searchTerm, {bool immediate = false}) {
|
|
if (mounted && searchTerm.isEmpty) {
|
|
setState(() {
|
|
isSearching = false;
|
|
});
|
|
}
|
|
if (immediate) {
|
|
_performSearchAction(searchTerm);
|
|
} else {
|
|
_debounceTimer?.cancel();
|
|
_debounceTimer = Timer(const Duration(milliseconds: 300), () {
|
|
_performSearchAction(searchTerm);
|
|
});
|
|
}
|
|
}
|
|
|
|
void _performSearchAction(String searchTerm) {
|
|
_pdfTextSearchResult = _pdfViewerController.searchText(searchTerm);
|
|
_pdfTextSearchResult.addListener(() {
|
|
if (mounted && _pdfTextSearchResult.isSearchCompleted) {
|
|
setState(() {});
|
|
}
|
|
});
|
|
}
|
|
|
|
Future<void> _saveDocument(List<int> dataBytes) async {
|
|
final appDir = await getApplicationDocumentsDirectory();
|
|
final directory = Directory(
|
|
'${appDir.path}/LGCC_Search/${context.locale.toString()}/library/');
|
|
final String path = directory.path;
|
|
final File file = File('$path/${widget.pdf}.pdf');
|
|
await file.writeAsBytes(dataBytes);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: isSearching
|
|
? TextField(
|
|
onChanged: (val) => _performSearch(val),
|
|
)
|
|
: Text(widget.title),
|
|
backgroundColor: const Color(0xFFe0e6d1),
|
|
actions: [
|
|
Container(
|
|
margin: const EdgeInsets.only(right: 12),
|
|
child: Row(
|
|
children: [
|
|
if (_pdfTextSearchResult.hasResult &&
|
|
_pdfTextSearchResult.totalInstanceCount > 1)
|
|
Text(
|
|
'${_pdfTextSearchResult.currentInstanceIndex} / ${_pdfTextSearchResult.totalInstanceCount}'),
|
|
if (_pdfTextSearchResult.hasResult &&
|
|
_pdfTextSearchResult.totalInstanceCount > 1)
|
|
IconButton(
|
|
icon: const Icon(Icons.chevron_left),
|
|
onPressed: (_pdfTextSearchResult.currentInstanceIndex > 1)
|
|
? () {
|
|
_pdfTextSearchResult.previousInstance();
|
|
}
|
|
: null,
|
|
),
|
|
if (_pdfTextSearchResult.hasResult &&
|
|
_pdfTextSearchResult.totalInstanceCount > 1)
|
|
IconButton(
|
|
icon: const Icon(Icons.chevron_right),
|
|
onPressed: (_pdfTextSearchResult.currentInstanceIndex <
|
|
_pdfTextSearchResult.totalInstanceCount)
|
|
? () {
|
|
_pdfTextSearchResult.nextInstance();
|
|
}
|
|
: null,
|
|
),
|
|
IconButton(
|
|
icon: Icon(isSearching ? Icons.close : Icons.search),
|
|
onPressed: () {
|
|
setState(() {
|
|
isSearching = !isSearching;
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
)
|
|
]),
|
|
body: FutureBuilder<String>(
|
|
future: _pdfPathFuture,
|
|
builder: (context, snapshot) {
|
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
return Center(
|
|
child: CircularPercentIndicator(
|
|
backgroundColor: Colors.grey.shade400,
|
|
progressColor: const Color(0xFF6b8e23),
|
|
lineWidth: 3,
|
|
radius: 32,
|
|
center: Text(downloadProgress.toString(),
|
|
style: const TextStyle(fontSize: 14, color: Colors.black)),
|
|
percent: downloadProgress / 100,
|
|
),
|
|
);
|
|
} else if (snapshot.hasError) {
|
|
return Center(child: Text('no_results'.tr()));
|
|
} else if (snapshot.hasData) {
|
|
return SfPdfViewer.file(
|
|
File(snapshot.data!),
|
|
controller: _pdfViewerController,
|
|
onDocumentLoaded: (PdfDocumentLoadedDetails details) {
|
|
if (widget.searchTerm != null) {
|
|
_performSearch(widget.searchTerm!, immediate: true);
|
|
}
|
|
},
|
|
onAnnotationAdded: (annotation) => saveDoc(),
|
|
);
|
|
} else {
|
|
return const Center(child: Text('Error'));
|
|
}
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|