167 lines
5.0 KiB
Dart
167 lines
5.0 KiB
Dart
|
|
import 'dart:convert';
|
||
|
|
import 'dart:typed_data';
|
||
|
|
|
||
|
|
import 'package:dart_sm/dart_sm.dart';
|
||
|
|
import 'package:flutter/material.dart';
|
||
|
|
import 'package:image_picker/image_picker.dart';
|
||
|
|
import 'package:webview_flutter/webview_flutter.dart';
|
||
|
|
|
||
|
|
class WebViewMediaBridgePage extends StatefulWidget {
|
||
|
|
const WebViewMediaBridgePage({super.key, required this.userId});
|
||
|
|
|
||
|
|
final String userId;
|
||
|
|
|
||
|
|
@override
|
||
|
|
State<WebViewMediaBridgePage> createState() => _WebViewMediaBridgePageState();
|
||
|
|
}
|
||
|
|
|
||
|
|
class _WebViewMediaBridgePageState extends State<WebViewMediaBridgePage> {
|
||
|
|
static const _jsChannelName = 'WebViewBridge';
|
||
|
|
static const _publicKey =
|
||
|
|
'049818e94f1abefab63d7193dd71b16241b5e721ab936eb1d4abb84f5024cb5d5b503a1776163cce4dd0f0ffdc612329f3497da3ac9b643306d5bfbe844459d186';
|
||
|
|
static const _baseUrl =
|
||
|
|
'http://121.22.11.43:8091/emisApp/#/pages/index?user=';
|
||
|
|
|
||
|
|
final ImagePicker _imagePicker = ImagePicker();
|
||
|
|
late final WebViewController _controller;
|
||
|
|
|
||
|
|
@override
|
||
|
|
void initState() {
|
||
|
|
super.initState();
|
||
|
|
final encryptedUser = SM2.encrypt(widget.userId, _publicKey);
|
||
|
|
final targetUrl = '$_baseUrl$encryptedUser';
|
||
|
|
|
||
|
|
_controller =
|
||
|
|
WebViewController()
|
||
|
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||
|
|
..setBackgroundColor(Colors.white)
|
||
|
|
..setNavigationDelegate(
|
||
|
|
NavigationDelegate(
|
||
|
|
onPageFinished: (_) => _injectBridge(),
|
||
|
|
onWebResourceError: (error) {
|
||
|
|
debugPrint('设备管理 WebView 加载错误: ${error.description}');
|
||
|
|
},
|
||
|
|
),
|
||
|
|
)
|
||
|
|
..addJavaScriptChannel(
|
||
|
|
_jsChannelName,
|
||
|
|
onMessageReceived: (message) => _handleWebMessage(message.message),
|
||
|
|
)
|
||
|
|
..loadRequest(Uri.parse(targetUrl));
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> _injectBridge() {
|
||
|
|
return _controller.runJavaScript('''
|
||
|
|
window.flutterSendToDart = function(message) {
|
||
|
|
if (window.$_jsChannelName && window.$_jsChannelName.postMessage) {
|
||
|
|
window.$_jsChannelName.postMessage(String(message));
|
||
|
|
}
|
||
|
|
};
|
||
|
|
''');
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> _handleWebMessage(String raw) async {
|
||
|
|
Map<String, dynamic> data;
|
||
|
|
try {
|
||
|
|
data = jsonDecode(raw) as Map<String, dynamic>;
|
||
|
|
} catch (_) {
|
||
|
|
data = {'action': raw};
|
||
|
|
}
|
||
|
|
|
||
|
|
final action = data['action']?.toString();
|
||
|
|
final callbackId = data['callbackId']?.toString();
|
||
|
|
switch (action) {
|
||
|
|
case 'camera':
|
||
|
|
await _pickImageAndCallback(ImageSource.camera, callbackId);
|
||
|
|
return;
|
||
|
|
case 'gallery':
|
||
|
|
await _pickImageAndCallback(ImageSource.gallery, callbackId);
|
||
|
|
return;
|
||
|
|
case 'back':
|
||
|
|
if (mounted) Navigator.of(context).pop();
|
||
|
|
return;
|
||
|
|
case 'ping':
|
||
|
|
await _replyToWeb(callbackId, {
|
||
|
|
'ok': true,
|
||
|
|
'message': 'pong from flutter',
|
||
|
|
'time': DateTime.now().toIso8601String(),
|
||
|
|
});
|
||
|
|
return;
|
||
|
|
default:
|
||
|
|
debugPrint('设备管理 WebView 未知 action: $action');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> _pickImageAndCallback(
|
||
|
|
ImageSource source,
|
||
|
|
String? callbackId,
|
||
|
|
) async {
|
||
|
|
try {
|
||
|
|
final file = await _imagePicker.pickImage(
|
||
|
|
source: source,
|
||
|
|
imageQuality: 85,
|
||
|
|
);
|
||
|
|
if (file == null) {
|
||
|
|
await _replyToWeb(callbackId, {
|
||
|
|
'ok': false,
|
||
|
|
'cancelled': true,
|
||
|
|
'message': '用户取消选择',
|
||
|
|
});
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
final Uint8List bytes = await file.readAsBytes();
|
||
|
|
final base64Data = base64Encode(bytes);
|
||
|
|
final mimeType = _guessMimeType(file.path);
|
||
|
|
await _replyToWeb(callbackId, {
|
||
|
|
'ok': true,
|
||
|
|
'name': file.name,
|
||
|
|
'path': file.path,
|
||
|
|
'mimeType': mimeType,
|
||
|
|
'base64': base64Data,
|
||
|
|
'dataUrl': 'data:$mimeType;base64,$base64Data',
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
debugPrint('设备管理图片选择失败: $error');
|
||
|
|
await _replyToWeb(callbackId, {'ok': false, 'message': error.toString()});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
String _guessMimeType(String path) {
|
||
|
|
final lower = path.toLowerCase();
|
||
|
|
if (lower.endsWith('.png')) return 'image/png';
|
||
|
|
if (lower.endsWith('.webp')) return 'image/webp';
|
||
|
|
if (lower.endsWith('.gif')) return 'image/gif';
|
||
|
|
if (lower.endsWith('.heic')) return 'image/heic';
|
||
|
|
return 'image/jpeg';
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> _replyToWeb(String? callbackId, Map<String, dynamic> payload) {
|
||
|
|
final jsonText = jsonEncode(payload);
|
||
|
|
if (callbackId == null || callbackId.isEmpty) {
|
||
|
|
return _controller.runJavaScript('''
|
||
|
|
if (window.onFlutterResult) window.onFlutterResult($jsonText);
|
||
|
|
''');
|
||
|
|
}
|
||
|
|
return _controller.runJavaScript('''
|
||
|
|
if (window.WebViewCallbacks && window.WebViewCallbacks['$callbackId']) {
|
||
|
|
window.WebViewCallbacks['$callbackId']($jsonText);
|
||
|
|
delete window.WebViewCallbacks['$callbackId'];
|
||
|
|
} else if (window.onFlutterResult) {
|
||
|
|
window.onFlutterResult($jsonText);
|
||
|
|
}
|
||
|
|
''');
|
||
|
|
}
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
return Scaffold(
|
||
|
|
body: SafeArea(
|
||
|
|
top: false,
|
||
|
|
bottom: false,
|
||
|
|
child: WebViewWidget(controller: _controller),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|