Skip to content

Commit a7fbc64

Browse files
authored
Merge pull request #359 from Scriptbash/pdf-overlay
Add overlay to PDF viewer
2 parents f46a347 + 8b33633 commit a7fbc64

4 files changed

Lines changed: 304 additions & 17 deletions

File tree

lib/l10n/app_en.arb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,18 @@
561561
}
562562
}
563563
},
564+
"zoomOut":"Zoom out",
565+
"@zoomOut":{},
566+
"zoomIn":"Zoom in",
567+
"@zoomIn":{},
568+
"goToFirstPage":"Go to first page",
569+
"@goToFirstPage":{},
570+
"goToLastPage":"Go to last page",
571+
"@goToLastPage":{},
572+
"nextMatch":"Next match",
573+
"@nextMatch":{},
574+
"previousMatch":"Previous match",
575+
"@previousMatch":{},
564576
"sourceCode": "Source code",
565577
"@sourceCode": {},
566578
"reportIssue": "Report an issue",

lib/screens/article_website.dart

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:flutter/material.dart';
2+
import 'package:http/io_client.dart';
23
import 'package:wispar/generated_l10n/app_localizations.dart';
34
import 'package:shared_preferences/shared_preferences.dart';
45
import 'package:wispar/services/unpaywall_api.dart';
@@ -8,7 +9,7 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart';
89
import 'package:url_launcher/url_launcher.dart';
910
import 'package:flutter/foundation.dart';
1011
import 'package:path_provider/path_provider.dart';
11-
import 'dart:io';
12+
import 'dart:io' as io;
1213
import 'package:http/http.dart' as http;
1314
import 'package:wispar/services/logs_helper.dart';
1415
import 'package:wispar/webview_env.dart';
@@ -58,7 +59,7 @@ class ArticleWebsiteState extends State<ArticleWebsite> {
5859
_initWebViewSettings();
5960
checkUnpaywallAvailability();
6061

61-
pullToRefreshController = Platform.isAndroid || Platform.isIOS
62+
pullToRefreshController = io.Platform.isAndroid || io.Platform.isIOS
6263
? PullToRefreshController(
6364
settings: PullToRefreshSettings(
6465
color: Colors.deepPurple,
@@ -97,15 +98,15 @@ class ArticleWebsiteState extends State<ArticleWebsite> {
9798
}
9899

99100
String _getPlatformUserAgent() {
100-
if (Platform.isAndroid) {
101+
if (io.Platform.isAndroid) {
101102
return "Mozilla/5.0 (Android 16; Mobile; LG-M255; rv:140.0) Gecko/140.0 Firefox/140.0";
102-
} else if (Platform.isIOS) {
103+
} else if (io.Platform.isIOS) {
103104
return "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile Safari/604.1";
104-
} else if (Platform.isMacOS) {
105+
} else if (io.Platform.isMacOS) {
105106
return "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)";
106-
} else if (Platform.isWindows) {
107+
} else if (io.Platform.isWindows) {
107108
return "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:142.0) Gecko/20100101 Firefox/142.0";
108-
} else if (Platform.isLinux) {
109+
} else if (io.Platform.isLinux) {
109110
return "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.3";
110111
} else {
111112
return "Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0 Mobile Safari/537.36";
@@ -309,7 +310,7 @@ class ArticleWebsiteState extends State<ArticleWebsite> {
309310
? InAppWebView(
310311
key: webViewKey,
311312
webViewEnvironment:
312-
Platform.isWindows ? webViewEnvironment : null,
313+
io.Platform.isWindows ? webViewEnvironment : null,
313314
initialUrlRequest: URLRequest(url: WebUri(pdfUrl)),
314315
initialSettings: settings,
315316
pullToRefreshController: pullToRefreshController,
@@ -403,7 +404,7 @@ class ArticleWebsiteState extends State<ArticleWebsite> {
403404
return ServerTrustAuthResponse(
404405
action: ServerTrustAuthResponseAction.PROCEED);
405406
},
406-
onDownloadStartRequest: (controller, urlInfo) async {
407+
onDownloadStarting: (controller, urlInfo) async {
407408
final Uri downloadUri = urlInfo.url;
408409
final String? mimeType = urlInfo.mimeType;
409410
final String? suggestedFilename =
@@ -946,7 +947,22 @@ class ArticleWebsiteState extends State<ArticleWebsite> {
946947
logger.info(
947948
'Full HTTP Request Headers being sent: $headers');
948949

949-
final client = http.Client();
950+
final io.HttpClient innerHttpClient = io.HttpClient()
951+
..badCertificateCallback =
952+
((io.X509Certificate cert, String host, int port) {
953+
final configuredProxyHost =
954+
Uri.tryParse(proxyUrl)?.host;
955+
956+
if (configuredProxyHost != null &&
957+
host.contains(configuredProxyHost)) {
958+
logger.info(
959+
'Trusting certificate for proxy host: $host');
960+
return true;
961+
}
962+
return false;
963+
});
964+
965+
final client = IOClient(innerHttpClient);
950966

951967
final request = http.Request('GET', finalDownloadUri)
952968
..headers.addAll(headers)
@@ -982,7 +998,7 @@ class ArticleWebsiteState extends State<ArticleWebsite> {
982998

983999
if (useCustomPath && customPath != null) {
9841000
baseDirPath = customPath;
985-
} else if (Platform.isWindows) {
1001+
} else if (io.Platform.isWindows) {
9861002
final defaultAppDir =
9871003
await getApplicationSupportDirectory();
9881004
baseDirPath = defaultAppDir.path;
@@ -1001,7 +1017,7 @@ class ArticleWebsiteState extends State<ArticleWebsite> {
10011017
}
10021018

10031019
final fileName = '$cleanedDoi.pdf';
1004-
final pdfFile = File('$baseDirPath/$fileName');
1020+
final pdfFile = io.File('$baseDirPath/$fileName');
10051021
await pdfFile.writeAsBytes(response.bodyBytes);
10061022
if (mounted) {
10071023
Navigator.of(this.context).push(MaterialPageRoute(

lib/screens/pdf_reader.dart

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import 'dart:io';
22
import 'package:flutter/material.dart';
3-
import '../generated_l10n/app_localizations.dart';
3+
import 'package:wispar/generated_l10n/app_localizations.dart';
44
import 'package:pdfrx/pdfrx.dart';
5-
import '../widgets/publication_card/publication_card.dart';
6-
import '../services/database_helper.dart';
5+
import 'package:wispar/widgets/pdf_control_overlay.dart';
6+
import 'package:wispar/widgets/publication_card/publication_card.dart';
7+
import 'package:wispar/services/database_helper.dart';
78
import 'package:url_launcher/url_launcher.dart';
89
import 'package:open_filex/open_filex.dart';
910
import 'package:path_provider/path_provider.dart';
1011
import 'package:path/path.dart' as p;
1112
import 'package:shared_preferences/shared_preferences.dart';
12-
import '../services/logs_helper.dart';
13-
import '../screens/chat_screen.dart';
13+
import 'package:wispar/services/logs_helper.dart';
14+
import 'package:wispar/screens/chat_screen.dart';
1415
import 'dart:math';
1516

1617
class PdfReader extends StatefulWidget {
@@ -38,9 +39,14 @@ class PdfReaderState extends State<PdfReader> {
3839
bool _darkPdfTheme = false;
3940
int _pdfOrientation = 0;
4041
bool _isZoomed = false;
42+
bool _overlayVisible = true;
43+
44+
PdfTextSearcher? textSearcher;
4145

4246
@override
4347
void dispose() {
48+
textSearcher?.removeListener(_update);
49+
textSearcher?.dispose();
4450
super.dispose();
4551
}
4652

@@ -53,6 +59,10 @@ class PdfReaderState extends State<PdfReader> {
5359
});
5460
}
5561

62+
void _update() {
63+
if (mounted) setState(() {});
64+
}
65+
5666
Future<void> _loadPreferences() async {
5767
final prefs = await SharedPreferences.getInstance();
5868
final pdfThemeOption = prefs.getInt('pdfThemeOption') ?? 0;
@@ -189,6 +199,16 @@ class PdfReaderState extends State<PdfReader> {
189199
child: PdfViewer.file(resolvedPdfPath,
190200
controller: controller,
191201
params: PdfViewerParams(
202+
onViewerReady: (document, controller) {
203+
setState(() {
204+
textSearcher = PdfTextSearcher(controller)
205+
..addListener(_update);
206+
});
207+
},
208+
pagePaintCallbacks: [
209+
if (textSearcher != null)
210+
textSearcher!.pageTextMatchPaintCallback,
211+
],
192212
layoutPages: _pdfOrientation == 1
193213
? (pages, params) {
194214
final height = pages.fold(
@@ -254,12 +274,52 @@ class PdfReaderState extends State<PdfReader> {
254274
},
255275
onTapUp: (details) {
256276
handleLinkTap(details.localPosition);
277+
278+
setState(() {
279+
_overlayVisible = !_overlayVisible;
280+
});
257281
},
258282
child: IgnorePointer(
259283
child: SizedBox(
260284
width: size.width, height: size.height),
261285
),
262286
),
287+
PdfViewerScrollThumb(
288+
controller: controller,
289+
orientation: _pdfOrientation == 1
290+
? ScrollbarOrientation.bottom
291+
: ScrollbarOrientation.right,
292+
thumbSize: _pdfOrientation == 1
293+
? const Size(60, 26)
294+
: const Size(40, 26),
295+
thumbBuilder:
296+
(context, thumbSize, pageNumber, controller) {
297+
return AnimatedOpacity(
298+
duration: const Duration(milliseconds: 200),
299+
curve: Curves.easeOut,
300+
opacity: _overlayVisible ? 1 : 0,
301+
child: Container(
302+
decoration: BoxDecoration(
303+
color:
304+
Colors.black.withValues(alpha: 0.75),
305+
borderRadius: BorderRadius.circular(12),
306+
),
307+
alignment: Alignment.center,
308+
child: Text(
309+
pageNumber.toString(),
310+
style: const TextStyle(
311+
color: Colors.white,
312+
),
313+
),
314+
),
315+
);
316+
},
317+
),
318+
PdfControlOverlay(
319+
controller: controller,
320+
textSearcher: textSearcher,
321+
overlayVisible: _overlayVisible,
322+
),
263323
],
264324
)))
265325
])

0 commit comments

Comments
 (0)