feat: 更新启动背景和主题样式,优化加载界面
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:startColor="#E7F4FF"
|
||||
android:centerColor="#F8FBFF"
|
||||
android:endColor="#FFFFFF" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:startColor="#E7F4FF"
|
||||
android:centerColor="#F8FBFF"
|
||||
android:endColor="#FFFFFF" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="backgroundColor" red="0.97254901960784312" green="0.98431372549019602" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
|
||||
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