feat: 更新启动背景和主题样式,优化加载界面
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:drawable="?android:colorBackground" />
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
<!-- You can insert your own image assets here -->
|
<gradient
|
||||||
<!-- <item>
|
android:angle="270"
|
||||||
<bitmap
|
android:startColor="#E7F4FF"
|
||||||
android:gravity="center"
|
android:centerColor="#F8FBFF"
|
||||||
android:src="@mipmap/launch_image" />
|
android:endColor="#FFFFFF" />
|
||||||
</item> -->
|
</shape>
|
||||||
|
</item>
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:drawable="@android:color/white" />
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
<!-- You can insert your own image assets here -->
|
<gradient
|
||||||
<!-- <item>
|
android:angle="270"
|
||||||
<bitmap
|
android:startColor="#E7F4FF"
|
||||||
android:gravity="center"
|
android:centerColor="#F8FBFF"
|
||||||
android:src="@mipmap/launch_image" />
|
android:endColor="#FFFFFF" />
|
||||||
</item> -->
|
</shape>
|
||||||
|
</item>
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
|||||||
@@ -13,6 +13,6 @@
|
|||||||
|
|
||||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
<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>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -13,6 +13,6 @@
|
|||||||
|
|
||||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
<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>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</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>
|
<constraints>
|
||||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
<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"/>
|
<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';
|
import 'config/app_config.dart';
|
||||||
|
|
||||||
final _homeUrl = AppConfig.homeUrl;
|
final _homeUrl = AppConfig.homeUrl;
|
||||||
|
const _shellBackground = Color(0xFFF8FBFF);
|
||||||
|
const _shellAccent = Color(0xFF0089FF);
|
||||||
|
const _shellSubText = Color(0xFF8E9AB0);
|
||||||
|
const _resumeCoverDuration = Duration(milliseconds: 700);
|
||||||
const _stopWebMediaScript = r'''
|
const _stopWebMediaScript = r'''
|
||||||
(() => {
|
(() => {
|
||||||
try {
|
try {
|
||||||
@@ -31,7 +35,7 @@ void main() {
|
|||||||
const SystemUiOverlayStyle(
|
const SystemUiOverlayStyle(
|
||||||
statusBarColor: Colors.transparent,
|
statusBarColor: Colors.transparent,
|
||||||
statusBarIconBrightness: Brightness.dark,
|
statusBarIconBrightness: Brightness.dark,
|
||||||
systemNavigationBarColor: Colors.white,
|
systemNavigationBarColor: _shellBackground,
|
||||||
systemNavigationBarIconBrightness: Brightness.dark,
|
systemNavigationBarIconBrightness: Brightness.dark,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -48,7 +52,7 @@ class ImWebViewApp extends StatelessWidget {
|
|||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF1F6FEB)),
|
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF1F6FEB)),
|
||||||
scaffoldBackgroundColor: Colors.white,
|
scaffoldBackgroundColor: _shellBackground,
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
home: const H5ShellPage(),
|
home: const H5ShellPage(),
|
||||||
@@ -68,6 +72,8 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
|||||||
|
|
||||||
int _progress = 0;
|
int _progress = 0;
|
||||||
String? _loadError;
|
String? _loadError;
|
||||||
|
bool _showShellCover = true;
|
||||||
|
Timer? _shellCoverTimer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -78,6 +84,7 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_shellCoverTimer?.cancel();
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -89,6 +96,8 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
|||||||
state == AppLifecycleState.paused ||
|
state == AppLifecycleState.paused ||
|
||||||
state == AppLifecycleState.detached) {
|
state == AppLifecycleState.detached) {
|
||||||
unawaited(_stopWebMedia());
|
unawaited(_stopWebMedia());
|
||||||
|
} else if (state == AppLifecycleState.resumed) {
|
||||||
|
_showTransientShellCover();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +122,7 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||||
..setBackgroundColor(Colors.white)
|
..setBackgroundColor(_shellBackground)
|
||||||
..setNavigationDelegate(
|
..setNavigationDelegate(
|
||||||
NavigationDelegate(
|
NavigationDelegate(
|
||||||
onProgress: (progress) {
|
onProgress: (progress) {
|
||||||
@@ -126,18 +135,25 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_loadError = null;
|
_loadError = null;
|
||||||
_progress = 0;
|
_progress = 0;
|
||||||
|
_showShellCover = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPageFinished: (_) {
|
onPageFinished: (_) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _progress = 100);
|
setState(() {
|
||||||
|
_progress = 100;
|
||||||
|
_showShellCover = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onWebResourceError: (error) {
|
onWebResourceError: (error) {
|
||||||
if (error.isForMainFrame ?? true) {
|
if (error.isForMainFrame ?? true) {
|
||||||
if (mounted) {
|
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;
|
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 {
|
Future<void> _runJavaScriptSafely(String source) async {
|
||||||
try {
|
try {
|
||||||
await _controller.runJavaScript(source);
|
await _controller.runJavaScript(source);
|
||||||
@@ -252,11 +280,17 @@ class _H5ShellPageState extends State<H5ShellPage> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
|
backgroundColor: _shellBackground,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
bottom: false,
|
bottom: false,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
|
const Positioned.fill(child: _ShellFallback()),
|
||||||
WebViewWidget(controller: _controller),
|
WebViewWidget(controller: _controller),
|
||||||
|
if (_showShellCover)
|
||||||
|
const Positioned.fill(
|
||||||
|
child: IgnorePointer(child: _ShellFallback()),
|
||||||
|
),
|
||||||
if (_progress < 100)
|
if (_progress < 100)
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
value: _progress == 0 ? null : _progress / 100,
|
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