feat(home): 启动页停留至首页就绪
This commit is contained in:
1 parent
c76b388814
commit
7871b0cfbb
3 files changed
+221
-9
No files matched your search
@@ -37,6 +37,11 @@ class HomePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
static const _androidSplashAsset = 'assets/icon/splash_logo_android12.png';
|
||||
static const _defaultSplashAsset = 'assets/icon/splash_logo.png';
|
||||
static const _androidSplashSize = 288.0;
|
||||
static const _defaultSplashSize = 384.0;
|
||||
|
||||
bool _resumeRetryInFlight = false;
|
||||
|
||||
@override
|
||||
@@ -100,15 +105,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
AppLocalizations l10n,
|
||||
) {
|
||||
if (catalog.loading) {
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle.light,
|
||||
child: const Scaffold(
|
||||
backgroundColor: AppColors.splashInk,
|
||||
body: Center(
|
||||
child: CircularProgressIndicator(color: AppColors.blue),
|
||||
),
|
||||
),
|
||||
);
|
||||
return catalog.settled ? _retryLoading() : _startupSplash();
|
||||
}
|
||||
if (catalog.error != null) {
|
||||
return Scaffold(
|
||||
@@ -140,6 +137,36 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
onRefreshFeed: catalog.refresh,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _startupSplash() {
|
||||
final isAndroid = defaultTargetPlatform == TargetPlatform.android;
|
||||
final size = isAndroid ? _androidSplashSize : _defaultSplashSize;
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle.light,
|
||||
child: ColoredBox(
|
||||
color: AppColors.splashInk,
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
isAndroid ? _androidSplashAsset : _defaultSplashAsset,
|
||||
key: const Key('startup-splash'),
|
||||
width: size,
|
||||
height: size,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _retryLoading() {
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle.light,
|
||||
child: const Scaffold(
|
||||
backgroundColor: AppColors.splashInk,
|
||||
body: Center(child: CircularProgressIndicator(color: AppColors.blue)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 首页目录加载失败与重试入口。
|
||||
|
||||
@@ -80,6 +80,9 @@ 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_logo.png
|
||||
- assets/icon/splash_logo_android12.png
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
# assets:
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart';
|
||||
import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart';
|
||||
|
||||
import 'package:fruitshorts/app.dart';
|
||||
import 'package:fruitshorts/core/config/app_config.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/repositories/favorite_repository.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/playback_repository.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart';
|
||||
import 'package:fruitshorts/core/data/services/catalog_controller.dart';
|
||||
import 'package:fruitshorts/core/theme/app_theme.dart';
|
||||
import 'package:fruitshorts/l10n/app_localizations.dart';
|
||||
import 'package:fruitshorts/pages/home/home_view.dart';
|
||||
|
||||
import '../support/fake_catalog.dart';
|
||||
import '../support/fake_category.dart';
|
||||
import '../support/fake_haptics.dart';
|
||||
import '../support/fake_interaction_repositories.dart';
|
||||
import '../support/fake_notice_repository.dart';
|
||||
import '../support/widget_player.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
SharedPreferencesAsyncPlatform.instance =
|
||||
InMemorySharedPreferencesAsync.empty();
|
||||
AppConfig.clearRuntimeBaseUrl();
|
||||
AppConfig.ready = Future<void>.value();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
AppConfig.clearRuntimeBaseUrl();
|
||||
AppConfig.ready = Future<void>.value();
|
||||
});
|
||||
|
||||
testWidgets('should_show_platform_startup_image_before_catalog_settles', (
|
||||
tester,
|
||||
) async {
|
||||
final controller = CatalogController(_EmptyCatalogRepository())
|
||||
..loading = true
|
||||
..settled = false;
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
for (final platform in [TargetPlatform.android, TargetPlatform.iOS]) {
|
||||
debugDefaultTargetPlatformOverride = platform;
|
||||
await _pumpHomePage(tester, controller);
|
||||
|
||||
final image = tester.widget<Image>(
|
||||
find.byKey(const Key('startup-splash')),
|
||||
);
|
||||
final expectedAsset = platform == TargetPlatform.android
|
||||
? 'assets/icon/splash_logo_android12.png'
|
||||
: 'assets/icon/splash_logo.png';
|
||||
final expectedSize = platform == TargetPlatform.android ? 288.0 : 384.0;
|
||||
|
||||
expect(
|
||||
image.image,
|
||||
isA<AssetImage>().having(
|
||||
(asset) => asset.assetName,
|
||||
'assetName',
|
||||
expectedAsset,
|
||||
),
|
||||
);
|
||||
expect(
|
||||
tester.getSize(find.byKey(const Key('startup-splash'))),
|
||||
Size.square(expectedSize),
|
||||
);
|
||||
expect(find.byType(CircularProgressIndicator), findsNothing);
|
||||
}
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
});
|
||||
|
||||
testWidgets('should_show_spinner_when_retry_loads_after_catalog_settled', (
|
||||
tester,
|
||||
) async {
|
||||
final controller = CatalogController(_EmptyCatalogRepository())
|
||||
..loading = true
|
||||
..settled = true;
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await _pumpHomePage(tester, controller);
|
||||
|
||||
expect(find.byType(CircularProgressIndicator), findsOneWidget);
|
||||
expect(find.byKey(const Key('startup-splash')), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('should_enter_home_and_remove_startup_image_when_data_is_ready', (
|
||||
tester,
|
||||
) async {
|
||||
AppConfig.ready = Future<void>.value();
|
||||
final catalog = _PendingCatalogRepository();
|
||||
await tester.pumpWidget(
|
||||
LumaDramaApp(
|
||||
playerFactory: createWidgetPlayer,
|
||||
catalogRepository: catalog,
|
||||
categoryRepository: FakeCategoryRepository(catalog: catalog),
|
||||
watchHistoryRepository: InMemoryWatchHistoryRepository(),
|
||||
favoriteRepository: InMemoryFavoriteRepository(cards: catalog.dramas),
|
||||
playbackRepository: FakePlaybackRepository(),
|
||||
commentRepository: FakeCommentRepository(),
|
||||
interactionRepository: FakeInteractionRepository(),
|
||||
noticeRepository: FakeNoticeRepository(),
|
||||
haptics: FakeHaptics(),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byKey(const Key('startup-splash')), findsOneWidget);
|
||||
expect(find.byType(CircularProgressIndicator), findsNothing);
|
||||
|
||||
catalog.complete(_feed);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const Key('startup-splash')), findsNothing);
|
||||
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _pumpHomePage(
|
||||
WidgetTester tester,
|
||||
CatalogController controller,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
ChangeNotifierProvider<CatalogController>.value(
|
||||
value: controller,
|
||||
child: MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
locale: const Locale('en'),
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
home: const HomePage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
}
|
||||
|
||||
final _feed = HomeFeed(
|
||||
sections: [
|
||||
FeedSection(code: 'hot', title: 'Hot', dramas: [demoDramas.first]),
|
||||
],
|
||||
);
|
||||
|
||||
class _EmptyCatalogRepository implements CatalogRepository {
|
||||
@override
|
||||
Future<HomeFeed> fetchHomeFeed() async => const HomeFeed();
|
||||
|
||||
@override
|
||||
Future<HomeFeed> fetchDiscoverFeed() async => const HomeFeed();
|
||||
|
||||
@override
|
||||
Future<Drama> fetchDrama(String dramaId) async => _feed.dramas.first;
|
||||
}
|
||||
|
||||
class _PendingCatalogRepository implements CatalogRepository {
|
||||
final Completer<HomeFeed> _home = Completer<HomeFeed>();
|
||||
|
||||
List<Drama> get dramas => _feed.dramas;
|
||||
|
||||
void complete(HomeFeed feed) {
|
||||
if (!_home.isCompleted) _home.complete(feed);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<HomeFeed> fetchHomeFeed() => _home.future;
|
||||
|
||||
@override
|
||||
Future<HomeFeed> fetchDiscoverFeed() async => _feed;
|
||||
|
||||
@override
|
||||
Future<Drama> fetchDrama(String dramaId) async => _feed.dramas.first;
|
||||
}
|
||||
Reference in new issue
Block a user