feat: 更新启动背景和主题样式,优化加载界面
This commit is contained in:
174
lib/main.dart
174
lib/main.dart
@@ -11,6 +11,10 @@ import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
|
||||
import 'config/app_config.dart';
|
||||
|
||||
final _homeUrl = AppConfig.homeUrl;
|
||||
const _shellBackground = Color(0xFFF8FBFF);
|
||||
const _shellAccent = Color(0xFF0089FF);
|
||||
const _shellSubText = Color(0xFF8E9AB0);
|
||||
const _resumeCoverDuration = Duration(milliseconds: 700);
|
||||
const _stopWebMediaScript = r'''
|
||||
(() => {
|
||||
try {
|
||||
@@ -31,7 +35,7 @@ void main() {
|
||||
const SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: Brightness.dark,
|
||||
systemNavigationBarColor: Colors.white,
|
||||
systemNavigationBarColor: _shellBackground,
|
||||
systemNavigationBarIconBrightness: Brightness.dark,
|
||||
),
|
||||
);
|
||||
@@ -48,7 +52,7 @@ class ImWebViewApp extends StatelessWidget {
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF1F6FEB)),
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
scaffoldBackgroundColor: _shellBackground,
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: const H5ShellPage(),
|
||||
@@ -68,6 +72,8 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
||||
|
||||
int _progress = 0;
|
||||
String? _loadError;
|
||||
bool _showShellCover = true;
|
||||
Timer? _shellCoverTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -78,6 +84,7 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_shellCoverTimer?.cancel();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
@@ -89,6 +96,8 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
||||
state == AppLifecycleState.paused ||
|
||||
state == AppLifecycleState.detached) {
|
||||
unawaited(_stopWebMedia());
|
||||
} else if (state == AppLifecycleState.resumed) {
|
||||
_showTransientShellCover();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +122,7 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
||||
},
|
||||
)
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..setBackgroundColor(Colors.white)
|
||||
..setBackgroundColor(_shellBackground)
|
||||
..setNavigationDelegate(
|
||||
NavigationDelegate(
|
||||
onProgress: (progress) {
|
||||
@@ -126,18 +135,25 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
||||
setState(() {
|
||||
_loadError = null;
|
||||
_progress = 0;
|
||||
_showShellCover = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
onPageFinished: (_) {
|
||||
if (mounted) {
|
||||
setState(() => _progress = 100);
|
||||
setState(() {
|
||||
_progress = 100;
|
||||
_showShellCover = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
onWebResourceError: (error) {
|
||||
if (error.isForMainFrame ?? true) {
|
||||
if (mounted) {
|
||||
setState(() => _loadError = error.description);
|
||||
setState(() {
|
||||
_loadError = error.description;
|
||||
_showShellCover = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -170,6 +186,18 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
||||
return controller;
|
||||
}
|
||||
|
||||
void _showTransientShellCover() {
|
||||
_shellCoverTimer?.cancel();
|
||||
if (mounted) {
|
||||
setState(() => _showShellCover = true);
|
||||
}
|
||||
_shellCoverTimer = Timer(_resumeCoverDuration, () {
|
||||
if (mounted) {
|
||||
setState(() => _showShellCover = _progress < 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _runJavaScriptSafely(String source) async {
|
||||
try {
|
||||
await _controller.runJavaScript(source);
|
||||
@@ -252,11 +280,17 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: _shellBackground,
|
||||
body: SafeArea(
|
||||
bottom: false,
|
||||
child: Stack(
|
||||
children: [
|
||||
const Positioned.fill(child: _ShellFallback()),
|
||||
WebViewWidget(controller: _controller),
|
||||
if (_showShellCover)
|
||||
const Positioned.fill(
|
||||
child: IgnorePointer(child: _ShellFallback()),
|
||||
),
|
||||
if (_progress < 100)
|
||||
LinearProgressIndicator(
|
||||
value: _progress == 0 ? null : _progress / 100,
|
||||
@@ -315,3 +349,133 @@ class _ErrorPanel extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ShellFallback extends StatelessWidget {
|
||||
const _ShellFallback();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Color(0xFFE7F4FF),
|
||||
_shellBackground,
|
||||
Colors.white,
|
||||
],
|
||||
stops: [0, 0.44, 1],
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
const Positioned(
|
||||
top: 44,
|
||||
right: -72,
|
||||
child: _SoftCircle(size: 190, opacity: 0.42),
|
||||
),
|
||||
const Positioned(
|
||||
top: 210,
|
||||
left: -44,
|
||||
child: _SoftCircle(size: 160, opacity: 0.34),
|
||||
),
|
||||
const Positioned(
|
||||
right: -54,
|
||||
bottom: 250,
|
||||
child: _SoftCircle(size: 220, opacity: 0.38),
|
||||
),
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
_ShellMark(),
|
||||
SizedBox(height: 26),
|
||||
Text(
|
||||
'心有回响',
|
||||
style: TextStyle(
|
||||
color: _shellAccent,
|
||||
fontSize: 26,
|
||||
height: 1.2,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Text(
|
||||
'正在为你唤醒会话',
|
||||
style: TextStyle(
|
||||
color: _shellSubText,
|
||||
fontSize: 13,
|
||||
height: 1.2,
|
||||
letterSpacing: 0,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 32),
|
||||
SizedBox(
|
||||
width: 110,
|
||||
child: LinearProgressIndicator(
|
||||
minHeight: 3,
|
||||
backgroundColor: Color(0xFFDCEEFF),
|
||||
color: _shellAccent,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ShellMark extends StatelessWidget {
|
||||
const _ShellMark();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 126,
|
||||
height: 126,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFFA8DDFF), _shellAccent, Color(0xFF0066E8)],
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: _shellAccent.withValues(alpha: 0.2),
|
||||
blurRadius: 34,
|
||||
offset: const Offset(0, 18),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.check_rounded,
|
||||
color: Colors.white,
|
||||
size: 82,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SoftCircle extends StatelessWidget {
|
||||
const _SoftCircle({required this.size, required this.opacity});
|
||||
|
||||
final double size;
|
||||
final double opacity;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: size,
|
||||
height: size,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: const Color(0xFFBFE4FF).withValues(alpha: opacity),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user