style(theme): 客户端切换深色视觉风格

This commit is contained in:
Zhou Weidong committed 2026-10-22 13:49:20 +08:00
1 parent 295c21513c
commit 4bc04fd8ae
75 files changed
+732 -330

No files matched your search

+5 -1
View File
@@ -13,6 +13,7 @@ import 'package:fruitshorts/core/data/services/auth_controller.dart';
import 'package:fruitshorts/core/data/services/auth_session_store.dart';
import 'package:fruitshorts/core/platform/haptics.dart';
import 'package:fruitshorts/core/theme/app_colors.dart';
import 'package:fruitshorts/core/theme/app_dimens.dart';
import 'package:fruitshorts/core/theme/app_theme.dart';
import 'package:fruitshorts/l10n/app_localizations.dart';
import 'package:fruitshorts/pages/auth/auth_view.dart';
@@ -131,7 +132,10 @@ void main() {
final card = tester.widget<Container>(find.byKey(const Key('auth-card')));
final cardDecoration = card.decoration! as BoxDecoration;
expect(cardDecoration.color, AppColors.card);
expect(cardDecoration.borderRadius, BorderRadius.circular(20));
expect(
cardDecoration.borderRadius,
BorderRadius.circular(AppDimens.authCardRadius),
);
expect(cardDecoration.boxShadow, contains(AppTheme.cardShadow));
expect(card.padding, const EdgeInsets.all(20));
+6 -6
View File
@@ -7,7 +7,7 @@ import 'package:fruitshorts/core/theme/app_theme.dart';
import 'package:fruitshorts/core/widgets/subpage_header.dart';
void main() {
testWidgets('should_render_unified_light_header_dimensions', (tester) async {
testWidgets('should_render_unified_dark_header_dimensions', (tester) async {
await tester.pumpWidget(
MaterialApp(
theme: AppTheme.light(),
@@ -47,7 +47,7 @@ void main() {
)
.decoration
as BoxDecoration;
expect(shadow.boxShadow!.single.color, const Color(0x14000000));
expect(shadow.boxShadow!.single.color, AppColors.shadowSubtle);
expect(shadow.boxShadow!.single.blurRadius, 8);
expect(shadow.boxShadow!.single.offset, const Offset(0, 2));
});
@@ -75,7 +75,7 @@ void main() {
MaterialApp(
theme: AppTheme.light(),
home: const Scaffold(
backgroundColor: Colors.black,
backgroundColor: AppColors.mediaBlack,
body: AppSubpageHeader(
title: 'Title',
backKey: Key('sample-back'),
@@ -97,8 +97,8 @@ void main() {
matching: find.byIcon(Icons.chevron_left_rounded),
),
);
expect(material.color, const Color(0x59000000));
expect(icon.color, Colors.white);
expect(material.color, AppColors.headerScrim);
expect(icon.color, AppColors.onSurface);
expect(icon.size, 22);
expect(icon.weight, 600);
final shadow =
@@ -108,7 +108,7 @@ void main() {
)
.decoration
as BoxDecoration;
expect(shadow.boxShadow!.single.color, const Color(0x33000000));
expect(shadow.boxShadow!.single.color, AppColors.shadowSoft);
expect(shadow.boxShadow!.single.blurRadius, 8);
});
}
+4 -4
View File
@@ -33,7 +33,7 @@ void main() {
);
expect(
tester.widget<Icon>(find.byKey(const Key('empty-state-icon'))).color,
AppColors.blue,
AppColors.blueSoft,
);
expect(
tester
@@ -103,7 +103,7 @@ void main() {
expect(find.byKey(const Key('empty-state-action')), findsNothing);
});
testWidgets('should_tint_the_bare_icon_with_the_light_theme', (tester) async {
testWidgets('should_tint_the_bare_icon_with_the_dark_theme', (tester) async {
await _pump(
tester,
theme: AppTheme.light(),
@@ -111,7 +111,7 @@ void main() {
);
expect(
tester.widget<Icon>(find.byKey(const Key('empty-state-icon'))).color,
AppColors.blue,
AppColors.blueSoft,
);
await _pump(
@@ -121,7 +121,7 @@ void main() {
);
expect(
tester.widget<Icon>(find.byKey(const Key('empty-state-icon'))).color,
AppColors.blue,
AppColors.blueSoft,
);
});
+1 -1
View File
@@ -144,6 +144,6 @@ void main() {
matching: find.byType(ColoredBox),
),
);
expect(coloredBox.color, Colors.black);
expect(coloredBox.color, AppColors.bar);
});
}
+8 -8
View File
@@ -52,9 +52,9 @@ void main() {
final bar = tester.widget<Container>(barFinder);
final decoration = bar.decoration as BoxDecoration;
final border = decoration.border as Border;
// 首页 feed 用深色导航。
expect(decoration.color, Colors.black);
expect(border.top.color, Colors.white12);
// 所有页面统一深色导航。
expect(decoration.color, AppColors.bar);
expect(border.top.color, AppColors.border);
expect(
tester
.widget<Text>(
@@ -65,7 +65,7 @@ void main() {
)
.style
?.color,
Colors.white70,
AppColors.navUnselected,
);
expect(
tester
@@ -100,10 +100,10 @@ void main() {
await tester.tap(find.byKey(const Key('tab-theater')));
await tester.pumpAndSettle();
expect(haptics.selectionClicks, 1);
final lightBar = tester.widget<Container>(barFinder);
final lightDecoration = lightBar.decoration as BoxDecoration;
expect(lightDecoration.color, AppColors.bar.withValues(alpha: 0.95));
expect((lightDecoration.border as Border).top.color, AppColors.border);
final theaterBar = tester.widget<Container>(barFinder);
final theaterDecoration = theaterBar.decoration as BoxDecoration;
expect(theaterDecoration.color, AppColors.bar.withValues(alpha: 0.96));
expect((theaterDecoration.border as Border).top.color, AppColors.border);
await tester.tap(find.byKey(const Key('tab-theater')));
await tester.pumpAndSettle();
expect(haptics.selectionClicks, 1);
+2 -2
View File
@@ -328,7 +328,7 @@ void main() {
expect(find.byKey(const Key('home-feed-pip')), findsNothing);
});
testWidgets('should_restore_light_theme_after_leaving_feed_tab', (
testWidgets('should_keep_light_status_bar_after_leaving_feed_tab', (
tester,
) async {
await _pumpApp(tester);
@@ -338,7 +338,7 @@ void main() {
await tester.pumpAndSettle();
expect(find.byKey(const Key('home-page-gradient')), findsOneWidget);
expect(_hasLightStatusBar(tester), isFalse);
expect(_hasLightStatusBar(tester), isTrue);
});
testWidgets('should_switch_feed_drama_when_swiped_up', (tester) async {
+3 -2
View File
@@ -3,6 +3,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/theme/app_colors.dart';
import 'package:fruitshorts/pages/player/widgets/loading_progress_track.dart';
void main() {
@@ -28,7 +29,7 @@ void main() {
tester
.widget<ColoredBox>(find.byKey(const Key('progress-loading-track')))
.color,
Colors.white24,
AppColors.inactiveTrack,
);
expect(
tester
@@ -36,7 +37,7 @@ void main() {
find.byKey(const Key('progress-loading-highlight')),
)
.color,
Colors.white,
AppColors.onPrimary,
);
final initialLeft = tester
+4 -1
View File
@@ -825,7 +825,10 @@ void main() {
for (final style in [label, synopsis, separator]) {
expect(style.fontSize, 12);
expect(style.fontWeight, FontWeight.w500);
expect(style.color?.withValues(alpha: 1), AppColors.playerMuted);
expect(
style.color?.withValues(alpha: 1),
AppColors.playerMuted.withValues(alpha: 1),
);
}
expect(separator.color, isNot(Colors.white));
});
+7 -5
View File
@@ -4,13 +4,15 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/theme/app_dimens.dart';
void main() {
test('should_expose_the_k20_spacing_and_type_scale', () {
test('should_expose_dark_theme_radius_and_spacing_scale', () {
expect(AppDimens.pageHorizontal, 16);
expect(AppDimens.cardRadius, 16);
expect(AppDimens.coverRadius, 16);
expect(AppDimens.authCardRadius, 20);
expect(AppDimens.cardRadius, 12);
expect(AppDimens.coverRadius, 12);
expect(AppDimens.authCardRadius, 12);
expect(AppDimens.buttonRadius, 10);
expect(AppDimens.inputRadius, 10);
expect(AppDimens.chipRadius, 6);
expect(AppDimens.pillRadius, 999);
expect(AppDimens.buttonRadius, 999);
expect(AppDimens.caption, 12);
expect(AppDimens.body, 14);
expect(AppDimens.listTitle, 15);
+40 -24
View File
@@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/app.dart';
import 'package:fruitshorts/core/theme/app_colors.dart';
import 'package:fruitshorts/core/theme/app_dimens.dart';
import 'package:fruitshorts/core/theme/app_theme.dart';
double _contrast(Color foreground, Color background) {
@@ -16,44 +17,59 @@ double _contrast(Color foreground, Color background) {
}
void main() {
test('should_expose_light_warm_orange_tokens', () {
expect(AppColors.blue, const Color(0xFFFF7A1A));
expect(AppColors.bluePressed, const Color(0xFFFF6A00));
expect(AppColors.secondary, const Color(0xFFFF6A8A));
expect(AppColors.ink, const Color(0xFFFFF8F0));
expect(AppColors.surfaceTop, const Color(0xFFFFE9D6));
expect(AppColors.card, const Color(0xFFFFFFFF));
expect(AppColors.onSurface, const Color(0xFF1F1B16));
expect(AppColors.muted, const Color(0xFF8C837B));
expect(AppColors.subtle, const Color(0xFFB3AAA1));
expect(AppColors.border, const Color(0xFFF0E4D6));
expect(AppColors.badgePink, const Color(0xFFFFE3EC));
expect(AppColors.badgePinkText, const Color(0xFFFF6A8A));
test('should_expose_dark_visual_tokens', () {
expect(AppColors.blue, const Color(0xFFFF6B2C));
expect(AppColors.bluePressed, const Color(0xFFE55A1F));
expect(AppColors.secondary, const Color(0xFF22D3EE));
expect(AppColors.ink, const Color(0xFF0B1020));
expect(AppColors.card, const Color(0xFF151B2E));
expect(AppColors.raised, const Color(0xFF1C2436));
expect(AppColors.border, const Color(0x0FFFFFFF));
expect(AppColors.onSurface, const Color(0xEBFFFFFF));
expect(AppColors.muted, const Color(0xA3FFFFFF));
expect(AppColors.subtle, const Color(0x61FFFFFF));
expect(AppDimens.cardRadius, 12);
expect(AppDimens.buttonRadius, 10);
expect(AppDimens.inputRadius, 10);
expect(AppDimens.chipRadius, 6);
});
test('should_keep_primary_text_readable_on_warm_background', () {
test('should_keep_primary_and_secondary_text_readable_on_dark', () {
expect(
_contrast(AppColors.onSurface, AppColors.ink),
greaterThanOrEqualTo(4.5),
);
expect(
_contrast(AppColors.onSurface, AppColors.card),
greaterThanOrEqualTo(4.5),
);
expect(_contrast(AppColors.muted, AppColors.card), greaterThanOrEqualTo(3));
expect(_contrast(AppColors.gold, AppColors.ink), greaterThanOrEqualTo(3));
expect(_contrast(AppColors.blue, AppColors.ink), greaterThanOrEqualTo(3));
});
test('should_use_light_color_scheme_and_dark_system_icons', () {
final theme = AppTheme.light();
test('should_use_dark_color_scheme_and_light_system_icons', () {
final theme = AppTheme.dark();
expect(theme.brightness, Brightness.light);
expect(theme.brightness, Brightness.dark);
expect(theme.colorScheme.primary, AppColors.blue);
expect(theme.colorScheme.onPrimary, AppColors.onPrimary);
expect(theme.colorScheme.secondary, AppColors.secondary);
expect(theme.colorScheme.surface, AppColors.ink);
expect(theme.colorScheme.onSurface, AppColors.onSurface);
expect(theme.colorScheme.outline, AppColors.border);
expect(theme.scaffoldBackgroundColor, AppColors.ink);
expect(theme.appBarTheme.systemOverlayStyle, SystemUiOverlayStyle.dark);
expect(theme.appBarTheme.backgroundColor, AppColors.card);
expect(theme.appBarTheme.systemOverlayStyle, SystemUiOverlayStyle.light);
expect(theme.cardTheme.elevation, 0);
expect(theme.dialogTheme.backgroundColor, AppColors.raised);
expect(theme.bottomNavigationBarTheme.backgroundColor, AppColors.bar);
expect(theme.inputDecorationTheme.fillColor, AppColors.input);
});
test('should_use_ink_navigation_bar_without_contrast_scrim', () {
test('should_use_ink_navigation_bar_with_light_icons', () {
expect(appSystemOverlayStyle.statusBarColor, Colors.transparent);
expect(appSystemOverlayStyle.statusBarIconBrightness, Brightness.light);
expect(appSystemOverlayStyle.systemNavigationBarColor, AppColors.ink);
expect(
appSystemOverlayStyle.systemNavigationBarDividerColor,
@@ -61,20 +77,20 @@ void main() {
);
expect(
appSystemOverlayStyle.systemNavigationBarIconBrightness,
Brightness.dark,
Brightness.light,
);
expect(appSystemOverlayStyle.systemNavigationBarContrastEnforced, isFalse);
final sheetTheme = AppTheme.light().bottomSheetTheme;
final sheetTheme = AppTheme.dark().bottomSheetTheme;
expect(sheetTheme.backgroundColor, Colors.transparent);
expect(sheetTheme.modalBackgroundColor, Colors.transparent);
expect(sheetTheme.surfaceTintColor, Colors.transparent);
});
test('should_expose_page_gradient_and_soft_card_shadow', () {
test('should_expose_flat_page_gradient_without_card_shadow', () {
expect(AppTheme.pageGradient.colors, [AppColors.surfaceTop, AppColors.ink]);
expect(AppTheme.cardShadow.color, AppColors.cardShadow);
expect(AppTheme.cardShadow.blurRadius, 18);
expect(AppTheme.cardShadow.offset, const Offset(0, 6));
expect(AppTheme.cardShadow.blurRadius, 0);
expect(AppTheme.cardShadow.offset, Offset.zero);
});
}
+117
View File
@@ -0,0 +1,117 @@
// Author: fruit.com.cn
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/data/remote/api_client.dart';
import 'package:fruitshorts/core/data/remote/auth_api.dart';
import 'package:fruitshorts/core/data/services/auth_controller.dart';
import 'package:fruitshorts/core/data/services/auth_session_store.dart';
import 'package:fruitshorts/core/theme/app_colors.dart';
import 'package:fruitshorts/core/theme/app_theme.dart';
import 'package:fruitshorts/l10n/app_localizations.dart';
import 'package:fruitshorts/pages/watch_records/watch_records_page.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 '../support/fake_http_adapter.dart';
void main() {
setUp(() {
SharedPreferencesAsyncPlatform.instance =
InMemorySharedPreferencesAsync.empty();
});
testWidgets('should_render_material_app_with_dark_default_theme', (
tester,
) async {
await tester.pumpWidget(
MaterialApp(
theme: AppTheme.dark(),
darkTheme: AppTheme.dark(),
themeMode: ThemeMode.dark,
locale: const Locale('en'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Scaffold(
key: const Key('dark-smoke-scaffold'),
appBar: AppBar(title: const Text('Dark')),
body: Card(
key: const Key('dark-smoke-card'),
child: ListTile(
title: Text(
'Primary',
style: TextStyle(color: AppColors.onSurface),
),
subtitle: Text('Muted', style: TextStyle(color: AppColors.muted)),
trailing: Text('128', style: TextStyle(color: AppColors.gold)),
),
),
bottomNavigationBar: NavigationBar(
selectedIndex: 0,
destinations: const [
NavigationDestination(icon: Icon(Icons.home), label: 'Home'),
NavigationDestination(icon: Icon(Icons.person), label: 'Mine'),
],
),
),
),
);
await tester.pumpAndSettle();
final context = tester.element(
find.byKey(const Key('dark-smoke-scaffold')),
);
final theme = Theme.of(context);
expect(theme.brightness, Brightness.dark);
expect(theme.scaffoldBackgroundColor, AppColors.ink);
expect(theme.appBarTheme.backgroundColor, AppColors.card);
expect(theme.cardTheme.color, AppColors.card);
final card = tester.widget<Card>(find.byKey(const Key('dark-smoke-card')));
expect(card.color ?? theme.cardTheme.color, AppColors.card);
expect(find.text('Primary'), findsOneWidget);
expect(find.text('Muted'), findsOneWidget);
expect(find.text('128'), findsOneWidget);
});
testWidgets('should_render_watch_records_page_on_dark_surface', (
tester,
) async {
final auth = AuthController(
AuthApi(
ApiClient(
baseUrl: 'http://127.0.0.1:1',
adapter: FakeHttpAdapter(
(_) async =>
FakeHttpResponse('{"code":0,"message":"ok","data":null}'),
),
),
),
AuthSessionStore(),
);
await tester.pumpWidget(
ChangeNotifierProvider<AuthController>.value(
value: auth,
child: MaterialApp(
theme: AppTheme.dark(),
darkTheme: AppTheme.dark(),
themeMode: ThemeMode.dark,
locale: const Locale('en'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const WatchRecordsPage(),
),
),
);
await tester.pumpAndSettle();
final scaffold = tester.widget<Scaffold>(
find.byKey(const Key('watch-records-page')),
);
expect(scaffold.backgroundColor, AppColors.ink);
expect(find.byKey(const Key('auth-gate-panel')), findsOneWidget);
expect(find.byType(Scaffold), findsOneWidget);
});
}