diff --git a/android/app/src/main/res/drawable-hdpi/android12splash.png b/android/app/src/main/res/drawable-hdpi/android12splash.png new file mode 100644 index 0000000..b83ff04 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-hdpi/splash.png b/android/app/src/main/res/drawable-hdpi/splash.png new file mode 100644 index 0000000..f7ad471 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-mdpi/android12splash.png b/android/app/src/main/res/drawable-mdpi/android12splash.png new file mode 100644 index 0000000..fdcfb53 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-mdpi/splash.png b/android/app/src/main/res/drawable-mdpi/splash.png new file mode 100644 index 0000000..b2ebfd8 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-night-hdpi/android12splash.png b/android/app/src/main/res/drawable-night-hdpi/android12splash.png new file mode 100644 index 0000000..b83ff04 Binary files /dev/null and b/android/app/src/main/res/drawable-night-hdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-night-mdpi/android12splash.png b/android/app/src/main/res/drawable-night-mdpi/android12splash.png new file mode 100644 index 0000000..fdcfb53 Binary files /dev/null and b/android/app/src/main/res/drawable-night-mdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-night-xhdpi/android12splash.png b/android/app/src/main/res/drawable-night-xhdpi/android12splash.png new file mode 100644 index 0000000..902356d Binary files /dev/null and b/android/app/src/main/res/drawable-night-xhdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png b/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png new file mode 100644 index 0000000..b111cdb Binary files /dev/null and b/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png b/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png new file mode 100644 index 0000000..54c03f6 Binary files /dev/null and b/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-v21/background.png b/android/app/src/main/res/drawable-v21/background.png new file mode 100644 index 0000000..3f0a992 Binary files /dev/null and b/android/app/src/main/res/drawable-v21/background.png differ diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml index d933a92..3cc4948 100644 --- a/android/app/src/main/res/drawable-v21/launch_background.xml +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -1,4 +1,9 @@ - + + + + + + diff --git a/android/app/src/main/res/drawable-xhdpi/android12splash.png b/android/app/src/main/res/drawable-xhdpi/android12splash.png new file mode 100644 index 0000000..902356d Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/splash.png b/android/app/src/main/res/drawable-xhdpi/splash.png new file mode 100644 index 0000000..b01e86c Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/android12splash.png b/android/app/src/main/res/drawable-xxhdpi/android12splash.png new file mode 100644 index 0000000..b111cdb Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/splash.png b/android/app/src/main/res/drawable-xxhdpi/splash.png new file mode 100644 index 0000000..5143af8 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/android12splash.png b/android/app/src/main/res/drawable-xxxhdpi/android12splash.png new file mode 100644 index 0000000..54c03f6 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/splash.png b/android/app/src/main/res/drawable-xxxhdpi/splash.png new file mode 100644 index 0000000..d255ff4 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable/background.png b/android/app/src/main/res/drawable/background.png new file mode 100644 index 0000000..3f0a992 Binary files /dev/null and b/android/app/src/main/res/drawable/background.png differ diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml index d933a92..3cc4948 100644 --- a/android/app/src/main/res/drawable/launch_background.xml +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -1,4 +1,9 @@ - + + + + + + diff --git a/android/app/src/main/res/values-night-v31/styles.xml b/android/app/src/main/res/values-night-v31/styles.xml index 8041da7..e4f4e80 100644 --- a/android/app/src/main/res/values-night-v31/styles.xml +++ b/android/app/src/main/res/values-night-v31/styles.xml @@ -5,6 +5,9 @@ + diff --git a/android/app/src/main/res/values-v31/styles.xml b/android/app/src/main/res/values-v31/styles.xml index 84e6f89..a7d85a0 100644 --- a/android/app/src/main/res/values-v31/styles.xml +++ b/android/app/src/main/res/values-v31/styles.xml @@ -5,6 +5,9 @@ + diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index 527fd56..c6431cb 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -2,6 +2,6 @@ - #FFF8F0 + #1F1B16 #FFF1E5 diff --git a/assets/icon/splash_logo.png b/assets/icon/splash_logo.png new file mode 100644 index 0000000..d8dbc36 Binary files /dev/null and b/assets/icon/splash_logo.png differ diff --git a/assets/icon/splash_logo_android12.png b/assets/icon/splash_logo_android12.png new file mode 100644 index 0000000..c3a14e3 Binary files /dev/null and b/assets/icon/splash_logo_android12.png differ diff --git a/assets/icon/splash_mark.png b/assets/icon/splash_mark.png deleted file mode 100644 index 121cacd..0000000 Binary files a/assets/icon/splash_mark.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json new file mode 100644 index 0000000..9f447e1 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "background.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png new file mode 100644 index 0000000..3f0a992 Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..00cabce --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "LaunchImage.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "LaunchImage@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "LaunchImage@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..b2ebfd8 Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..b01e86c Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..5143af8 Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard index 4897787..5a37630 100644 --- a/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -9,9 +9,27 @@ + + + + - + + + + + + + + + + + + + + + @@ -19,4 +37,8 @@ + + + + diff --git a/lib/core/theme/app_colors.dart b/lib/core/theme/app_colors.dart index 46df55a..e8961a9 100644 --- a/lib/core/theme/app_colors.dart +++ b/lib/core/theme/app_colors.dart @@ -36,6 +36,9 @@ abstract final class AppColors { /// 页面背景。 static const ink = Color(0xFFFFF8F0); + /// 原生开屏与冷启动过渡底色。 + static const splashInk = Color(0xFF1F1B16); + /// 页面顶部暖色渐变起点。 static const surfaceTop = Color(0xFFFFE9D6); diff --git a/lib/pages/home/home_view.dart b/lib/pages/home/home_view.dart index 66908e1..3be91ba 100644 --- a/lib/pages/home/home_view.dart +++ b/lib/pages/home/home_view.dart @@ -13,6 +13,7 @@ import '../../core/data/services/catalog_controller.dart'; import '../../core/data/services/catalog_retry.dart'; import '../../core/data/services/category_controller.dart'; import '../../core/platform/haptics.dart'; +import '../../core/theme/app_colors.dart'; import '../../core/theme/app_theme.dart'; import '../../core/widgets/app_bottom_nav.dart'; import '../../core/widgets/catalog_debug_info.dart'; @@ -24,7 +25,6 @@ import '../discover/discover_view.dart'; import '../create/create_view.dart'; import '../mine/mine_types.dart'; import '../player/player_view.dart'; -import '../splash/splash_page.dart'; import '../mine/mine_view.dart'; import 'home_binding.dart'; import 'home_logic.dart'; @@ -87,7 +87,6 @@ class _HomePageState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { final catalog = context.watch(); - if (!catalog.settled) return const SplashPage(); final l10n = AppLocalizations.of(context)!; return Theme( data: AppTheme.light(), @@ -101,9 +100,14 @@ class _HomePageState extends State with WidgetsBindingObserver { AppLocalizations l10n, ) { if (catalog.loading) { - return const Scaffold( - backgroundColor: Colors.transparent, - body: Center(child: CircularProgressIndicator()), + return AnnotatedRegion( + value: SystemUiOverlayStyle.light, + child: const Scaffold( + backgroundColor: AppColors.splashInk, + body: Center( + child: CircularProgressIndicator(color: AppColors.blue), + ), + ), ); } if (catalog.error != null) { diff --git a/lib/pages/splash/splash_page.dart b/lib/pages/splash/splash_page.dart deleted file mode 100644 index 9064d41..0000000 --- a/lib/pages/splash/splash_page.dart +++ /dev/null @@ -1,212 +0,0 @@ -// Author: fruit.com.cn - -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - -import '../../core/theme/app_colors.dart'; - -const Color _markGlow = Color(0x33FF7A1A); -const double _markSize = 112; -const double _markRadius = 24; -const double _logoGlowSize = 144; -const double _logoGlowBlur = 40; -const double _loaderSize = 22; -const double _loaderStroke = 2; -const String _markAsset = 'assets/icon/splash_mark.png'; -const Duration _introDuration = Duration(milliseconds: 1200); - -/// 品牌开屏:浅暖底上依次呈现水果标记、FruitShorts 与中文标语。 -class SplashPage extends StatefulWidget { - const SplashPage({super.key}); - - @override - State createState() => _SplashPageState(); -} - -class _SplashPageState extends State - with SingleTickerProviderStateMixin { - late final AnimationController _controller; - late final Animation _logoScale; - late final Animation _logoOpacity; - late final Animation _brandReveal; - late final Animation _taglineReveal; - late final Animation _loadingReveal; - - @override - void initState() { - super.initState(); - _controller = AnimationController(vsync: this, duration: _introDuration) - ..forward(); - _logoScale = Tween(begin: 0.86, end: 1).animate( - CurvedAnimation( - parent: _controller, - curve: const Interval(0, 0.42, curve: Curves.easeOutBack), - ), - ); - _logoOpacity = Tween(begin: 0, end: 1).animate( - CurvedAnimation( - parent: _controller, - curve: const Interval(0, 0.42, curve: Curves.easeOut), - ), - ); - _brandReveal = _reveal(0.125, 0.542); - _taglineReveal = _reveal(0.25, 0.667); - _loadingReveal = _reveal(0.417, 0.833); - } - - Animation _reveal(double begin, double end) { - return Tween(begin: 0, end: 1).animate( - CurvedAnimation( - parent: _controller, - curve: Interval(begin, end, curve: Curves.easeOutCubic), - ), - ); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return AnnotatedRegion( - value: SystemUiOverlayStyle.dark, - child: ColoredBox( - key: const Key('splash-screen'), - color: AppColors.ink, - child: Stack( - fit: StackFit.expand, - children: [ - Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - _logo(), - const SizedBox(height: 24), - _brand(), - const SizedBox(height: 6), - _tagline(), - ], - ), - ), - Align( - alignment: Alignment.bottomCenter, - child: Padding( - padding: const EdgeInsets.only(bottom: 48), - child: FadeTransition( - key: const Key('splash-loading-fade'), - opacity: _loadingReveal, - child: const SizedBox.square( - dimension: _loaderSize, - child: CircularProgressIndicator( - key: Key('splash-loading'), - strokeWidth: _loaderStroke, - color: AppColors.blue, - ), - ), - ), - ), - ), - ], - ), - ), - ); - } - - Widget _logo() { - return FadeTransition( - key: const Key('splash-logo-fade'), - opacity: _logoOpacity, - child: ScaleTransition( - scale: _logoScale, - child: SizedBox.square( - dimension: _logoGlowSize, - child: Stack( - alignment: Alignment.center, - children: [ - ImageFiltered( - key: const Key('splash-logo-glow'), - imageFilter: ImageFilter.blur( - sigmaX: _logoGlowBlur, - sigmaY: _logoGlowBlur, - ), - child: Container( - width: _markSize, - height: _markSize, - decoration: const BoxDecoration( - shape: BoxShape.circle, - gradient: RadialGradient( - colors: [_markGlow, Color(0x00FF7A1A)], - ), - ), - ), - ), - ClipRRect( - borderRadius: BorderRadius.circular(_markRadius), - child: Image.asset( - _markAsset, - key: const Key('splash-logo-mark'), - width: _markSize, - height: _markSize, - filterQuality: FilterQuality.medium, - gaplessPlayback: true, - ), - ), - ], - ), - ), - ), - ); - } - - Widget _brand() { - return FadeTransition( - key: const Key('splash-brand-fade'), - opacity: _brandReveal, - child: AnimatedBuilder( - animation: _brandReveal, - builder: (context, child) => Transform.translate( - offset: Offset(0, 12 * (1 - _brandReveal.value)), - child: child, - ), - child: const Text( - 'FruitShorts', - key: Key('splash-brand'), - style: TextStyle( - color: AppColors.onSurface, - fontSize: 28, - fontWeight: FontWeight.w900, - fontFamily: 'Outfit', - ), - ), - ), - ); - } - - Widget _tagline() { - return FadeTransition( - key: const Key('splash-tagline-fade'), - opacity: _taglineReveal, - child: AnimatedBuilder( - animation: _taglineReveal, - builder: (context, child) => Transform.translate( - offset: Offset(0, 8 * (1 - _taglineReveal.value)), - child: child, - ), - child: const Text( - '水果短剧', - key: Key('splash-tagline'), - style: TextStyle( - color: AppColors.muted, - fontSize: 13, - letterSpacing: 4, - ), - ), - ), - ); - } -} diff --git a/pubspec.yaml b/pubspec.yaml index b64bbfe..902ace3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -80,8 +80,6 @@ flutter: # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true - assets: - - assets/icon/splash_mark.png # To add assets to your application, add an assets section, like this: # assets: @@ -125,12 +123,11 @@ flutter_launcher_icons: # 修改以下开屏配置后,必须重新执行: # dart run flutter_native_splash:create flutter_native_splash: - color: "#FFF8F0" - color_dark: "#FFF8F0" + color: "#1F1B16" + image: assets/icon/splash_logo.png android: true ios: true web: false android_12: - color: "#FFF8F0" - color_dark: "#FFF8F0" - icon_background_color: "#FFF8F0" + color: "#1F1B16" + image: assets/icon/splash_logo_android12.png diff --git a/test/splash/splash_gate_test.dart b/test/splash/splash_gate_test.dart deleted file mode 100644 index f25245a..0000000 --- a/test/splash/splash_gate_test.dart +++ /dev/null @@ -1,114 +0,0 @@ -// Author: fruit.com.cn - -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:fruitshorts/core/data/models/drama.dart'; -import 'package:fruitshorts/core/data/models/home_feed.dart'; -import 'package:fruitshorts/core/data/repositories/catalog_repository.dart'; -import 'package:fruitshorts/core/data/services/catalog_controller.dart'; -import 'package:fruitshorts/core/theme/app_colors.dart'; -import 'package:fruitshorts/l10n/app_localizations.dart'; -import 'package:fruitshorts/pages/home/home_view.dart'; -import 'package:fruitshorts/pages/splash/splash_page.dart'; -import 'package:provider/provider.dart'; - -void main() { - testWidgets('should_keep_splash_with_loading_until_home_feed_settles', ( - tester, - ) async { - final repository = _PendingCatalog(); - await tester.pumpWidget( - MaterialApp( - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - home: ChangeNotifierProvider( - create: (_) => CatalogController(repository)..load(), - child: const HomePage(), - ), - ), - ); - - expect(find.byKey(const Key('splash-screen')), findsOneWidget); - expect(find.byKey(const Key('splash-loading')), findsOneWidget); - expect(find.byKey(const Key('home-empty')), findsNothing); - final clip = tester.widget(find.byType(ClipRRect)); - expect(clip.borderRadius, BorderRadius.circular(24)); - - repository.pending.complete(const HomeFeed()); - await tester.pump(); - await tester.pump(); - - expect(find.byKey(const Key('splash-screen')), findsNothing); - expect(find.byKey(const Key('home-empty')), findsOneWidget); - }); - - testWidgets('should_animate_brand_copy_and_loading_to_full_opacity', ( - tester, - ) async { - await tester.pumpWidget(const MaterialApp(home: SplashPage())); - - expect(find.byKey(const Key('splash-screen')), findsOneWidget); - expect(find.byKey(const Key('splash-brand')), findsOneWidget); - expect(find.byKey(const Key('splash-tagline')), findsOneWidget); - expect(find.byKey(const Key('splash-loading')), findsOneWidget); - final mark = tester.widget( - find.byKey(const Key('splash-logo-mark')), - ); - expect((mark.image as AssetImage).assetName, 'assets/icon/splash_mark.png'); - expect(mark.width, 112); - expect(mark.height, 112); - final glow = tester.widget( - find.descendant( - of: find.byKey(const Key('splash-logo-glow')), - matching: find.byType(Container), - ), - ); - final glowGradient = (glow.decoration as BoxDecoration).gradient; - expect( - (glowGradient as RadialGradient).colors.first, - const Color(0x33FF7A1A), - ); - expect( - tester.widget(find.byKey(const Key('splash-screen'))).color, - AppColors.ink, - ); - expect( - tester.widget(find.byKey(const Key('splash-brand'))).style?.color, - AppColors.onSurface, - ); - expect( - tester.widget(find.byKey(const Key('splash-tagline'))).style?.color, - AppColors.muted, - ); - final overlay = tester.widget>( - find.byType(AnnotatedRegion).first, - ); - expect(overlay.value.statusBarIconBrightness, Brightness.dark); - - await tester.pump(const Duration(milliseconds: 1200)); - - double opacity(String key) => - tester.widget(find.byKey(Key(key))).opacity.value; - expect(opacity('splash-logo-fade'), 1); - expect(opacity('splash-brand-fade'), 1); - expect(opacity('splash-tagline-fade'), 1); - expect(opacity('splash-loading-fade'), 1); - }); -} - -class _PendingCatalog implements CatalogRepository { - final Completer pending = Completer(); - - @override - Future fetchHomeFeed() => pending.future; - - @override - Future fetchDiscoverFeed() => pending.future; - - @override - Future fetchDrama(String dramaId) => - Future.error(StateError('unused')); -}