310 lines
7.8 KiB
Dart
310 lines
7.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import 'package:loading_animation_widget/loading_animation_widget.dart';
|
|
import 'package:skywood/global/const/common_color.dart';
|
|
import 'package:skywood/utils/tools/widgets/sky_app_bar.dart';
|
|
import 'package:webview_flutter/webview_flutter.dart';
|
|
|
|
import '../../../utils/tools/help_img.dart';
|
|
|
|
/// WebView 页面参数
|
|
class WebLinkParams {
|
|
final String title;
|
|
final String urlString;
|
|
final bool showAppbar;
|
|
|
|
const WebLinkParams({
|
|
this.title = 'Web View',
|
|
required this.urlString,
|
|
this.showAppbar = true,
|
|
});
|
|
}
|
|
|
|
/// WebView 页面
|
|
class SkyWebPage extends StatefulWidget {
|
|
final WebLinkParams params;
|
|
|
|
const SkyWebPage({
|
|
super.key,
|
|
required this.params,
|
|
});
|
|
|
|
@override
|
|
State<SkyWebPage> createState() => _WebLinkViewState();
|
|
}
|
|
|
|
class _WebLinkViewState extends State<SkyWebPage> {
|
|
late WebViewController _webViewController;
|
|
int _loadingProgress = 0;
|
|
bool _isLoading = true;
|
|
bool _hasError = false;
|
|
String _errorMessage = '';
|
|
String _urlString = '';
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_initializeParameters();
|
|
_initializeWebView();
|
|
_loadUrl();
|
|
}
|
|
|
|
void _initializeParameters() {
|
|
_urlString = _validateAndFixUrl(widget.params.urlString);
|
|
}
|
|
|
|
void _initializeWebView() {
|
|
_webViewController = WebViewController()
|
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
|
..setBackgroundColor(const Color(0x00FF0000))
|
|
..setNavigationDelegate(
|
|
NavigationDelegate(
|
|
onProgress: (int progress) {
|
|
setState(() {
|
|
_loadingProgress = progress;
|
|
});
|
|
},
|
|
onPageStarted: (String url) {
|
|
setState(() {
|
|
_isLoading = true;
|
|
_hasError = false;
|
|
_loadingProgress = 0;
|
|
});
|
|
},
|
|
onPageFinished: (String url) async {
|
|
setState(() {
|
|
_isLoading = false;
|
|
_loadingProgress = 100;
|
|
});
|
|
|
|
// 修改网页背景为透明,文字为白色
|
|
await _webViewController.runJavaScript('''
|
|
document.body.style.background = 'none';
|
|
document.body.style.color = '#fff';
|
|
|
|
// 修改标题样式
|
|
var titleElements = document.getElementsByClassName('title');
|
|
for (var i = 0; i < titleElements.length; i++) {
|
|
titleElements[i].style.display = 'none';
|
|
}
|
|
|
|
// 修改文本背景为白色
|
|
var bgElements = document.getElementsByClassName('bg-fff');
|
|
for (var i = 0; i < bgElements.length; i++) {
|
|
bgElements[i].style.background = 'none';
|
|
bgElements[i].style.paddingTop = '0px';
|
|
}
|
|
|
|
// 修改所有文本元素为白色
|
|
var allElements = document.getElementsByTagName('*');
|
|
for (var i = 0; i < allElements.length; i++) {
|
|
allElements[i].style.color = '#fff';
|
|
}
|
|
|
|
// 修改链接颜色
|
|
var links = document.getElementsByTagName('a');
|
|
for (var i = 0; i < links.length; i++) {
|
|
links[i].style.color = '#87CEEB';
|
|
}
|
|
''');
|
|
},
|
|
onHttpError: (HttpResponseError error) {
|
|
setState(() {
|
|
_hasError = true;
|
|
_errorMessage = 'HTTP Error: ${error.response?.statusCode ?? "Unknown"}';
|
|
_isLoading = false;
|
|
});
|
|
},
|
|
onWebResourceError: (WebResourceError error) {
|
|
setState(() {
|
|
_hasError = true;
|
|
_errorMessage = error.description;
|
|
_isLoading = false;
|
|
});
|
|
},
|
|
onNavigationRequest: (NavigationRequest request) {
|
|
return NavigationDecision.navigate;
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
void _loadUrl() {
|
|
if (_urlString.isEmpty) {
|
|
setState(() {
|
|
_hasError = true;
|
|
_errorMessage = 'Invalid URL';
|
|
_isLoading = false;
|
|
});
|
|
return;
|
|
}
|
|
|
|
try {
|
|
final uri = Uri.parse(_urlString);
|
|
_webViewController.loadRequest(uri);
|
|
} catch (e) {
|
|
setState(() {
|
|
_hasError = true;
|
|
_errorMessage = 'Failed to load URL: $e';
|
|
_isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
String _validateAndFixUrl(String url) {
|
|
if (url.isEmpty) {
|
|
return '';
|
|
}
|
|
|
|
url = url.trim();
|
|
|
|
// Already has a valid scheme
|
|
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
return url;
|
|
}
|
|
|
|
// Looks like a domain (has a dot and no spaces)
|
|
if (url.contains('.') && !url.contains(' ')) {
|
|
return 'https://$url';
|
|
}
|
|
|
|
// Treat as search query
|
|
return 'https://www.google.com/search?q=${Uri.encodeComponent(url)}';
|
|
}
|
|
|
|
void _reload() {
|
|
setState(() {
|
|
_hasError = false;
|
|
_isLoading = true;
|
|
_loadingProgress = 0;
|
|
_errorMessage = '';
|
|
});
|
|
_initializeWebView();
|
|
_loadUrl();
|
|
}
|
|
|
|
void _goBack() {
|
|
_webViewController.goBack();
|
|
}
|
|
|
|
void _goForward() {
|
|
_webViewController.goForward();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_webViewController.clearCache();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
extendBodyBehindAppBar: true,
|
|
appBar: widget.params.showAppbar
|
|
? _buildAppBar()
|
|
: null,
|
|
body: _buildBody(),
|
|
);
|
|
}
|
|
|
|
PreferredSizeWidget _buildAppBar() {
|
|
return SkyAppBar(
|
|
backgroundColor: Colors.transparent,
|
|
title: widget.params.title,
|
|
titleColor: Colors.white,
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(Icons.refresh, color: Colors.white,),
|
|
onPressed: _reload,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildBody() {
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
colors: [
|
|
CommonColor.primacyColor,
|
|
Color(0xFF2D1B4E),
|
|
],
|
|
),
|
|
),
|
|
child: SafeArea(
|
|
child: Column(
|
|
children: [
|
|
// 加载进度条(可选)
|
|
if (_isLoading)
|
|
LinearProgressIndicator(
|
|
value: _loadingProgress / 100,
|
|
backgroundColor: Colors.white.withOpacity(0.2),
|
|
valueColor: const AlwaysStoppedAnimation<Color>(
|
|
Color(0xFFFB98E0),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: _buildContent(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildContent() {
|
|
if (_isLoading) {
|
|
return Center(
|
|
child: SizedBox(
|
|
width: 120,
|
|
height: 120,
|
|
child: Image.asset(ConstImg.loading, fit: BoxFit.fill),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (_hasError) {
|
|
return _buildErrorWidget();
|
|
}
|
|
|
|
return WebViewWidget(
|
|
controller: _webViewController,
|
|
);
|
|
}
|
|
|
|
Widget _buildErrorWidget() {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.error_outline,
|
|
size: 64.w,
|
|
color: Colors.white.withOpacity(0.6),
|
|
),
|
|
SizedBox(height: 16.h),
|
|
Text(
|
|
_errorMessage,
|
|
style: TextStyle(
|
|
color: Colors.white.withOpacity(0.8),
|
|
fontSize: 16.sp,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
SizedBox(height: 24.h),
|
|
ElevatedButton(
|
|
onPressed: _reload,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFFFB98E0),
|
|
foregroundColor: Colors.white,
|
|
),
|
|
child: const Text('重试'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} |