feat: 调整品牌开屏与纯黑原生启动
No files matched your search
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 46 KiB |
@@ -3,7 +3,4 @@
|
|||||||
<item>
|
<item>
|
||||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
|
||||||
</item>
|
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 52 KiB |
@@ -3,7 +3,4 @@
|
|||||||
<item>
|
<item>
|
||||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
|
||||||
</item>
|
|
||||||
</layer-list>
|
</layer-list>
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
<item name="android:windowSplashScreenBackground">#000000</item>
|
<item name="android:windowSplashScreenBackground">#000000</item>
|
||||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
|
||||||
</style>
|
</style>
|
||||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
<item name="android:windowBackground">@color/splash_background</item>
|
<item name="android:windowBackground">@color/splash_background</item>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
<item name="android:windowSplashScreenBackground">#000000</item>
|
<item name="android:windowSplashScreenBackground">#000000</item>
|
||||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
|
||||||
</style>
|
</style>
|
||||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
<item name="android:windowBackground">@color/splash_background</item>
|
<item name="android:windowBackground">@color/splash_background</item>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 69 B |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 69 B |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 69 B |
@@ -38,7 +38,7 @@
|
|||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="LaunchImage" width="1400" height="1400"/>
|
<image name="LaunchImage" width="168" height="185"/>
|
||||||
<image name="LaunchBackground" width="1" height="1"/>
|
<image name="LaunchBackground" width="1" height="1"/>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
@@ -30,6 +30,9 @@ import 'home_logic.dart';
|
|||||||
import 'widgets/home_feed_grid.dart';
|
import 'widgets/home_feed_grid.dart';
|
||||||
import 'widgets/home_grid_header.dart';
|
import 'widgets/home_grid_header.dart';
|
||||||
|
|
||||||
|
const Duration _startupSplashMinimumDuration = Duration(milliseconds: 400);
|
||||||
|
const Duration _startupSplashFadeDuration = Duration(milliseconds: 200);
|
||||||
|
|
||||||
class HomePage extends StatefulWidget {
|
class HomePage extends StatefulWidget {
|
||||||
const HomePage({super.key});
|
const HomePage({super.key});
|
||||||
|
|
||||||
@@ -41,15 +44,31 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
bool _resumeRetryInFlight = false;
|
bool _resumeRetryInFlight = false;
|
||||||
HomeFeedController? _homeFeed;
|
HomeFeedController? _homeFeed;
|
||||||
bool _recommendSeeded = false;
|
bool _recommendSeeded = false;
|
||||||
|
Timer? _startupSplashMinimumTimer;
|
||||||
|
bool _catalogSettled = false;
|
||||||
|
bool _startupSplashVisible = true;
|
||||||
|
bool _startupSplashFadingOut = false;
|
||||||
|
bool _startupSplashMinimumElapsed = false;
|
||||||
|
bool _startupSplashDismissing = false;
|
||||||
|
bool _startupSplashSettledTriggered = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
_startupSplashMinimumTimer = Timer(_startupSplashMinimumDuration, () {
|
||||||
|
if (!mounted) return;
|
||||||
|
_startupSplashMinimumElapsed = true;
|
||||||
|
unawaited(_dismissStartupSplashIfReady());
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_startupSplashMinimumTimer?.cancel();
|
||||||
_homeFeed?.dispose();
|
_homeFeed?.dispose();
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@@ -83,7 +102,41 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final catalog = context.watch<CatalogController>();
|
final catalog = context.watch<CatalogController>();
|
||||||
return Theme(data: AppTheme.dark(), child: _buildHome(context, catalog));
|
_catalogSettled = catalog.settled;
|
||||||
|
if (_startupSplashVisible &&
|
||||||
|
_catalogSettled &&
|
||||||
|
!_startupSplashSettledTriggered) {
|
||||||
|
_startupSplashSettledTriggered = true;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (mounted) unawaited(_dismissStartupSplashIfReady());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Theme(
|
||||||
|
data: AppTheme.dark(),
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
_buildHome(context, catalog),
|
||||||
|
if (_startupSplashVisible)
|
||||||
|
_StartupSplashOverlay(fadingOut: _startupSplashFadingOut),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _dismissStartupSplashIfReady() async {
|
||||||
|
if (!mounted ||
|
||||||
|
!_startupSplashVisible ||
|
||||||
|
_startupSplashDismissing ||
|
||||||
|
!_startupSplashMinimumElapsed ||
|
||||||
|
!_catalogSettled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_startupSplashDismissing = true;
|
||||||
|
setState(() => _startupSplashFadingOut = true);
|
||||||
|
await Future<void>.delayed(_startupSplashFadeDuration);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _startupSplashVisible = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildHome(BuildContext context, CatalogController catalog) {
|
Widget _buildHome(BuildContext context, CatalogController catalog) {
|
||||||
@@ -109,6 +162,35 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _StartupSplashOverlay extends StatelessWidget {
|
||||||
|
const _StartupSplashOverlay({required this.fadingOut});
|
||||||
|
|
||||||
|
final bool fadingOut;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IgnorePointer(
|
||||||
|
child: AnimatedOpacity(
|
||||||
|
key: const Key('startup-splash'),
|
||||||
|
opacity: fadingOut ? 0 : 1,
|
||||||
|
duration: _startupSplashFadeDuration,
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
child: ColoredBox(
|
||||||
|
color: AppColors.appBackground,
|
||||||
|
child: Center(
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/icon/splash_logo.png',
|
||||||
|
key: const Key('startup-splash-logo'),
|
||||||
|
width: 240,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 四个根页面共享同一路由并保持各自状态。
|
/// 四个根页面共享同一路由并保持各自状态。
|
||||||
class _HomeTabs extends StatefulWidget {
|
class _HomeTabs extends StatefulWidget {
|
||||||
const _HomeTabs({super.key, required this.feedController});
|
const _HomeTabs({super.key, required this.feedController});
|
||||||
|
|||||||
@@ -129,10 +129,8 @@ flutter_launcher_icons:
|
|||||||
# dart run flutter_native_splash:create
|
# dart run flutter_native_splash:create
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
color: "#000000"
|
color: "#000000"
|
||||||
image: assets/icon/splash_logo.png
|
|
||||||
android: true
|
android: true
|
||||||
ios: true
|
ios: true
|
||||||
web: false
|
web: false
|
||||||
android_12:
|
android_12:
|
||||||
color: "#000000"
|
color: "#000000"
|
||||||
image: assets/icon/splash_logo_android12.png
|
|
||||||
@@ -36,7 +36,7 @@ void main() {
|
|||||||
AppConfig.ready = Future<void>.value();
|
AppConfig.ready = Future<void>.value();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('should_show_home_shell_and_grid_skeleton_while_catalog_loads', (
|
testWidgets('should_show_brand_splash_and_grid_skeleton_while_loading', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
final repository = _PendingCatalogRepository();
|
final repository = _PendingCatalogRepository();
|
||||||
@@ -47,12 +47,14 @@ void main() {
|
|||||||
expect(find.byKey(const Key('home-grid-header')), findsOneWidget);
|
expect(find.byKey(const Key('home-grid-header')), findsOneWidget);
|
||||||
expect(find.byKey(const Key('home-grid-skeleton')), findsOneWidget);
|
expect(find.byKey(const Key('home-grid-skeleton')), findsOneWidget);
|
||||||
expect(find.byKey(const Key('home-tabs-bar')), findsOneWidget);
|
expect(find.byKey(const Key('home-tabs-bar')), findsOneWidget);
|
||||||
expect(find.byKey(const Key('startup-splash')), findsNothing);
|
expect(find.byKey(const Key('startup-splash')), findsOneWidget);
|
||||||
|
expect(find.byKey(const Key('startup-splash-logo')), findsOneWidget);
|
||||||
expect(find.byType(CircularProgressIndicator), findsNothing);
|
expect(find.byType(CircularProgressIndicator), findsNothing);
|
||||||
|
|
||||||
repository.complete(_feed);
|
repository.complete(_feed);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.byKey(const Key('startup-splash')), findsNothing);
|
||||||
expect(find.byKey(const Key('home-grid-skeleton')), findsNothing);
|
expect(find.byKey(const Key('home-grid-skeleton')), findsNothing);
|
||||||
expect(find.byKey(const Key('home-grid')), findsOneWidget);
|
expect(find.byKey(const Key('home-grid')), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|||||||