修复处理内容包括公告 WebView、文件预览页、分享页兜底获取下载链接的隐藏 WebView。

This commit is contained in:
2026-05-27 23:47:10 +08:00
parent 12f2c2660e
commit 24d20cbfcb
4 changed files with 545 additions and 242 deletions
@@ -1,11 +1,17 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart' as desktop;
import 'package:webview_flutter/webview_flutter.dart' as mobile;
import '../../../data/models/file_model.dart';
import '../../../services/api_service.dart';
bool get _useDesktopWebView =>
!kIsWeb && (Platform.isWindows || Platform.isLinux);
/// 完全交给 Cloudreve 官方 Web 前端处理文件打开。
///
/// 这里不再自己读取 file_viewers,也不再自己创建 viewerSession。
@@ -26,7 +32,8 @@ class CloudreveFileAppPage extends StatefulWidget {
}
class _CloudreveFileAppPageState extends State<CloudreveFileAppPage> {
late final WebViewController _controller;
mobile.WebViewController? _mobileController;
desktop.InAppWebViewController? _desktopController;
bool _isPreparing = true;
bool _sessionInjected = false;
@@ -40,36 +47,47 @@ class _CloudreveFileAppPageState extends State<CloudreveFileAppPage> {
void initState() {
super.initState();
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(Colors.transparent)
..setNavigationDelegate(
NavigationDelegate(
onProgress: (progress) {
if (!mounted) return;
setState(() => _progress = progress);
},
onPageFinished: (_) => _injectSessionAndOpenIfNeeded(),
onWebResourceError: (error) {
if (!mounted) return;
if (error.isForMainFrame == true) {
setState(() {
_error = '${error.errorCode}: ${error.description}';
});
}
},
),
);
if (!_useDesktopWebView) {
_mobileController = mobile.WebViewController()
..setJavaScriptMode(mobile.JavaScriptMode.unrestricted)
..setBackgroundColor(Colors.transparent)
..setNavigationDelegate(
mobile.NavigationDelegate(
onProgress: (progress) {
if (!mounted) return;
setState(() => _progress = progress);
},
onPageFinished: (_) => _injectSessionAndOpenIfNeeded(),
onWebResourceError: (error) {
if (!mounted) return;
if (error.isForMainFrame == true) {
setState(() {
_error = '${error.errorCode}: ${error.description}';
});
}
},
),
);
}
_prepareAndOpen();
}
@override
void dispose() {
_desktopController?.dispose();
super.dispose();
}
Future<void> _prepareAndOpen() async {
setState(() {
_isPreparing = true;
_error = null;
_progress = 0;
_sessionInjected = false;
if (_useDesktopWebView) {
_desktopController = null;
}
});
try {
@@ -80,7 +98,7 @@ class _CloudreveFileAppPageState extends State<CloudreveFileAppPage> {
// 先打开同源页面,再注入 localStorage。
// 这样 Cloudreve 官方前端可以直接复用 App 的 access_token。
await _controller.loadRequest(_originUri);
await _loadOrigin();
if (!mounted) return;
setState(() => _isPreparing = false);
@@ -105,7 +123,9 @@ class _CloudreveFileAppPageState extends State<CloudreveFileAppPage> {
Uri _buildCloudreveHomeUri() {
final parent = _parentUri(widget.file.relativePath);
final openTarget = widget.file.id.isNotEmpty ? widget.file.id : widget.file.relativePath;
final openTarget = widget.file.id.isNotEmpty
? widget.file.id
: widget.file.relativePath;
return Uri(
scheme: _originUri.scheme,
@@ -121,7 +141,9 @@ class _CloudreveFileAppPageState extends State<CloudreveFileAppPage> {
}
String _parentUri(String uri) {
final normalized = uri.endsWith('/') ? uri.substring(0, uri.length - 1) : uri;
final normalized = uri.endsWith('/')
? uri.substring(0, uri.length - 1)
: uri;
final index = normalized.lastIndexOf('/');
if (index <= 'cloudreve://my'.length) {
return 'cloudreve://my';
@@ -138,7 +160,9 @@ class _CloudreveFileAppPageState extends State<CloudreveFileAppPage> {
Map<String, dynamic>? user;
try {
final response = await ApiService.instance.get<Map<String, dynamic>>('/user/me');
final response = await ApiService.instance.get<Map<String, dynamic>>(
'/user/me',
);
user = Map<String, dynamic>.from(response);
} catch (_) {
user = null;
@@ -181,15 +205,16 @@ class _CloudreveFileAppPageState extends State<CloudreveFileAppPage> {
try {
if (sessionJson != null) {
final script = '''
final script =
'''
try {
localStorage.setItem('cloudreve_session', ${jsonEncode(sessionJson)});
} catch (e) {}
window.location.replace(${jsonEncode(target)});
''';
await _controller.runJavaScript(script);
await _runJavaScript(script);
} else {
await _controller.loadRequest(_targetUri);
await _loadTarget();
}
} catch (e) {
if (!mounted) return;
@@ -199,17 +224,52 @@ class _CloudreveFileAppPageState extends State<CloudreveFileAppPage> {
Future<void> _openTargetAgain() async {
_sessionInjected = true;
await _controller.loadRequest(_targetUri);
await _loadTarget();
}
Future<void> _reloadWebView() async {
if (_useDesktopWebView) {
await _desktopController?.reload();
} else {
await _mobileController?.reload();
}
}
Future<void> _loadOrigin() async {
if (_useDesktopWebView) {
final controller = _desktopController;
if (controller == null) return;
await _desktopController?.loadUrl(
urlRequest: desktop.URLRequest(url: desktop.WebUri.uri(_originUri)),
);
} else {
await _mobileController!.loadRequest(_originUri);
}
}
Future<void> _loadTarget() async {
if (_useDesktopWebView) {
await _desktopController?.loadUrl(
urlRequest: desktop.URLRequest(url: desktop.WebUri.uri(_targetUri)),
);
} else {
await _mobileController!.loadRequest(_targetUri);
}
}
Future<void> _runJavaScript(String source) async {
if (_useDesktopWebView) {
await _desktopController?.evaluateJavascript(source: source);
} else {
await _mobileController!.runJavaScript(source);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.file.name,
overflow: TextOverflow.ellipsis,
),
title: Text(widget.file.name, overflow: TextOverflow.ellipsis),
actions: [
IconButton(
tooltip: '重新打开',
@@ -219,7 +279,7 @@ class _CloudreveFileAppPageState extends State<CloudreveFileAppPage> {
IconButton(
tooltip: '刷新',
icon: const Icon(Icons.refresh),
onPressed: () => _controller.reload(),
onPressed: _reloadWebView,
),
],
bottom: PreferredSize(
@@ -260,6 +320,32 @@ class _CloudreveFileAppPageState extends State<CloudreveFileAppPage> {
);
}
return WebViewWidget(controller: _controller);
if (_useDesktopWebView) {
return desktop.InAppWebView(
initialSettings: desktop.InAppWebViewSettings(
javaScriptEnabled: true,
domStorageEnabled: true,
transparentBackground: true,
supportZoom: false,
),
onWebViewCreated: (controller) async {
_desktopController = controller;
if (!_isPreparing) {
await _loadOrigin();
}
},
onProgressChanged: (_, progress) {
if (!mounted) return;
setState(() => _progress = progress);
},
onLoadStop: (_, url) => _injectSessionAndOpenIfNeeded(),
onReceivedError: (_, request, error) {
if (!mounted || request.isForMainFrame != true) return;
setState(() => _error = '${error.type}: ${error.description}');
},
);
}
return mobile.WebViewWidget(controller: _mobileController!);
}
}