155 lines
4.1 KiB
Dart
155 lines
4.1 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:google_fonts/google_fonts.dart';
|
||
import 'package:webview_flutter/webview_flutter.dart';
|
||
|
||
import '../../design/pencil_theme.dart';
|
||
|
||
/// 通用应用内 Web 页(任意 HTTPS/HTTP 链接),供用户协议、隐私政策及其他 H5 复用。
|
||
class AppWebViewScreen extends StatefulWidget {
|
||
const AppWebViewScreen({
|
||
super.key,
|
||
required this.title,
|
||
required this.initialUrl,
|
||
});
|
||
|
||
final String title;
|
||
|
||
/// 完整 URL(含 `http`/`https`)。
|
||
final String initialUrl;
|
||
|
||
@override
|
||
State<AppWebViewScreen> createState() => _AppWebViewScreenState();
|
||
}
|
||
|
||
class _AppWebViewScreenState extends State<AppWebViewScreen> {
|
||
WebViewController? _controller;
|
||
bool _loading = true;
|
||
String? _loadError;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
final uri = _parseHttpUrl(widget.initialUrl);
|
||
if (uri == null) {
|
||
_loadError = 'Invalid link';
|
||
_loading = false;
|
||
return;
|
||
}
|
||
_controller = WebViewController()
|
||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||
..setNavigationDelegate(
|
||
NavigationDelegate(
|
||
onPageStarted: (_) {
|
||
if (mounted) setState(() => _loading = true);
|
||
},
|
||
onPageFinished: (_) {
|
||
if (mounted) setState(() => _loading = false);
|
||
},
|
||
onWebResourceError: (WebResourceError e) {
|
||
if (mounted) {
|
||
setState(() {
|
||
_loading = false;
|
||
_loadError = e.description.isNotEmpty
|
||
? e.description
|
||
: 'Failed to load page';
|
||
});
|
||
}
|
||
},
|
||
),
|
||
)
|
||
..loadRequest(uri);
|
||
}
|
||
|
||
static Uri? _parseHttpUrl(String raw) {
|
||
final u = Uri.tryParse(raw.trim());
|
||
if (u == null || !u.hasScheme) return null;
|
||
if (u.scheme != 'http' && u.scheme != 'https') return null;
|
||
return u;
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
backgroundColor: Colors.white,
|
||
appBar: AppBar(
|
||
backgroundColor: Colors.white,
|
||
surfaceTintColor: Colors.transparent,
|
||
elevation: 0,
|
||
foregroundColor: PencilTheme.stone900,
|
||
title: Text(
|
||
widget.title,
|
||
style: GoogleFonts.inter(
|
||
fontSize: 17,
|
||
fontWeight: FontWeight.w600,
|
||
color: PencilTheme.stone900,
|
||
),
|
||
),
|
||
leading: IconButton(
|
||
icon: const Icon(Icons.arrow_back_ios_new_rounded, size: 20),
|
||
onPressed: () => Navigator.of(context).pop(),
|
||
),
|
||
),
|
||
body: _buildBody(),
|
||
);
|
||
}
|
||
|
||
Widget _buildBody() {
|
||
if (_loadError != null && _controller == null) {
|
||
return Center(
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(24),
|
||
child: Text(
|
||
_loadError!,
|
||
textAlign: TextAlign.center,
|
||
style: GoogleFonts.inter(
|
||
fontSize: 15,
|
||
color: PencilTheme.stone600,
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
final c = _controller;
|
||
if (c == null) {
|
||
return const SizedBox.shrink();
|
||
}
|
||
return Stack(
|
||
fit: StackFit.expand,
|
||
children: [
|
||
WebViewWidget(controller: c),
|
||
if (_loading)
|
||
const ColoredBox(
|
||
color: Color(0x0F000000),
|
||
child: Center(
|
||
child: CircularProgressIndicator(
|
||
color: PencilTheme.underlineGold,
|
||
),
|
||
),
|
||
),
|
||
if (_loadError != null && _controller != null)
|
||
Positioned(
|
||
left: 0,
|
||
right: 0,
|
||
bottom: 0,
|
||
child: Material(
|
||
color: const Color(0xFFFFF7ED),
|
||
child: SafeArea(
|
||
top: false,
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(12),
|
||
child: Text(
|
||
_loadError!,
|
||
style: GoogleFonts.inter(
|
||
fontSize: 13,
|
||
color: const Color(0xFF9A3412),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
}
|