Files
PiliPlus/lib/pages/webview/webview_page.dart
bggRGjQaUbCoE cc774015f9 feat: fav note page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-03-28 22:35:38 +08:00

318 lines
12 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/utils/accounts/account.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/cache_manage.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
enum _WebviewMenuItem {
refresh,
copy,
openInBrowser,
clearCache,
resetCookie,
goBack,
}
extension _WebviewMenuItemExt on _WebviewMenuItem {
String get title => [
'刷新',
'复制链接',
'浏览器中打开',
'清除缓存',
'重新设置Cookie',
'返回',
][index];
}
class WebviewPageNew extends StatefulWidget {
const WebviewPageNew(
{super.key, this.url, this.oid, this.title, this.uaType});
final String? url;
// note
final int? oid;
final String? title;
final String? uaType;
@override
State<WebviewPageNew> createState() => _WebviewPageNewState();
}
class _WebviewPageNewState extends State<WebviewPageNew> {
late final String _url = widget.url ?? Get.parameters['url'] ?? '';
late final uaType = widget.uaType ?? Get.parameters['uaType'] ?? 'mob';
final _titleStream = StreamController<String?>();
final _progressStream = StreamController<double>();
bool? _inApp;
bool? _off;
InAppWebViewController? _webViewController;
@override
void initState() {
super.initState();
if (Get.arguments is Map) {
_inApp = Get.arguments['inApp'];
_off = Get.arguments['off'];
}
}
@override
void dispose() {
_titleStream.close();
_progressStream.close();
_webViewController = null;
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: widget.url != null
? null
: AppBar(
title: StreamBuilder(
initialData: null,
stream: _titleStream.stream,
builder: (context, snapshot) => Text(
maxLines: 1,
snapshot.hasData ? snapshot.data! : _url,
overflow: TextOverflow.ellipsis,
),
),
bottom: PreferredSize(
preferredSize: Size.zero,
child: StreamBuilder(
initialData: 0.0,
stream: _progressStream.stream,
builder: (context, snapshot) => snapshot.data as double < 1
? LinearProgressIndicator(
value: snapshot.data as double,
)
: const SizedBox.shrink(),
),
),
actions: [
PopupMenuButton(
onSelected: (item) async {
switch (item) {
case _WebviewMenuItem.refresh:
_webViewController?.reload();
break;
case _WebviewMenuItem.copy:
WebUri? uri = await _webViewController?.getUrl();
if (uri != null) {
Utils.copyText(uri.toString());
}
break;
case _WebviewMenuItem.openInBrowser:
WebUri? uri = await _webViewController?.getUrl();
if (uri != null) {
Utils.launchURL(uri.toString());
}
break;
case _WebviewMenuItem.clearCache:
try {
await InAppWebViewController.clearAllCache();
await _webViewController?.clearHistory();
SmartDialog.showToast('已清理');
} catch (e) {
SmartDialog.showToast(e.toString());
}
break;
case _WebviewMenuItem.goBack:
if (await _webViewController?.canGoBack() == true) {
_webViewController?.goBack();
} else {
Get.back();
}
break;
case _WebviewMenuItem.resetCookie:
final cookies = Accounts.main.cookieJar.toList();
for (var item in cookies) {
await CookieManager().setCookie(
url: WebUri(item.domain ?? ''),
name: item.name,
value: item.value,
path: item.path ?? '',
domain: item.domain,
isSecure: item.secure,
isHttpOnly: item.httpOnly,
);
}
SmartDialog.showToast('设置成功,刷新或重新打开网页');
break;
}
},
itemBuilder: (context) => <PopupMenuEntry<_WebviewMenuItem>>[
..._WebviewMenuItem.values
.sublist(0, _WebviewMenuItem.values.length - 1)
.map((item) => PopupMenuItem(
value: item, child: Text(item.title))),
const PopupMenuDivider(),
PopupMenuItem(
value: _WebviewMenuItem.goBack,
child: Text(
_WebviewMenuItem.goBack.title,
style: TextStyle(
color: Theme.of(context).colorScheme.error),
)),
],
)
],
),
body: SafeArea(
child: InAppWebView(
initialSettings: InAppWebViewSettings(
clearCache: true,
javaScriptEnabled: true,
forceDark: ForceDark.AUTO,
useHybridComposition: false,
algorithmicDarkeningAllowed: true,
useShouldOverrideUrlLoading: true,
userAgent: Request.headerUa(type: uaType),
mixedContentMode: MixedContentMode.MIXED_CONTENT_ALWAYS_ALLOW,
),
initialUrlRequest:
URLRequest(url: WebUri.uri(Uri.tryParse(_url) ?? Uri())),
onWebViewCreated: (InAppWebViewController controller) {
_webViewController = controller;
_webViewController?.addJavaScriptHandler(
handlerName: 'finishButtonClicked',
callback: (args) {
_webViewController?.evaluateJavascript(source: """
Array.from(document.querySelectorAll('.ql-editor > p')).map(p => p.textContent).join('\\n');
""").then((value) {
try {
String? summary = (value as String?);
if (summary?.isNotEmpty == true) {
VideoHttp.addNote(
oid: widget.oid!,
title: widget.title!,
summary: summary!,
);
}
} catch (_) {}
Get.back();
});
},
);
},
onProgressChanged: (controller, progress) {
_progressStream.add(progress / 100);
},
onTitleChanged: (controller, title) {
_titleStream.add(title);
},
onCloseWindow: (controller) => Get.back(),
onLoadStop: (controller, url) {
if (url
.toString()
.startsWith('https://www.bilibili.com/h5/note-app')) {
_webViewController?.evaluateJavascript(source: """
document.querySelector('.finish-btn').addEventListener('click', function() {
window.flutter_inappwebview.callHandler('finishButtonClicked');
});
""");
} else if (url.toString().startsWith('https://live.bilibili.com')) {
_webViewController?.evaluateJavascript(
source: '''
document.styleSheets[0].insertRule('div.open-app-btn.bili-btn-warp {display:none;}', 0);
document.styleSheets[0].insertRule('#app__display-area > div.control-panel {display:none;}', 0);
''',
);
}
// _webViewController?.evaluateJavascript(
// source: '''
// document.querySelector('#internationalHeader').remove();
// document.querySelector('#message-navbar').remove();
// ''',
// );
},
onDownloadStartRequest: Platform.isAndroid
? (controller, request) {
showDialog(
context: context,
builder: (context) {
String suggestedFilename =
request.suggestedFilename.toString();
String fileSize = CacheManage.formatSize(
request.contentLength.toDouble());
try {
suggestedFilename =
Uri.decodeComponent(suggestedFilename);
} catch (e) {
debugPrint(e.toString());
}
return AlertDialog(
title: Text(
'下载文件: $suggestedFilename ?',
style: const TextStyle(fontSize: 18),
),
content: SelectableText(request.url.toString()),
actions: [
TextButton(
onPressed: Get.back,
child: const Text('取消'),
),
TextButton(
onPressed: () async {
Get.back();
Utils.launchURL(request.url.toString());
},
child: Text('确定 ($fileSize)')),
],
);
});
_progressStream.add(1);
}
: null,
shouldOverrideUrlLoading: (controller, navigationAction) async {
if (_inApp == true) {
return NavigationActionPolicy.ALLOW;
}
late String url = navigationAction.request.url.toString();
bool hasMatch = await PiliScheme.routePush(
navigationAction.request.url?.uriValue ?? Uri(),
selfHandle: true,
off: _off ?? true,
);
// debugPrint('webview: [$url], [$hasMatch]');
if (hasMatch) {
_progressStream.add(1.0);
return NavigationActionPolicy.CANCEL;
} else if (RegExp(r'^(?!(https?://))\S+://', caseSensitive: false)
.hasMatch(url)) {
if (context.mounted) {
SnackBar snackBar = SnackBar(
content: const Text('当前网页将要打开外部链接,是否打开'),
showCloseIcon: true,
action: SnackBarAction(
label: '打开',
onPressed: () => Utils.launchURL(url),
),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
_progressStream.add(1.0);
return NavigationActionPolicy.CANCEL;
}
return NavigationActionPolicy.ALLOW;
},
),
),
);
}
}