1 files added
9 files modified
| .. | .. |
|---|
| 53 | 53 | - in_app_purchase_storekit (0.0.1): |
|---|
| 54 | 54 | - Flutter |
|---|
| 55 | 55 | - FlutterMacOS |
|---|
| 56 | + - pdfrx (0.0.6): |
|---|
| 57 | + - Flutter |
|---|
| 58 | + - FlutterMacOS |
|---|
| 56 | 59 | - permission_handler_apple (9.3.0): |
|---|
| 57 | 60 | - Flutter |
|---|
| 58 | 61 | - push (0.0.1): |
|---|
| .. | .. |
|---|
| 85 | 88 | - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) |
|---|
| 86 | 89 | - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) |
|---|
| 87 | 90 | - in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/darwin`) |
|---|
| 91 | + - pdfrx (from `.symlinks/plugins/pdfrx/darwin`) |
|---|
| 88 | 92 | - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) |
|---|
| 89 | 93 | - push (from `.symlinks/plugins/push/darwin`) |
|---|
| 90 | 94 | - record_ios (from `.symlinks/plugins/record_ios/ios`) |
|---|
| .. | .. |
|---|
| 121 | 125 | :path: ".symlinks/plugins/image_picker_ios/ios" |
|---|
| 122 | 126 | in_app_purchase_storekit: |
|---|
| 123 | 127 | :path: ".symlinks/plugins/in_app_purchase_storekit/darwin" |
|---|
| 128 | + pdfrx: |
|---|
| 129 | + :path: ".symlinks/plugins/pdfrx/darwin" |
|---|
| 124 | 130 | permission_handler_apple: |
|---|
| 125 | 131 | :path: ".symlinks/plugins/permission_handler_apple/ios" |
|---|
| 126 | 132 | push: |
|---|
| .. | .. |
|---|
| 149 | 155 | flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 |
|---|
| 150 | 156 | image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326 |
|---|
| 151 | 157 | in_app_purchase_storekit: 22cca7d08eebca9babdf4d07d0baccb73325d3c8 |
|---|
| 158 | + pdfrx: 310e84d01e06fd2af26e16507a0e48c27e99195c |
|---|
| 152 | 159 | permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d |
|---|
| 153 | 160 | push: 91373ae39c5341c6de6adefa3fda7f7287d646bf |
|---|
| 154 | 161 | record_ios: 412daca2350b228e698fffcd08f1f94ceb1e3844 |
|---|
| .. | .. |
|---|
| 13 | 13 | final String content; |
|---|
| 14 | 14 | final String? audioUri; |
|---|
| 15 | 15 | final String? imageBase64; |
|---|
| 16 | + final String? mimeType; |
|---|
| 16 | 17 | final int timestamp; |
|---|
| 17 | 18 | final MessageStatus? status; |
|---|
| 18 | 19 | final int? duration; |
|---|
| .. | .. |
|---|
| 25 | 26 | required this.timestamp, |
|---|
| 26 | 27 | this.audioUri, |
|---|
| 27 | 28 | this.imageBase64, |
|---|
| 29 | + this.mimeType, |
|---|
| 28 | 30 | this.status, |
|---|
| 29 | 31 | this.duration, |
|---|
| 30 | 32 | }); |
|---|
| .. | .. |
|---|
| 67 | 69 | required MessageRole role, |
|---|
| 68 | 70 | required String imageBase64, |
|---|
| 69 | 71 | String content = '', |
|---|
| 72 | + String? mimeType, |
|---|
| 70 | 73 | MessageStatus? status, |
|---|
| 71 | 74 | }) { |
|---|
| 72 | 75 | return Message( |
|---|
| .. | .. |
|---|
| 75 | 78 | type: MessageType.image, |
|---|
| 76 | 79 | content: content, |
|---|
| 77 | 80 | imageBase64: imageBase64, |
|---|
| 81 | + mimeType: mimeType, |
|---|
| 78 | 82 | timestamp: DateTime.now().millisecondsSinceEpoch, |
|---|
| 79 | 83 | status: status, |
|---|
| 80 | 84 | ); |
|---|
| .. | .. |
|---|
| 84 | 88 | String? content, |
|---|
| 85 | 89 | String? audioUri, |
|---|
| 86 | 90 | String? imageBase64, |
|---|
| 91 | + String? mimeType, |
|---|
| 87 | 92 | MessageStatus? status, |
|---|
| 88 | 93 | int? duration, |
|---|
| 89 | 94 | }) { |
|---|
| .. | .. |
|---|
| 94 | 99 | content: content ?? this.content, |
|---|
| 95 | 100 | audioUri: audioUri ?? this.audioUri, |
|---|
| 96 | 101 | imageBase64: imageBase64 ?? this.imageBase64, |
|---|
| 102 | + mimeType: mimeType ?? this.mimeType, |
|---|
| 97 | 103 | timestamp: timestamp, |
|---|
| 98 | 104 | status: status ?? this.status, |
|---|
| 99 | 105 | duration: duration ?? this.duration, |
|---|
| .. | .. |
|---|
| 108 | 114 | 'content': content, |
|---|
| 109 | 115 | if (audioUri != null) 'audioUri': audioUri, |
|---|
| 110 | 116 | if (imageBase64 != null) 'imageBase64': imageBase64, |
|---|
| 117 | + if (mimeType != null) 'mimeType': mimeType, |
|---|
| 111 | 118 | 'timestamp': timestamp, |
|---|
| 112 | 119 | if (status != null) 'status': status!.name, |
|---|
| 113 | 120 | if (duration != null) 'duration': duration, |
|---|
| .. | .. |
|---|
| 130 | 137 | if (duration != null) 'duration': duration, |
|---|
| 131 | 138 | // Keep imageBase64 — images are typically 50-200 KB and must survive restart. |
|---|
| 132 | 139 | if (imageBase64 != null) 'imageBase64': imageBase64, |
|---|
| 140 | + if (mimeType != null) 'mimeType': mimeType, |
|---|
| 133 | 141 | }; |
|---|
| 134 | 142 | } |
|---|
| 135 | 143 | |
|---|
| .. | .. |
|---|
| 141 | 149 | content: json['content'] as String? ?? '', |
|---|
| 142 | 150 | audioUri: json['audioUri'] as String?, |
|---|
| 143 | 151 | imageBase64: json['imageBase64'] as String?, |
|---|
| 152 | + mimeType: json['mimeType'] as String?, |
|---|
| 144 | 153 | timestamp: json['timestamp'] as int, |
|---|
| 145 | 154 | status: json['status'] != null |
|---|
| 146 | 155 | ? MessageStatus.values.byName(json['status'] as String) |
|---|
| .. | .. |
|---|
| 413 | 413 | role: MessageRole.assistant, |
|---|
| 414 | 414 | imageBase64: imageData, |
|---|
| 415 | 415 | content: content, |
|---|
| 416 | + mimeType: map['mimeType'] as String?, |
|---|
| 416 | 417 | status: MessageStatus.sent, |
|---|
| 417 | 418 | ); |
|---|
| 418 | 419 | } else { |
|---|
| .. | .. |
|---|
| 679 | 680 | _screenshotForChat = false; |
|---|
| 680 | 681 | } |
|---|
| 681 | 682 | |
|---|
| 683 | + final mimeType = msg['mimeType'] as String?; |
|---|
| 682 | 684 | final message = Message.image( |
|---|
| 683 | 685 | role: MessageRole.assistant, |
|---|
| 684 | 686 | imageBase64: imageData, |
|---|
| 685 | 687 | content: content, |
|---|
| 688 | + mimeType: mimeType, |
|---|
| 686 | 689 | status: MessageStatus.sent, |
|---|
| 687 | 690 | ); |
|---|
| 688 | 691 | |
|---|
| .. | .. |
|---|
| 68 | 68 | // (Per-session subscriptions removed — single pailot/out topic now) |
|---|
| 69 | 69 | static const int _maxSeenIds = 500; |
|---|
| 70 | 70 | |
|---|
| 71 | + // Reconnect backoff |
|---|
| 72 | + Timer? _reconnectTimer; |
|---|
| 73 | + Timer? _stabilityTimer; |
|---|
| 74 | + int _reconnectAttempt = 0; |
|---|
| 75 | + static const int _maxReconnectDelay = 30000; // 30s cap |
|---|
| 76 | + static const int _stabilityThresholdMs = 10000; // 10s stable = reset backoff |
|---|
| 77 | + |
|---|
| 71 | 78 | // Callbacks |
|---|
| 72 | 79 | void Function(ConnectionStatus status)? onStatusChanged; |
|---|
| 73 | 80 | void Function(String detail)? onStatusDetail; // "Probing local...", "Scanning network..." |
|---|
| .. | .. |
|---|
| 120 | 127 | } |
|---|
| 121 | 128 | |
|---|
| 122 | 129 | /// Fast reconnect to a known host — skips discovery, short timeout. |
|---|
| 123 | | - Future<void> _fastReconnect(String host) async { |
|---|
| 130 | + /// Returns true if connected, false if failed. |
|---|
| 131 | + Future<bool> _fastReconnect(String host) async { |
|---|
| 124 | 132 | _mqttLog('MQTT: fast reconnect to $host'); |
|---|
| 125 | 133 | final clientId = await _getClientId(); |
|---|
| 126 | 134 | if (await _tryConnect(host, clientId, timeout: 2000)) { |
|---|
| 127 | 135 | connectedHost = host; |
|---|
| 128 | | - return; |
|---|
| 136 | + return true; |
|---|
| 129 | 137 | } |
|---|
| 130 | | - // Fast path failed — fall back to full connect |
|---|
| 131 | | - _mqttLog('MQTT: fast reconnect failed, full connect...'); |
|---|
| 132 | | - connect(); |
|---|
| 138 | + _mqttLog('MQTT: fast reconnect failed'); |
|---|
| 139 | + return false; |
|---|
| 133 | 140 | } |
|---|
| 134 | 141 | |
|---|
| 135 | 142 | /// Connect to the MQTT broker. |
|---|
| .. | .. |
|---|
| 440 | 447 | ); |
|---|
| 441 | 448 | _mqttLog('MQTT: connect result=${result?.state}'); |
|---|
| 442 | 449 | if (result?.state == MqttConnectionState.connected) { |
|---|
| 443 | | - client.autoReconnect = true; |
|---|
| 450 | + // Don't use autoReconnect — it has no backoff and causes tight reconnect loops. |
|---|
| 451 | + // We handle reconnection manually in _onDisconnected with exponential backoff. |
|---|
| 452 | + _reconnectAttempt = 0; |
|---|
| 444 | 453 | return true; |
|---|
| 445 | 454 | } |
|---|
| 446 | 455 | _client = null; |
|---|
| .. | .. |
|---|
| 454 | 463 | |
|---|
| 455 | 464 | void _onConnected() { |
|---|
| 456 | 465 | _mqttLog('MQTT: _onConnected fired'); |
|---|
| 466 | + _reconnectTimer?.cancel(); |
|---|
| 467 | + // Don't reset _reconnectAttempt here — only after the connection has been |
|---|
| 468 | + // STABLE for 10+ seconds. This prevents flap loops where each brief connect |
|---|
| 469 | + // resets the backoff and we hammer the server every 5s forever. |
|---|
| 470 | + _stabilityTimer?.cancel(); |
|---|
| 471 | + _stabilityTimer = Timer(const Duration(milliseconds: _stabilityThresholdMs), () { |
|---|
| 472 | + if (_status == ConnectionStatus.connected) { |
|---|
| 473 | + _mqttLog('MQTT: connection stable for ${_stabilityThresholdMs}ms — resetting backoff'); |
|---|
| 474 | + _reconnectAttempt = 0; |
|---|
| 475 | + } |
|---|
| 476 | + }); |
|---|
| 457 | 477 | _setStatus(ConnectionStatus.connected); |
|---|
| 458 | 478 | _subscribe(); |
|---|
| 459 | 479 | _listenMessages(); |
|---|
| .. | .. |
|---|
| 461 | 481 | } |
|---|
| 462 | 482 | |
|---|
| 463 | 483 | void _onDisconnected() { |
|---|
| 484 | + _stabilityTimer?.cancel(); |
|---|
| 464 | 485 | _updatesSub?.cancel(); |
|---|
| 465 | 486 | _updatesSub = null; |
|---|
| 466 | 487 | |
|---|
| .. | .. |
|---|
| 470 | 491 | } else { |
|---|
| 471 | 492 | _setStatus(ConnectionStatus.reconnecting); |
|---|
| 472 | 493 | onReconnecting?.call(); |
|---|
| 494 | + _scheduleReconnect(); |
|---|
| 473 | 495 | } |
|---|
| 474 | 496 | } |
|---|
| 475 | 497 | |
|---|
| 498 | + void _scheduleReconnect() { |
|---|
| 499 | + _reconnectTimer?.cancel(); |
|---|
| 500 | + // Exponential backoff: 1s, 2s, 4s, 8s, 16s, 30s cap |
|---|
| 501 | + final delayMs = (1000 * (1 << _reconnectAttempt)).clamp(1000, _maxReconnectDelay); |
|---|
| 502 | + _reconnectAttempt++; |
|---|
| 503 | + _mqttLog('MQTT: scheduling reconnect in ${delayMs}ms (attempt $_reconnectAttempt)'); |
|---|
| 504 | + _reconnectTimer = Timer(Duration(milliseconds: delayMs), () async { |
|---|
| 505 | + if (_intentionalClose || _status == ConnectionStatus.connected) return; |
|---|
| 506 | + final host = connectedHost ?? _lastDiscoveredHost; |
|---|
| 507 | + if (host != null) { |
|---|
| 508 | + _mqttLog('MQTT: reconnect attempt $_reconnectAttempt to $host'); |
|---|
| 509 | + final ok = await _fastReconnect(host); |
|---|
| 510 | + if (!ok && !_intentionalClose) { |
|---|
| 511 | + _scheduleReconnect(); // Try again with increased backoff |
|---|
| 512 | + } |
|---|
| 513 | + } else { |
|---|
| 514 | + _mqttLog('MQTT: no known host, running full connect'); |
|---|
| 515 | + await connect(); |
|---|
| 516 | + } |
|---|
| 517 | + }); |
|---|
| 518 | + } |
|---|
| 519 | + |
|---|
| 476 | 520 | void _onAutoReconnect() { |
|---|
| 521 | + // Unused — autoReconnect is disabled, but keep callback for safety |
|---|
| 477 | 522 | _setStatus(ConnectionStatus.reconnecting); |
|---|
| 478 | 523 | onReconnecting?.call(); |
|---|
| 479 | 524 | } |
|---|
| 480 | 525 | |
|---|
| 481 | 526 | void _onAutoReconnected() { |
|---|
| 527 | + // Unused — autoReconnect is disabled, but keep callback for safety |
|---|
| 528 | + _reconnectAttempt = 0; |
|---|
| 482 | 529 | _setStatus(ConnectionStatus.connected); |
|---|
| 483 | 530 | _subscribe(); |
|---|
| 484 | 531 | _listenMessages(); |
|---|
| .. | .. |
|---|
| 764 | 811 | /// Disconnect intentionally. |
|---|
| 765 | 812 | void disconnect() { |
|---|
| 766 | 813 | _intentionalClose = true; |
|---|
| 814 | + _reconnectTimer?.cancel(); |
|---|
| 815 | + _reconnectTimer = null; |
|---|
| 816 | + _stabilityTimer?.cancel(); |
|---|
| 817 | + _stabilityTimer = null; |
|---|
| 818 | + _reconnectAttempt = 0; |
|---|
| 767 | 819 | _updatesSub?.cancel(); |
|---|
| 768 | 820 | _updatesSub = null; |
|---|
| 769 | 821 | _connectivitySub?.cancel(); |
|---|
| .. | .. |
|---|
| 799 | 851 | case AppLifecycleState.resumed: |
|---|
| 800 | 852 | if (_intentionalClose) break; |
|---|
| 801 | 853 | _mqttLog('MQTT: app resumed'); |
|---|
| 802 | | - // Let autoReconnect handle dead connections (keepalive timeout). |
|---|
| 803 | | - // Just trigger catch_up to fetch missed messages and rebuild UI. |
|---|
| 854 | + // If disconnected, trigger immediate reconnect (reset backoff). |
|---|
| 855 | + if (_status != ConnectionStatus.connected) { |
|---|
| 856 | + _reconnectAttempt = 0; |
|---|
| 857 | + _scheduleReconnect(); |
|---|
| 858 | + } |
|---|
| 859 | + // Trigger catch_up to fetch missed messages and rebuild UI. |
|---|
| 804 | 860 | onResume?.call(); |
|---|
| 805 | 861 | case AppLifecycleState.paused: |
|---|
| 806 | 862 | break; |
|---|
| .. | .. |
|---|
| 1 | 1 | import 'dart:convert'; |
|---|
| 2 | +import 'dart:io'; |
|---|
| 2 | 3 | import 'dart:math'; |
|---|
| 3 | 4 | import 'dart:typed_data'; |
|---|
| 4 | 5 | |
|---|
| 5 | 6 | import 'package:flutter/material.dart'; |
|---|
| 6 | 7 | import 'package:flutter/services.dart'; |
|---|
| 7 | 8 | import 'package:flutter_markdown/flutter_markdown.dart'; |
|---|
| 9 | +import 'package:path_provider/path_provider.dart'; |
|---|
| 10 | +import 'package:share_plus/share_plus.dart'; |
|---|
| 8 | 11 | import 'package:url_launcher/url_launcher.dart'; |
|---|
| 9 | 12 | import 'package:intl/intl.dart'; |
|---|
| 10 | 13 | |
|---|
| 11 | 14 | import '../models/message.dart'; |
|---|
| 12 | 15 | import '../theme/app_theme.dart'; |
|---|
| 13 | 16 | import 'image_viewer.dart'; |
|---|
| 17 | +import 'pdf_viewer.dart'; |
|---|
| 14 | 18 | |
|---|
| 15 | 19 | // Cache decoded image bytes to prevent flicker on widget rebuild |
|---|
| 16 | 20 | final Map<String, Uint8List> _imageCache = {}; |
|---|
| .. | .. |
|---|
| 263 | 267 | ); |
|---|
| 264 | 268 | } |
|---|
| 265 | 269 | |
|---|
| 270 | + /// True if the mimeType is a renderable image format. |
|---|
| 271 | + bool get _isImageMime { |
|---|
| 272 | + final mime = message.mimeType?.toLowerCase() ?? 'image/jpeg'; |
|---|
| 273 | + return mime.startsWith('image/'); |
|---|
| 274 | + } |
|---|
| 275 | + |
|---|
| 266 | 276 | Widget _buildImageContent(BuildContext context) { |
|---|
| 267 | 277 | if (message.imageBase64 == null || message.imageBase64!.isEmpty) { |
|---|
| 268 | 278 | return const Text('Image unavailable'); |
|---|
| 279 | + } |
|---|
| 280 | + |
|---|
| 281 | + // Non-image files (PDF, CSV, etc.) — show file card instead of Image.memory |
|---|
| 282 | + if (!_isImageMime) { |
|---|
| 283 | + return _buildFileCard(context); |
|---|
| 269 | 284 | } |
|---|
| 270 | 285 | |
|---|
| 271 | 286 | // Cache decoded bytes to prevent flicker on rebuild; evict oldest if over 50 entries |
|---|
| .. | .. |
|---|
| 322 | 337 | ); |
|---|
| 323 | 338 | } |
|---|
| 324 | 339 | |
|---|
| 340 | + Widget _buildFileCard(BuildContext context) { |
|---|
| 341 | + final mime = message.mimeType ?? 'application/octet-stream'; |
|---|
| 342 | + final caption = message.content.isNotEmpty ? message.content : 'File'; |
|---|
| 343 | + final isPdf = mime == 'application/pdf'; |
|---|
| 344 | + |
|---|
| 345 | + // File type icon |
|---|
| 346 | + IconData icon; |
|---|
| 347 | + Color iconColor; |
|---|
| 348 | + if (isPdf) { |
|---|
| 349 | + icon = Icons.picture_as_pdf; |
|---|
| 350 | + iconColor = Colors.red; |
|---|
| 351 | + } else if (mime.contains('spreadsheet') || mime.contains('excel') || mime == 'text/csv') { |
|---|
| 352 | + icon = Icons.table_chart; |
|---|
| 353 | + iconColor = Colors.green; |
|---|
| 354 | + } else if (mime.contains('word') || mime.contains('document')) { |
|---|
| 355 | + icon = Icons.description; |
|---|
| 356 | + iconColor = Colors.blue; |
|---|
| 357 | + } else if (mime == 'text/plain' || mime == 'application/json') { |
|---|
| 358 | + icon = Icons.text_snippet; |
|---|
| 359 | + iconColor = Colors.grey; |
|---|
| 360 | + } else { |
|---|
| 361 | + icon = Icons.insert_drive_file; |
|---|
| 362 | + iconColor = Colors.blueGrey; |
|---|
| 363 | + } |
|---|
| 364 | + |
|---|
| 365 | + final sizeKB = ((message.imageBase64?.length ?? 0) * 3 / 4 / 1024).round(); |
|---|
| 366 | + |
|---|
| 367 | + return GestureDetector( |
|---|
| 368 | + onTap: () => _openFile(context), |
|---|
| 369 | + child: Container( |
|---|
| 370 | + width: 260, |
|---|
| 371 | + padding: const EdgeInsets.all(12), |
|---|
| 372 | + decoration: BoxDecoration( |
|---|
| 373 | + color: (_isUser ? Colors.white : Theme.of(context).colorScheme.primary).withAlpha(25), |
|---|
| 374 | + borderRadius: BorderRadius.circular(8), |
|---|
| 375 | + border: Border.all( |
|---|
| 376 | + color: (_isUser ? Colors.white : Colors.grey).withAlpha(50), |
|---|
| 377 | + ), |
|---|
| 378 | + ), |
|---|
| 379 | + child: Column( |
|---|
| 380 | + crossAxisAlignment: CrossAxisAlignment.start, |
|---|
| 381 | + children: [ |
|---|
| 382 | + Row( |
|---|
| 383 | + children: [ |
|---|
| 384 | + Icon(icon, size: 32, color: iconColor), |
|---|
| 385 | + const SizedBox(width: 10), |
|---|
| 386 | + Expanded( |
|---|
| 387 | + child: Column( |
|---|
| 388 | + crossAxisAlignment: CrossAxisAlignment.start, |
|---|
| 389 | + children: [ |
|---|
| 390 | + Text( |
|---|
| 391 | + caption, |
|---|
| 392 | + style: TextStyle( |
|---|
| 393 | + fontSize: 14, |
|---|
| 394 | + fontWeight: FontWeight.w600, |
|---|
| 395 | + color: _isUser ? Colors.white : null, |
|---|
| 396 | + ), |
|---|
| 397 | + maxLines: 2, |
|---|
| 398 | + overflow: TextOverflow.ellipsis, |
|---|
| 399 | + ), |
|---|
| 400 | + const SizedBox(height: 2), |
|---|
| 401 | + Text( |
|---|
| 402 | + '${mime.split('/').last.toUpperCase()} - ${sizeKB} KB', |
|---|
| 403 | + style: TextStyle( |
|---|
| 404 | + fontSize: 11, |
|---|
| 405 | + color: (_isUser ? Colors.white : Colors.grey).withAlpha(180), |
|---|
| 406 | + ), |
|---|
| 407 | + ), |
|---|
| 408 | + ], |
|---|
| 409 | + ), |
|---|
| 410 | + ), |
|---|
| 411 | + Icon( |
|---|
| 412 | + Icons.open_in_new, |
|---|
| 413 | + size: 20, |
|---|
| 414 | + color: (_isUser ? Colors.white : Colors.grey).withAlpha(150), |
|---|
| 415 | + ), |
|---|
| 416 | + ], |
|---|
| 417 | + ), |
|---|
| 418 | + ], |
|---|
| 419 | + ), |
|---|
| 420 | + ), |
|---|
| 421 | + ); |
|---|
| 422 | + } |
|---|
| 423 | + |
|---|
| 424 | + Future<void> _openFile(BuildContext context) async { |
|---|
| 425 | + final data = message.imageBase64; |
|---|
| 426 | + if (data == null || data.isEmpty) return; |
|---|
| 427 | + |
|---|
| 428 | + try { |
|---|
| 429 | + final bytes = Uint8List.fromList( |
|---|
| 430 | + base64Decode(data.contains(',') ? data.split(',').last : data), |
|---|
| 431 | + ); |
|---|
| 432 | + 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 |
|---|
| 450 | + final ext = _mimeToExt(mime); |
|---|
| 451 | + final dir = await getTemporaryDirectory(); |
|---|
| 452 | + final fileName = '${message.content.isNotEmpty ? message.content.replaceAll(RegExp(r'[^\w\s.-]'), '').trim() : 'file'}.$ext'; |
|---|
| 453 | + final file = File('${dir.path}/$fileName'); |
|---|
| 454 | + await file.writeAsBytes(bytes); |
|---|
| 455 | + |
|---|
| 456 | + await SharePlus.instance.share( |
|---|
| 457 | + ShareParams(files: [XFile(file.path, mimeType: mime)]), |
|---|
| 458 | + ); |
|---|
| 459 | + } catch (e) { |
|---|
| 460 | + if (context.mounted) { |
|---|
| 461 | + ScaffoldMessenger.of(context).showSnackBar( |
|---|
| 462 | + SnackBar(content: Text('Could not open file: $e')), |
|---|
| 463 | + ); |
|---|
| 464 | + } |
|---|
| 465 | + } |
|---|
| 466 | + } |
|---|
| 467 | + |
|---|
| 468 | + String _mimeToExt(String mime) { |
|---|
| 469 | + const map = { |
|---|
| 470 | + 'application/pdf': 'pdf', |
|---|
| 471 | + 'application/msword': 'doc', |
|---|
| 472 | + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx', |
|---|
| 473 | + 'application/vnd.ms-excel': 'xls', |
|---|
| 474 | + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx', |
|---|
| 475 | + 'text/plain': 'txt', |
|---|
| 476 | + 'text/csv': 'csv', |
|---|
| 477 | + 'application/json': 'json', |
|---|
| 478 | + }; |
|---|
| 479 | + return map[mime] ?? 'bin'; |
|---|
| 480 | + } |
|---|
| 481 | + |
|---|
| 325 | 482 | Widget _buildFooter(BuildContext context) { |
|---|
| 326 | 483 | final isDark = Theme.of(context).brightness == Brightness.dark; |
|---|
| 327 | 484 | final dt = DateTime.fromMillisecondsSinceEpoch(message.timestamp); |
|---|
| .. | .. |
|---|
| 1 | +import 'dart:io'; |
|---|
| 2 | +import 'dart:typed_data'; |
|---|
| 3 | + |
|---|
| 4 | +import 'package:flutter/material.dart'; |
|---|
| 5 | +import 'package:path_provider/path_provider.dart'; |
|---|
| 6 | +import 'package:pdfrx/pdfrx.dart'; |
|---|
| 7 | +import 'package:share_plus/share_plus.dart'; |
|---|
| 8 | + |
|---|
| 9 | +/// Full-screen PDF viewer with pinch-to-zoom, page navigation, and share/save. |
|---|
| 10 | +class PdfViewerScreen extends StatelessWidget { |
|---|
| 11 | + final Uint8List pdfBytes; |
|---|
| 12 | + final String title; |
|---|
| 13 | + |
|---|
| 14 | + const PdfViewerScreen({ |
|---|
| 15 | + super.key, |
|---|
| 16 | + required this.pdfBytes, |
|---|
| 17 | + this.title = 'PDF', |
|---|
| 18 | + }); |
|---|
| 19 | + |
|---|
| 20 | + Future<void> _share(BuildContext context) async { |
|---|
| 21 | + try { |
|---|
| 22 | + final dir = await getTemporaryDirectory(); |
|---|
| 23 | + final safeName = title.replaceAll(RegExp(r'[^\w\s.-]'), '').trim(); |
|---|
| 24 | + final fileName = safeName.endsWith('.pdf') ? safeName : '$safeName.pdf'; |
|---|
| 25 | + final file = File('${dir.path}/$fileName'); |
|---|
| 26 | + await file.writeAsBytes(pdfBytes); |
|---|
| 27 | + await SharePlus.instance.share( |
|---|
| 28 | + ShareParams(files: [XFile(file.path, mimeType: 'application/pdf')]), |
|---|
| 29 | + ); |
|---|
| 30 | + } catch (e) { |
|---|
| 31 | + if (context.mounted) { |
|---|
| 32 | + ScaffoldMessenger.of(context).showSnackBar( |
|---|
| 33 | + SnackBar(content: Text('Share failed: $e')), |
|---|
| 34 | + ); |
|---|
| 35 | + } |
|---|
| 36 | + } |
|---|
| 37 | + } |
|---|
| 38 | + |
|---|
| 39 | + @override |
|---|
| 40 | + Widget build(BuildContext context) { |
|---|
| 41 | + return Scaffold( |
|---|
| 42 | + appBar: AppBar( |
|---|
| 43 | + title: Text(title, style: const TextStyle(fontSize: 16)), |
|---|
| 44 | + backgroundColor: Colors.black87, |
|---|
| 45 | + foregroundColor: Colors.white, |
|---|
| 46 | + actions: [ |
|---|
| 47 | + IconButton( |
|---|
| 48 | + icon: const Icon(Icons.ios_share), |
|---|
| 49 | + tooltip: 'Share / Save', |
|---|
| 50 | + onPressed: () => _share(context), |
|---|
| 51 | + ), |
|---|
| 52 | + ], |
|---|
| 53 | + ), |
|---|
| 54 | + backgroundColor: Colors.grey[900], |
|---|
| 55 | + body: PdfViewer.data(pdfBytes, sourceName: title), |
|---|
| 56 | + ); |
|---|
| 57 | + } |
|---|
| 58 | +} |
|---|
| .. | .. |
|---|
| 11 | 11 | ) |
|---|
| 12 | 12 | |
|---|
| 13 | 13 | list(APPEND FLUTTER_FFI_PLUGIN_LIST |
|---|
| 14 | + pdfrx |
|---|
| 14 | 15 | ) |
|---|
| 15 | 16 | |
|---|
| 16 | 17 | set(PLUGIN_BUNDLED_LIBRARIES) |
|---|
| .. | .. |
|---|
| 1 | 1 | # Generated by pub |
|---|
| 2 | 2 | # See https://dart.dev/tools/pub/glossary#lockfile |
|---|
| 3 | 3 | 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" |
|---|
| 4 | 20 | args: |
|---|
| 5 | 21 | dependency: transitive |
|---|
| 6 | 22 | description: |
|---|
| .. | .. |
|---|
| 137 | 153 | url: "https://pub.dev" |
|---|
| 138 | 154 | source: hosted |
|---|
| 139 | 155 | 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" |
|---|
| 140 | 180 | clock: |
|---|
| 141 | 181 | dependency: transitive |
|---|
| 142 | 182 | description: |
|---|
| .. | .. |
|---|
| 177 | 217 | url: "https://pub.dev" |
|---|
| 178 | 218 | source: hosted |
|---|
| 179 | 219 | 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" |
|---|
| 180 | 236 | cross_file: |
|---|
| 181 | 237 | dependency: transitive |
|---|
| 182 | 238 | description: |
|---|
| .. | .. |
|---|
| 193 | 249 | url: "https://pub.dev" |
|---|
| 194 | 250 | source: hosted |
|---|
| 195 | 251 | 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" |
|---|
| 196 | 260 | cupertino_icons: |
|---|
| 197 | 261 | dependency: "direct main" |
|---|
| 198 | 262 | description: |
|---|
| .. | .. |
|---|
| 201 | 265 | url: "https://pub.dev" |
|---|
| 202 | 266 | source: hosted |
|---|
| 203 | 267 | 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" |
|---|
| 204 | 276 | dbus: |
|---|
| 205 | 277 | dependency: transitive |
|---|
| 206 | 278 | description: |
|---|
| .. | .. |
|---|
| 408 | 480 | description: flutter |
|---|
| 409 | 481 | source: sdk |
|---|
| 410 | 482 | 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" |
|---|
| 411 | 491 | glob: |
|---|
| 412 | 492 | dependency: transitive |
|---|
| 413 | 493 | description: |
|---|
| .. | .. |
|---|
| 432 | 512 | url: "https://pub.dev" |
|---|
| 433 | 513 | source: hosted |
|---|
| 434 | 514 | 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" |
|---|
| 435 | 523 | http: |
|---|
| 436 | 524 | dependency: transitive |
|---|
| 437 | 525 | description: |
|---|
| .. | .. |
|---|
| 440 | 528 | url: "https://pub.dev" |
|---|
| 441 | 529 | source: hosted |
|---|
| 442 | 530 | 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" |
|---|
| 443 | 539 | http_parser: |
|---|
| 444 | 540 | dependency: transitive |
|---|
| 445 | 541 | description: |
|---|
| .. | .. |
|---|
| 552 | 648 | url: "https://pub.dev" |
|---|
| 553 | 649 | source: hosted |
|---|
| 554 | 650 | 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" |
|---|
| 555 | 659 | js: |
|---|
| 556 | 660 | dependency: transitive |
|---|
| 557 | 661 | description: |
|---|
| .. | .. |
|---|
| 672 | 776 | url: "https://pub.dev" |
|---|
| 673 | 777 | source: hosted |
|---|
| 674 | 778 | 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" |
|---|
| 675 | 787 | objective_c: |
|---|
| 676 | 788 | dependency: transitive |
|---|
| 677 | 789 | description: |
|---|
| .. | .. |
|---|
| 680 | 792 | url: "https://pub.dev" |
|---|
| 681 | 793 | source: hosted |
|---|
| 682 | 794 | 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" |
|---|
| 683 | 811 | path: |
|---|
| 684 | 812 | dependency: transitive |
|---|
| 685 | 813 | description: |
|---|
| .. | .. |
|---|
| 736 | 864 | url: "https://pub.dev" |
|---|
| 737 | 865 | source: hosted |
|---|
| 738 | 866 | 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" |
|---|
| 739 | 875 | permission_handler: |
|---|
| 740 | 876 | dependency: "direct main" |
|---|
| 741 | 877 | description: |
|---|
| .. | .. |
|---|
| 808 | 944 | url: "https://pub.dev" |
|---|
| 809 | 945 | source: hosted |
|---|
| 810 | 946 | 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" |
|---|
| 811 | 955 | pub_semver: |
|---|
| 812 | 956 | dependency: transitive |
|---|
| 813 | 957 | description: |
|---|
| .. | .. |
|---|
| 816 | 960 | url: "https://pub.dev" |
|---|
| 817 | 961 | source: hosted |
|---|
| 818 | 962 | 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" |
|---|
| 819 | 971 | push: |
|---|
| 820 | 972 | dependency: "direct main" |
|---|
| 821 | 973 | description: |
|---|
| .. | .. |
|---|
| 888 | 1040 | url: "https://pub.dev" |
|---|
| 889 | 1041 | source: hosted |
|---|
| 890 | 1042 | 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" |
|---|
| 891 | 1051 | riverpod: |
|---|
| 892 | 1052 | dependency: transitive |
|---|
| 893 | 1053 | description: |
|---|
| .. | .. |
|---|
| 904 | 1064 | url: "https://pub.dev" |
|---|
| 905 | 1065 | source: hosted |
|---|
| 906 | 1066 | 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" |
|---|
| 907 | 1083 | share_plus: |
|---|
| 908 | 1084 | dependency: "direct main" |
|---|
| 909 | 1085 | description: |
|---|
| .. | .. |
|---|
| 976 | 1152 | url: "https://pub.dev" |
|---|
| 977 | 1153 | source: hosted |
|---|
| 978 | 1154 | 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" |
|---|
| 979 | 1187 | sky_engine: |
|---|
| 980 | 1188 | dependency: transitive |
|---|
| 981 | 1189 | description: flutter |
|---|
| 982 | 1190 | source: sdk |
|---|
| 983 | 1191 | 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" |
|---|
| 984 | 1208 | source_span: |
|---|
| 985 | 1209 | dependency: transitive |
|---|
| 986 | 1210 | description: |
|---|
| .. | .. |
|---|
| 1037 | 1261 | url: "https://pub.dev" |
|---|
| 1038 | 1262 | source: hosted |
|---|
| 1039 | 1263 | 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" |
|---|
| 1040 | 1272 | test_api: |
|---|
| 1041 | 1273 | dependency: transitive |
|---|
| 1042 | 1274 | description: |
|---|
| .. | .. |
|---|
| 1045 | 1277 | url: "https://pub.dev" |
|---|
| 1046 | 1278 | source: hosted |
|---|
| 1047 | 1279 | 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" |
|---|
| 1048 | 1288 | typed_data: |
|---|
| 1049 | 1289 | dependency: transitive |
|---|
| 1050 | 1290 | description: |
|---|
| .. | .. |
|---|
| 1165 | 1405 | url: "https://pub.dev" |
|---|
| 1166 | 1406 | source: hosted |
|---|
| 1167 | 1407 | 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" |
|---|
| 1168 | 1416 | web: |
|---|
| 1169 | 1417 | dependency: transitive |
|---|
| 1170 | 1418 | description: |
|---|
| .. | .. |
|---|
| 1173 | 1421 | url: "https://pub.dev" |
|---|
| 1174 | 1422 | source: hosted |
|---|
| 1175 | 1423 | 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" |
|---|
| 1176 | 1448 | win32: |
|---|
| 1177 | 1449 | dependency: transitive |
|---|
| 1178 | 1450 | description: |
|---|
| .. | .. |
|---|
| 36 | 36 | connectivity_plus: ^7.1.0 |
|---|
| 37 | 37 | in_app_purchase: ^3.2.3 |
|---|
| 38 | 38 | url_launcher: ^6.3.2 |
|---|
| 39 | + pdfrx: ^1.0.100 |
|---|
| 39 | 40 | |
|---|
| 40 | 41 | dev_dependencies: |
|---|
| 41 | 42 | flutter_test: |
|---|
| .. | .. |
|---|
| 15 | 15 | ) |
|---|
| 16 | 16 | |
|---|
| 17 | 17 | list(APPEND FLUTTER_FFI_PLUGIN_LIST |
|---|
| 18 | + pdfrx |
|---|
| 18 | 19 | ) |
|---|
| 19 | 20 | |
|---|
| 20 | 21 | set(PLUGIN_BUNDLED_LIBRARIES) |
|---|