md-app/lib/screens/pdf.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'));
}
},
),
);
}
}