Matthias Nott
3 days ago c9739ac4a22733d45167173446c1d3ce65a767eb
feat: inline PDF viewer with pinch-to-zoom

- Add pdfrx package for native PDF rendering
- Tap PDF file card opens full-screen viewer inside the app
- Supports pinch-to-zoom and page navigation
- Other file types still open via iOS share sheet
1 files added
6 files modified
changed files
ios/Podfile.lock patch | view | blame | history
lib/widgets/message_bubble.dart patch | view | blame | history
lib/widgets/pdf_viewer.dart patch | view | blame | history
linux/flutter/generated_plugins.cmake patch | view | blame | history
pubspec.lock patch | view | blame | history
pubspec.yaml patch | view | blame | history
windows/flutter/generated_plugins.cmake patch | view | blame | history
ios/Podfile.lock
....@@ -53,6 +53,9 @@
5353 - in_app_purchase_storekit (0.0.1):
5454 - Flutter
5555 - FlutterMacOS
56
+ - pdfrx (0.0.6):
57
+ - Flutter
58
+ - FlutterMacOS
5659 - permission_handler_apple (9.3.0):
5760 - Flutter
5861 - push (0.0.1):
....@@ -85,6 +88,7 @@
8588 - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
8689 - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
8790 - in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/darwin`)
91
+ - pdfrx (from `.symlinks/plugins/pdfrx/darwin`)
8892 - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
8993 - push (from `.symlinks/plugins/push/darwin`)
9094 - record_ios (from `.symlinks/plugins/record_ios/ios`)
....@@ -121,6 +125,8 @@
121125 :path: ".symlinks/plugins/image_picker_ios/ios"
122126 in_app_purchase_storekit:
123127 :path: ".symlinks/plugins/in_app_purchase_storekit/darwin"
128
+ pdfrx:
129
+ :path: ".symlinks/plugins/pdfrx/darwin"
124130 permission_handler_apple:
125131 :path: ".symlinks/plugins/permission_handler_apple/ios"
126132 push:
....@@ -149,6 +155,7 @@
149155 flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
150156 image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
151157 in_app_purchase_storekit: 22cca7d08eebca9babdf4d07d0baccb73325d3c8
158
+ pdfrx: 310e84d01e06fd2af26e16507a0e48c27e99195c
152159 permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
153160 push: 91373ae39c5341c6de6adefa3fda7f7287d646bf
154161 record_ios: 412daca2350b228e698fffcd08f1f94ceb1e3844
lib/widgets/message_bubble.dart
....@@ -14,6 +14,7 @@
1414 import '../models/message.dart';
1515 import '../theme/app_theme.dart';
1616 import 'image_viewer.dart';
17
+import 'pdf_viewer.dart';
1718
1819 // Cache decoded image bytes to prevent flicker on widget rebuild
1920 final Map<String, Uint8List> _imageCache = {};
....@@ -425,15 +426,33 @@
425426 if (data == null || data.isEmpty) return;
426427
427428 try {
428
- final bytes = base64Decode(data.contains(',') ? data.split(',').last : data);
429
+ final bytes = Uint8List.fromList(
430
+ base64Decode(data.contains(',') ? data.split(',').last : data),
431
+ );
429432 final mime = message.mimeType ?? 'application/octet-stream';
433
+
434
+ // PDFs: open inline viewer
435
+ if (mime == 'application/pdf') {
436
+ if (context.mounted) {
437
+ Navigator.of(context).push(
438
+ MaterialPageRoute(
439
+ builder: (_) => PdfViewerScreen(
440
+ pdfBytes: bytes,
441
+ title: message.content.isNotEmpty ? message.content : 'PDF',
442
+ ),
443
+ ),
444
+ );
445
+ }
446
+ return;
447
+ }
448
+
449
+ // Other files: save to temp and share
430450 final ext = _mimeToExt(mime);
431451 final dir = await getTemporaryDirectory();
432452 final fileName = '${message.content.isNotEmpty ? message.content.replaceAll(RegExp(r'[^\w\s.-]'), '').trim() : 'file'}.$ext';
433453 final file = File('${dir.path}/$fileName');
434454 await file.writeAsBytes(bytes);
435455
436
- // Use share sheet — works for PDFs, Office docs, and all file types on iOS
437456 await SharePlus.instance.share(
438457 ShareParams(files: [XFile(file.path, mimeType: mime)]),
439458 );
lib/widgets/pdf_viewer.dart
....@@ -0,0 +1,29 @@
1
+import 'dart:typed_data';
2
+
3
+import 'package:flutter/material.dart';
4
+import 'package:pdfrx/pdfrx.dart';
5
+
6
+/// Full-screen PDF viewer with pinch-to-zoom and page navigation.
7
+class PdfViewerScreen extends StatelessWidget {
8
+ final Uint8List pdfBytes;
9
+ final String title;
10
+
11
+ const PdfViewerScreen({
12
+ super.key,
13
+ required this.pdfBytes,
14
+ this.title = 'PDF',
15
+ });
16
+
17
+ @override
18
+ Widget build(BuildContext context) {
19
+ return Scaffold(
20
+ appBar: AppBar(
21
+ title: Text(title, style: const TextStyle(fontSize: 16)),
22
+ backgroundColor: Colors.black87,
23
+ foregroundColor: Colors.white,
24
+ ),
25
+ backgroundColor: Colors.grey[900],
26
+ body: PdfViewer.data(pdfBytes, sourceName: title),
27
+ );
28
+ }
29
+}
linux/flutter/generated_plugins.cmake
....@@ -11,6 +11,7 @@
1111 )
1212
1313 list(APPEND FLUTTER_FFI_PLUGIN_LIST
14
+ pdfrx
1415 )
1516
1617 set(PLUGIN_BUNDLED_LIBRARIES)
pubspec.lock
....@@ -1,6 +1,22 @@
11 # Generated by pub
22 # See https://dart.dev/tools/pub/glossary#lockfile
33 packages:
4
+ _fe_analyzer_shared:
5
+ dependency: transitive
6
+ description:
7
+ name: _fe_analyzer_shared
8
+ sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d"
9
+ url: "https://pub.dev"
10
+ source: hosted
11
+ version: "93.0.0"
12
+ analyzer:
13
+ dependency: transitive
14
+ description:
15
+ name: analyzer
16
+ sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b
17
+ url: "https://pub.dev"
18
+ source: hosted
19
+ version: "10.0.1"
420 args:
521 dependency: transitive
622 description:
....@@ -137,6 +153,30 @@
137153 url: "https://pub.dev"
138154 source: hosted
139155 version: "1.4.1"
156
+ checked_yaml:
157
+ dependency: transitive
158
+ description:
159
+ name: checked_yaml
160
+ sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
161
+ url: "https://pub.dev"
162
+ source: hosted
163
+ version: "2.0.4"
164
+ cli_config:
165
+ dependency: transitive
166
+ description:
167
+ name: cli_config
168
+ sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
169
+ url: "https://pub.dev"
170
+ source: hosted
171
+ version: "0.2.0"
172
+ cli_util:
173
+ dependency: transitive
174
+ description:
175
+ name: cli_util
176
+ sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
177
+ url: "https://pub.dev"
178
+ source: hosted
179
+ version: "0.4.2"
140180 clock:
141181 dependency: transitive
142182 description:
....@@ -177,6 +217,22 @@
177217 url: "https://pub.dev"
178218 source: hosted
179219 version: "2.1.0"
220
+ convert:
221
+ dependency: transitive
222
+ description:
223
+ name: convert
224
+ sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
225
+ url: "https://pub.dev"
226
+ source: hosted
227
+ version: "3.1.2"
228
+ coverage:
229
+ dependency: transitive
230
+ description:
231
+ name: coverage
232
+ sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d"
233
+ url: "https://pub.dev"
234
+ source: hosted
235
+ version: "1.15.0"
180236 cross_file:
181237 dependency: transitive
182238 description:
....@@ -193,6 +249,14 @@
193249 url: "https://pub.dev"
194250 source: hosted
195251 version: "3.0.7"
252
+ csslib:
253
+ dependency: transitive
254
+ description:
255
+ name: csslib
256
+ sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
257
+ url: "https://pub.dev"
258
+ source: hosted
259
+ version: "1.0.2"
196260 cupertino_icons:
197261 dependency: "direct main"
198262 description:
....@@ -201,6 +265,14 @@
201265 url: "https://pub.dev"
202266 source: hosted
203267 version: "1.0.8"
268
+ dart_pubspec_licenses:
269
+ dependency: transitive
270
+ description:
271
+ name: dart_pubspec_licenses
272
+ sha256: "3d579e1aa3ad3b6519f08fce6980799c0a8375bf41e0b8d58ca21f1be64032c9"
273
+ url: "https://pub.dev"
274
+ source: hosted
275
+ version: "3.2.0"
204276 dbus:
205277 dependency: transitive
206278 description:
....@@ -408,6 +480,14 @@
408480 description: flutter
409481 source: sdk
410482 version: "0.0.0"
483
+ frontend_server_client:
484
+ dependency: transitive
485
+ description:
486
+ name: frontend_server_client
487
+ sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
488
+ url: "https://pub.dev"
489
+ source: hosted
490
+ version: "4.0.0"
411491 glob:
412492 dependency: transitive
413493 description:
....@@ -432,6 +512,14 @@
432512 url: "https://pub.dev"
433513 source: hosted
434514 version: "1.0.2"
515
+ html:
516
+ dependency: transitive
517
+ description:
518
+ name: html
519
+ sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602"
520
+ url: "https://pub.dev"
521
+ source: hosted
522
+ version: "0.15.6"
435523 http:
436524 dependency: transitive
437525 description:
....@@ -440,6 +528,14 @@
440528 url: "https://pub.dev"
441529 source: hosted
442530 version: "1.6.0"
531
+ http_multi_server:
532
+ dependency: transitive
533
+ description:
534
+ name: http_multi_server
535
+ sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
536
+ url: "https://pub.dev"
537
+ source: hosted
538
+ version: "3.2.2"
443539 http_parser:
444540 dependency: transitive
445541 description:
....@@ -552,6 +648,14 @@
552648 url: "https://pub.dev"
553649 source: hosted
554650 version: "0.20.2"
651
+ io:
652
+ dependency: transitive
653
+ description:
654
+ name: io
655
+ sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
656
+ url: "https://pub.dev"
657
+ source: hosted
658
+ version: "1.0.5"
555659 js:
556660 dependency: transitive
557661 description:
....@@ -672,6 +776,14 @@
672776 url: "https://pub.dev"
673777 source: hosted
674778 version: "0.5.0"
779
+ node_preamble:
780
+ dependency: transitive
781
+ description:
782
+ name: node_preamble
783
+ sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
784
+ url: "https://pub.dev"
785
+ source: hosted
786
+ version: "2.0.2"
675787 objective_c:
676788 dependency: transitive
677789 description:
....@@ -680,6 +792,22 @@
680792 url: "https://pub.dev"
681793 source: hosted
682794 version: "9.3.0"
795
+ package_config:
796
+ dependency: transitive
797
+ description:
798
+ name: package_config
799
+ sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
800
+ url: "https://pub.dev"
801
+ source: hosted
802
+ version: "2.2.0"
803
+ pana:
804
+ dependency: transitive
805
+ description:
806
+ name: pana
807
+ sha256: "847ee5df6ac13fdc6c53d641095de1b25886c973d0b0f0469c73521ebe4602fa"
808
+ url: "https://pub.dev"
809
+ source: hosted
810
+ version: "0.23.12"
683811 path:
684812 dependency: transitive
685813 description:
....@@ -736,6 +864,14 @@
736864 url: "https://pub.dev"
737865 source: hosted
738866 version: "2.3.0"
867
+ pdfrx:
868
+ dependency: "direct main"
869
+ description:
870
+ name: pdfrx
871
+ sha256: "94c865686e7e15e93f2a5e3c0255f7d6c2ac39b2e5e82d62a0d278cb4b7d0d3b"
872
+ url: "https://pub.dev"
873
+ source: hosted
874
+ version: "1.3.5"
739875 permission_handler:
740876 dependency: "direct main"
741877 description:
....@@ -808,6 +944,14 @@
808944 url: "https://pub.dev"
809945 source: hosted
810946 version: "2.1.8"
947
+ pool:
948
+ dependency: transitive
949
+ description:
950
+ name: pool
951
+ sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
952
+ url: "https://pub.dev"
953
+ source: hosted
954
+ version: "1.5.2"
811955 pub_semver:
812956 dependency: transitive
813957 description:
....@@ -816,6 +960,14 @@
816960 url: "https://pub.dev"
817961 source: hosted
818962 version: "2.2.0"
963
+ pubspec_parse:
964
+ dependency: transitive
965
+ description:
966
+ name: pubspec_parse
967
+ sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
968
+ url: "https://pub.dev"
969
+ source: hosted
970
+ version: "1.5.0"
819971 push:
820972 dependency: "direct main"
821973 description:
....@@ -888,6 +1040,14 @@
8881040 url: "https://pub.dev"
8891041 source: hosted
8901042 version: "1.0.7"
1043
+ retry:
1044
+ dependency: transitive
1045
+ description:
1046
+ name: retry
1047
+ sha256: "822e118d5b3aafed083109c72d5f484c6dc66707885e07c0fbcb8b986bba7efc"
1048
+ url: "https://pub.dev"
1049
+ source: hosted
1050
+ version: "3.1.2"
8911051 riverpod:
8921052 dependency: transitive
8931053 description:
....@@ -904,6 +1064,22 @@
9041064 url: "https://pub.dev"
9051065 source: hosted
9061066 version: "2.6.1"
1067
+ rxdart:
1068
+ dependency: transitive
1069
+ description:
1070
+ name: rxdart
1071
+ sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
1072
+ url: "https://pub.dev"
1073
+ source: hosted
1074
+ version: "0.28.0"
1075
+ safe_url_check:
1076
+ dependency: transitive
1077
+ description:
1078
+ name: safe_url_check
1079
+ sha256: "49a3e060a7869cbafc8f4845ca1ecbbaaa53179980a32f4fdfeab1607e90f41d"
1080
+ url: "https://pub.dev"
1081
+ source: hosted
1082
+ version: "1.1.2"
9071083 share_plus:
9081084 dependency: "direct main"
9091085 description:
....@@ -976,11 +1152,59 @@
9761152 url: "https://pub.dev"
9771153 source: hosted
9781154 version: "2.4.1"
1155
+ shelf:
1156
+ dependency: transitive
1157
+ description:
1158
+ name: shelf
1159
+ sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
1160
+ url: "https://pub.dev"
1161
+ source: hosted
1162
+ version: "1.4.2"
1163
+ shelf_packages_handler:
1164
+ dependency: transitive
1165
+ description:
1166
+ name: shelf_packages_handler
1167
+ sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
1168
+ url: "https://pub.dev"
1169
+ source: hosted
1170
+ version: "3.0.2"
1171
+ shelf_static:
1172
+ dependency: transitive
1173
+ description:
1174
+ name: shelf_static
1175
+ sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
1176
+ url: "https://pub.dev"
1177
+ source: hosted
1178
+ version: "1.1.3"
1179
+ shelf_web_socket:
1180
+ dependency: transitive
1181
+ description:
1182
+ name: shelf_web_socket
1183
+ sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
1184
+ url: "https://pub.dev"
1185
+ source: hosted
1186
+ version: "3.0.0"
9791187 sky_engine:
9801188 dependency: transitive
9811189 description: flutter
9821190 source: sdk
9831191 version: "0.0.0"
1192
+ source_map_stack_trace:
1193
+ dependency: transitive
1194
+ description:
1195
+ name: source_map_stack_trace
1196
+ sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
1197
+ url: "https://pub.dev"
1198
+ source: hosted
1199
+ version: "2.1.2"
1200
+ source_maps:
1201
+ dependency: transitive
1202
+ description:
1203
+ name: source_maps
1204
+ sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
1205
+ url: "https://pub.dev"
1206
+ source: hosted
1207
+ version: "0.10.13"
9841208 source_span:
9851209 dependency: transitive
9861210 description:
....@@ -1037,6 +1261,14 @@
10371261 url: "https://pub.dev"
10381262 source: hosted
10391263 version: "1.2.2"
1264
+ test:
1265
+ dependency: transitive
1266
+ description:
1267
+ name: test
1268
+ sha256: "280d6d890011ca966ad08df7e8a4ddfab0fb3aa49f96ed6de56e3521347a9ae7"
1269
+ url: "https://pub.dev"
1270
+ source: hosted
1271
+ version: "1.30.0"
10401272 test_api:
10411273 dependency: transitive
10421274 description:
....@@ -1045,6 +1277,14 @@
10451277 url: "https://pub.dev"
10461278 source: hosted
10471279 version: "0.7.10"
1280
+ test_core:
1281
+ dependency: transitive
1282
+ description:
1283
+ name: test_core
1284
+ sha256: "0381bd1585d1a924763c308100f2138205252fb90c9d4eeaf28489ee65ccde51"
1285
+ url: "https://pub.dev"
1286
+ source: hosted
1287
+ version: "0.6.16"
10481288 typed_data:
10491289 dependency: transitive
10501290 description:
....@@ -1165,6 +1405,14 @@
11651405 url: "https://pub.dev"
11661406 source: hosted
11671407 version: "15.0.2"
1408
+ watcher:
1409
+ dependency: transitive
1410
+ description:
1411
+ name: watcher
1412
+ sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
1413
+ url: "https://pub.dev"
1414
+ source: hosted
1415
+ version: "1.2.1"
11681416 web:
11691417 dependency: transitive
11701418 description:
....@@ -1173,6 +1421,30 @@
11731421 url: "https://pub.dev"
11741422 source: hosted
11751423 version: "1.1.1"
1424
+ web_socket:
1425
+ dependency: transitive
1426
+ description:
1427
+ name: web_socket
1428
+ sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
1429
+ url: "https://pub.dev"
1430
+ source: hosted
1431
+ version: "1.0.1"
1432
+ web_socket_channel:
1433
+ dependency: transitive
1434
+ description:
1435
+ name: web_socket_channel
1436
+ sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
1437
+ url: "https://pub.dev"
1438
+ source: hosted
1439
+ version: "3.0.3"
1440
+ webkit_inspection_protocol:
1441
+ dependency: transitive
1442
+ description:
1443
+ name: webkit_inspection_protocol
1444
+ sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
1445
+ url: "https://pub.dev"
1446
+ source: hosted
1447
+ version: "1.2.1"
11761448 win32:
11771449 dependency: transitive
11781450 description:
pubspec.yaml
....@@ -36,6 +36,7 @@
3636 connectivity_plus: ^7.1.0
3737 in_app_purchase: ^3.2.3
3838 url_launcher: ^6.3.2
39
+ pdfrx: ^1.0.100
3940
4041 dev_dependencies:
4142 flutter_test:
windows/flutter/generated_plugins.cmake
....@@ -15,6 +15,7 @@
1515 )
1616
1717 list(APPEND FLUTTER_FFI_PLUGIN_LIST
18
+ pdfrx
1819 )
1920
2021 set(PLUGIN_BUNDLED_LIBRARIES)