11 Commits
Author SHA1 Message Date
Zhou Weidong 66c03ee245 chore(release): 发布 iOS 分享面板修复
flutter-ci / test (push) Canceled after 0s
# Conflicts:
#	lib/core/config/app_config.dart
2026-10-31 14:02:40 +08:00
Zhou Weidong a3ac007759 fix(config): release 构建自动使用生产环境 2026-10-31 14:00:33 +08:00
Zhou Weidong 8ec6df2be5 merge: 修复 iOS 系统分享面板
flutter-ci / test (push) Canceled after 0s
2026-10-31 13:40:14 +08:00
Zhou Weidong 5ec4db151a fix(ios): 修复分享面板并同步互动栏布局断言 2026-10-31 13:33:13 +08:00
Vicky 525f58c150 chore(release): 合并 develop 到 main
flutter-ci / test (push) Canceled after 0s
2026-10-31 09:49:54 +08:00
Vicky a47af8c3ad style(player): 视频信息区文案统一为 65% 半透明白
flutter-ci / test (push) Canceled after 0s
2026-10-31 09:41:19 +08:00
Vicky 0a02620564 chore(release): 合并 develop 到 main
flutter-ci / test (push) Canceled after 0s
2026-10-31 00:02:59 +08:00
Vicky 2ddf9ee1c6 test(config): 服务地址断言跟随环境开关
flutter-ci / test (push) Canceled after 0s
2026-10-31 00:02:53 +08:00
Vicky 3bc79ad8e2 fix(i18n): 去掉印尼语内容完整性过滤,缺失时回退中文 2026-10-31 00:02:53 +08:00
Vicky 78a33cd4f1 chore(release): 合并 develop 到 main
flutter-ci / test (push) Canceled after 0s
2026-10-30 22:56:52 +08:00
Vicky e8610b7aed chore(release): 生产包切换为生产环境地址
flutter-ci / test (push) Canceled after 0s
2026-10-30 17:20:48 +08:00
25 changed files with 133 additions and 151 deletions

No files matched your search

+48 -6
View File
@@ -386,15 +386,29 @@ final class PipShadowCoordinator: NSObject, AVPictureInPictureControllerDelegate
guard call.method == "shareText",
let arguments = call.arguments as? [String: Any],
let text = arguments["text"] as? String,
!text.isEmpty,
let controller = self?.window?.rootViewController else {
!text.isEmpty else {
result(FlutterError(code: "share_unavailable", message: "Share sheet is unavailable", details: nil))
return
}
let activity = UIActivityViewController(activityItems: [text], applicationActivities: nil)
activity.popoverPresentationController?.sourceView = controller.view
activity.completionWithItemsHandler = { _, _, _, _ in result(nil) }
controller.present(activity, animated: true)
DispatchQueue.main.async {
guard let controller = self?.sharePresenter() else {
result(FlutterError(code: "share_unavailable", message: "Share sheet is unavailable", details: nil))
return
}
let activity = UIActivityViewController(activityItems: [text], applicationActivities: nil)
if let popover = activity.popoverPresentationController {
popover.sourceView = controller.view
popover.sourceRect = CGRect(
x: controller.view.bounds.midX,
y: controller.view.bounds.midY,
width: 0,
height: 0
)
popover.permittedArrowDirections = []
}
activity.completionWithItemsHandler = { _, _, _, _ in result(nil) }
controller.present(activity, animated: true)
}
}
let thumbnailChannel = FlutterMethodChannel(
@@ -459,6 +473,34 @@ final class PipShadowCoordinator: NSObject, AVPictureInPictureControllerDelegate
}
}
/// 当前前台 Scene 的根控制器之上,最适合展示系统分享面板的控制器。
private func sharePresenter() -> UIViewController? {
let keyWindow = UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.first { $0.activationState == .foregroundActive }?
.windows.first { $0.isKeyWindow }
guard let rootController = keyWindow?.rootViewController else { return nil }
let presenter = topPresentedController(from: rootController)
guard presenter.viewIfLoaded?.window != nil else { return nil }
return presenter
}
/// 解析已展示页面、导航栈和标签页中的当前可见控制器。
private func topPresentedController(from controller: UIViewController) -> UIViewController {
if let presented = controller.presentedViewController {
return topPresentedController(from: presented)
}
if let navigation = controller as? UINavigationController,
let visible = navigation.visibleViewController {
return topPresentedController(from: visible)
}
if let tabs = controller as? UITabBarController,
let selected = tabs.selectedViewController {
return topPresentedController(from: selected)
}
return controller
}
/// iOS 画中画:影子播放器通道,与 Android 共用同一 channel 名。
private func registerPipChannel(_ engineBridge: FlutterImplicitEngineBridge) {
let channel = FlutterMethodChannel(
+5 -5
View File
@@ -1,5 +1,7 @@
// Author: fruit.com.cn
import 'package:flutter/foundation.dart';
import 'dev_config.dart';
import 'prod_config.dart';
@@ -9,12 +11,10 @@ import 'prod_config.dart';
/// - `dev_config.dart`:[DevConfig](本机联调)
/// - `prod_config.dart`:[ProdConfig](生产)
///
/// 切换环境只改下面这一个布尔值,不用传任何构建参数、也不做网络探测。
/// Debug 使用开发环境,Release 自动使用生产环境,不做网络探测。
abstract final class AppConfig {
/// **环境开关**:true 用开发环境,false 用生产环境。
///
/// 打生产包前必须改成 false;release 构建若仍为 true 会在启动时直接报错。
static const bool useDevConfig = true;
/// **环境开关**:Debug 使用开发环境,Release 使用生产环境。
static const bool useDevConfig = !kReleaseMode;
/// 当前选中的环境地址。
static String get baseUrl =>
+2 -2
View File
@@ -241,11 +241,11 @@ class Drama {
}
String localizedTitle(String languageCode) {
return localizedRequiredText(languageCode, title, titleLocales);
return localizedText(languageCode, title, titleLocales);
}
String localizedSynopsis(String languageCode) {
return localizedRequiredText(languageCode, synopsis, synopsisLocales);
return localizedText(languageCode, synopsis, synopsisLocales);
}
String localizedTag(String languageCode) {
-13
View File
@@ -18,16 +18,3 @@ String localizedText(
if (chinese.isNotEmpty) return chinese;
return fallback;
}
/// 按当前语言取必须本地化的文案;印尼语缺失时不回退中文。
String localizedRequiredText(
String languageCode,
String fallback,
Map<String, String> locales,
) {
final primaryCode = languageCode.split(RegExp(r'[_-]')).first;
final selected = locales[languageCode] ?? locales[primaryCode] ?? '';
if (selected.trim().isNotEmpty) return selected;
if (primaryCode == 'id') return '';
return localizedText(primaryCode, fallback, locales);
}
@@ -1,21 +0,0 @@
// Author: fruit.com.cn
import '../data/models/drama.dart';
const String _indonesianLanguageCode = 'id';
/// 判断短剧是否满足当前界面语言的基础文案完整度。
bool isDramaCompleteForLocale(Drama drama, String languageCode) {
final primaryCode = languageCode.split(RegExp(r'[_-]')).first;
if (primaryCode != _indonesianLanguageCode) return true;
return drama.localizedTitle(primaryCode).trim().isNotEmpty &&
drama.localizedSynopsis(primaryCode).trim().isNotEmpty;
}
/// 过滤不满足首发语言文案完整度的短剧,保持原顺序。
List<Drama> visibleDramasForLocale(
Iterable<Drama> dramas,
String languageCode,
) => dramas
.where((drama) => isDramaCompleteForLocale(drama, languageCode))
.toList(growable: false);
+3
View File
@@ -27,6 +27,9 @@ abstract final class AppColors {
static const overlaySecondary = Color(0xFFC7C7CC);
static const overlayLabel = Color(0xFFE6E6EB);
/// 首页及剧集播放页视频信息文案:参考设计图的 65% 半透明白。
static const homeFeedVideoCopy = Color(0xA6FFFFFF);
/// 主操作、选中态、播放进度。旧品牌橙已全量映射为品牌红(2026-10 改版)。
static const blue = Color(0xFFF04E4E);
static const primary = blue;
@@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
import '../../../core/data/models/drama.dart';
import '../../../core/formatting/error_text.dart';
import '../../../core/formatting/content_locale_guard.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/widgets/empty_state.dart';
import '../../../l10n/app_localizations.dart';
@@ -34,18 +33,10 @@ class DiscoverSearchResults extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final visibleResults = visibleDramasForLocale(
results,
Localizations.localeOf(context).languageCode,
);
if (!loading && error == null && visibleResults.isNotEmpty) {
if (!loading && error == null && results.isNotEmpty) {
return SliverPadding(
padding: _topPadding,
sliver: SliverDramaGrid(
l10n: l10n,
dramas: visibleResults,
onOpen: onOpen,
),
sliver: SliverDramaGrid(l10n: l10n, dramas: results, onOpen: onOpen),
);
}
return SliverPadding(
@@ -7,7 +7,6 @@ import 'package:provider/provider.dart';
import '../../../core/data/models/drama.dart';
import '../../../core/data/services/category_controller.dart';
import '../../../core/formatting/content_locale_guard.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../core/widgets/app_pull_to_refresh.dart';
import '../../../core/widgets/drama_grid_skeleton.dart';
@@ -78,10 +77,7 @@ class _HomeCategoryDramasState extends State<HomeCategoryDramas> {
final controller = context.watch<CategoryController>();
final categoryId = widget.lockedCategoryId ?? '';
final slot = categoryId.isEmpty ? null : controller.slotOf(categoryId);
final dramas = visibleDramasForLocale(
slot?.dramas ?? const <Drama>[],
Localizations.localeOf(context).languageCode,
);
final dramas = slot?.dramas ?? const <Drama>[];
return Column(
children: [
Expanded(
+3 -13
View File
@@ -12,7 +12,6 @@ import '../../../core/data/services/catalog_controller.dart';
import '../../../core/data/services/category_controller.dart';
import '../../../core/data/services/home_feed_controller.dart';
import '../../../core/data/services/wallet_controller.dart';
import '../../../core/formatting/content_locale_guard.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../core/widgets/app_pull_to_refresh.dart';
@@ -195,10 +194,7 @@ class _HomeFeedGridState extends State<HomeFeedGrid> {
mainAxisSpacing: AppDimens.homeGridRowGap,
);
}
final dramas = _dramasFor(
category,
Localizations.localeOf(context).languageCode,
);
final dramas = _dramasFor(category);
if (widget.feedController.loadingFirstPage && dramas.isEmpty) {
return const DramaGridSkeleton(
key: Key('home-grid-skeleton'),
@@ -328,14 +324,8 @@ class _HomeFeedGridState extends State<HomeFeedGrid> {
];
}
List<Drama> _dramasFor(
HomeGridCategoryShortcut category,
String languageCode,
) {
final dramas = visibleDramasForLocale(
widget.feedController.dramas,
languageCode,
);
List<Drama> _dramasFor(HomeGridCategoryShortcut category) {
final dramas = widget.feedController.dramas;
final categoryName = category.categoryName.toLowerCase();
if (categoryName.isEmpty) return dramas;
final matched = dramas
@@ -12,7 +12,6 @@ const double _synopsisToggleWidthFraction = 0.55;
const double _synopsisToggleGap = 6;
const _synopsisMotion = Duration(milliseconds: 240);
const _synopsisStyle = TextStyle(
color: AppColors.overlaySecondary,
fontSize: _synopsisFont,
fontWeight: FontWeight.w400,
height: _synopsisLineHeight,
@@ -32,6 +31,7 @@ class ExpandableSynopsis extends StatefulWidget {
required this.synopsis,
this.keyPrefix = 'home-feed',
this.expandedFooter,
this.textColor = AppColors.homeFeedVideoCopy,
});
/// 按当前语言获取的简介正文。
@@ -43,6 +43,9 @@ class ExpandableSynopsis extends StatefulWidget {
/// 展开后附加的声明等内容。
final Widget? expandedFooter;
/// 简介正文颜色;默认使用视频信息区统一的半透明浅灰白。
final Color textColor;
@override
State<ExpandableSynopsis> createState() => _ExpandableSynopsisState();
}
@@ -88,7 +91,7 @@ class _ExpandableSynopsisState extends State<ExpandableSynopsis> {
}
bool _needsToggle(double maxWidth) {
final painter = _painter(widget.synopsis, _synopsisStyle);
final painter = _painter(widget.synopsis, _bodyStyle);
painter.layout(maxWidth: maxWidth);
final overflows = painter.didExceedMaxLines;
painter.dispose();
@@ -120,11 +123,14 @@ class _ExpandableSynopsisState extends State<ExpandableSynopsis> {
Key _key(String suffix) => Key('${widget.keyPrefix}-synopsis-$suffix');
TextStyle get _bodyStyle => _synopsisStyle.copyWith(color: widget.textColor);
Widget _body() {
final style = _bodyStyle;
final text = Text.rich(
TextSpan(text: widget.synopsis, style: _synopsisStyle),
TextSpan(text: widget.synopsis, style: style),
key: _expanded ? _key('expanded') : Key('${widget.keyPrefix}-synopsis'),
style: _synopsisStyle,
style: style,
maxLines: _expanded ? null : _collapsedSynopsisLines,
overflow: _expanded ? TextOverflow.clip : TextOverflow.ellipsis,
);
@@ -101,7 +101,7 @@ class _TagChip extends StatelessWidget {
maxLines: 1,
strutStyle: _metadataStrutStyle,
style: const TextStyle(
color: AppColors.overlaySecondary,
color: AppColors.homeFeedVideoCopy,
fontSize: _tagFont,
fontWeight: FontWeight.w500,
height: _metadataLineHeight,
@@ -27,6 +27,7 @@ const double _progressHeight = 2;
const double _progressHitHeight = 16;
const double _pageHorizontal = 20;
const double _railRight = 14;
const double _railShiftRight = 8;
const double _railGap = 12;
const double _railWidth = 56;
const double _infoWatchAllGap = 2;
@@ -234,7 +235,10 @@ class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
padding: const EdgeInsets.only(
bottom: _railBottomOffset,
),
child: _rail(),
child: Transform.translate(
offset: const Offset(_railShiftRight, 0),
child: _rail(),
),
),
),
],
@@ -103,6 +103,7 @@ class _HomeFeedInfoPanelState extends State<HomeFeedInfoPanel> {
ExpandableSynopsis(
key: ValueKey(widget.drama.id),
synopsis: synopsis,
textColor: AppColors.homeFeedVideoCopy,
),
],
],
@@ -9,7 +9,6 @@ import '../../core/data/models/drama.dart';
import '../../core/data/models/home_feed.dart';
import '../../core/data/remote/catalog_api.dart';
import '../../core/data/services/category_controller.dart';
import '../../core/formatting/content_locale_guard.dart';
import '../../core/theme/app_colors.dart';
import '../../core/widgets/app_pull_to_refresh.dart';
import '../../core/widgets/empty_state.dart';
@@ -285,10 +284,7 @@ class _ChannelFeedState extends State<_ChannelFeed>
super.build(context);
final categories = context.watch<CategoryController>();
final slot = categories.slotOf(widget.categoryId);
final dramas = visibleDramasForLocale(
slot.dramas,
Localizations.localeOf(context).languageCode,
);
final dramas = slot.dramas;
final showCarousel = widget.isFeatured && widget.banners.isNotEmpty;
final bottomInset = MediaQuery.of(context).padding.bottom;
+1 -5
View File
@@ -13,7 +13,6 @@ import '../../core/data/services/download_controller.dart';
import '../../core/data/services/home_feed_controller.dart';
import '../../core/data/services/playlist_controller.dart';
import '../../core/data/services/wallet_controller.dart';
import '../../core/formatting/content_locale_guard.dart';
import '../../core/theme/app_colors.dart';
import '../../core/widgets/empty_state.dart';
import '../../l10n/app_localizations.dart';
@@ -299,10 +298,7 @@ class _RecommendPageState extends State<RecommendPage>
Widget _recommendFeed(BuildContext context) {
final feed = widget.feedController;
final dramas = visibleDramasForLocale(
widget.feedController.dramas,
Localizations.localeOf(context).languageCode,
);
final dramas = widget.feedController.dramas;
if (dramas.isNotEmpty) {
return PlayerPage(
key: const Key('recommend-feed-player'),
@@ -10,7 +10,6 @@ import '../../../core/data/models/drama.dart';
import '../../../core/data/remote/catalog_api.dart';
import '../../../core/data/services/all_dramas_controller.dart';
import '../../../core/data/services/home_feed_controller.dart';
import '../../../core/formatting/content_locale_guard.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../core/widgets/app_pull_to_refresh.dart';
@@ -108,10 +107,7 @@ class _AllDramasFilterGridState extends State<AllDramasFilterGrid> {
}
Widget _dramaList(AppLocalizations l10n, double itemExtent) {
final dramas = visibleDramasForLocale(
_dramas,
Localizations.localeOf(context).languageCode,
);
final dramas = _dramas;
return NotificationListener<ScrollNotification>(
onNotification: _onScroll,
child: AppPullToRefresh(
@@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
import '../../../core/data/models/drama.dart';
import '../../../core/data/models/playlist_detail.dart';
import '../../../core/formatting/content_locale_guard.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../core/widgets/app_pull_to_refresh.dart';
@@ -158,10 +157,7 @@ class _PlaylistDetailPageState extends State<PlaylistDetailPage> {
}
Widget _body(double itemExtent) {
final visibleDramas = visibleDramasForLocale(
_dramas,
Localizations.localeOf(context).languageCode,
);
final visibleDramas = _dramas;
return CustomScrollView(
key: const Key('playlist-detail-scroll'),
physics: AppPullToRefresh.physics,
@@ -8,7 +8,6 @@ import '../../../core/data/models/playlist_detail.dart';
import '../../../core/data/services/category_controller.dart';
import '../../../core/data/services/home_feed_controller.dart';
import '../../../core/data/services/playlist_controller.dart';
import '../../../core/formatting/content_locale_guard.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../core/widgets/app_pull_to_refresh.dart';
@@ -44,8 +43,7 @@ class RecommendSectionsView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final languageCode = Localizations.localeOf(context).languageCode;
final source = visibleDramasForLocale(feedController.dramas, languageCode);
final source = feedController.dramas;
final remote = playlists;
return AppPullToRefresh(
key: const Key('recommend-sections-refresh'),
@@ -165,10 +163,7 @@ class RecommendSectionsView extends StatelessWidget {
subtitle: playlist.localizedDescription(
Localizations.localeOf(context).languageCode,
),
dramas: visibleDramasForLocale(
playlist.dramas,
Localizations.localeOf(context).languageCode,
).take(_sectionPreviewCount).toList(),
dramas: playlist.dramas.take(_sectionPreviewCount).toList(),
onMore: () => onOpenPlaylist?.call(playlist),
),
);
+10 -5
View File
@@ -62,10 +62,12 @@ void main() {
});
test('should_pick_address_by_environment_switch', () {
// 仓库里的开关默认指向开发环境。
expect(AppConfig.useDevConfig, isTrue);
expect(AppConfig.baseUrl, DevConfig.baseUrl);
expect(AppConfig.apiBaseUrl, DevConfig.baseUrl);
// 跟随开关:开发分支为 true(开发地址),发版分支为 false(生产地址)。
final expected = AppConfig.useDevConfig
? DevConfig.baseUrl
: ProdConfig.baseUrl;
expect(AppConfig.baseUrl, expected);
expect(AppConfig.apiBaseUrl, expected);
expect(Uri.parse(AppConfig.baseUrl).port, 8081);
});
@@ -87,7 +89,10 @@ void main() {
AppConfig.applyRuntimeBaseUrl('http://runtime.test:8081');
expect(AppConfig.apiBaseUrl, 'http://runtime.test:8081');
expect(AppConfig.baseUrl, DevConfig.baseUrl);
expect(
AppConfig.baseUrl,
AppConfig.useDevConfig ? DevConfig.baseUrl : ProdConfig.baseUrl,
);
});
test('should_append_host_to_relative_media_paths', () {
@@ -0,0 +1,18 @@
// Author: fruit.com.cn
// 应用环境选择测试。
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/config/app_config.dart';
import 'package:fruitshorts/core/config/dev_config.dart';
import 'package:fruitshorts/core/config/prod_config.dart';
void main() {
test('should_use_environment_matching_build_mode', () {
expect(AppConfig.useDevConfig, !kReleaseMode);
expect(
AppConfig.baseUrl,
kReleaseMode ? ProdConfig.baseUrl : DevConfig.baseUrl,
);
});
}
+3 -31
View File
@@ -1,7 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/data/models/drama.dart';
import 'package:fruitshorts/core/formatting/localized_content.dart';
import 'package:fruitshorts/core/formatting/content_locale_guard.dart';
import 'package:fruitshorts/l10n/app_localizations_en.dart';
import 'package:fruitshorts/l10n/app_localizations_id.dart';
import 'package:fruitshorts/l10n/app_localizations_zh.dart';
@@ -77,7 +76,7 @@ void main() {
expect(localizedDramaSynopsis(zh, custom), 'Custom synopsis');
});
test('should_not_fall_back_to_chinese_when_indonesian_copy_is_missing', () {
test('should_fall_back_to_chinese_when_indonesian_copy_is_missing', () {
const drama = Drama(
id: 'missing-id-copy',
title: '古城',
@@ -87,35 +86,8 @@ void main() {
posterUrl: '/files/poster.png',
);
expect(drama.localizedTitle('id'), isEmpty);
expect(drama.localizedSynopsis('id'), isEmpty);
});
test('should_filter_incomplete_indonesian_content_from_visible_list', () {
const complete = Drama(
id: 'complete-id-copy',
title: 'Kota Lama',
synopsis: 'Kisah dari kota lama.',
titleLocales: {'id': 'Kota Lama'},
synopsisLocales: {'id': 'Kisah dari kota lama.'},
posterUrl: '/files/complete.png',
);
const incomplete = Drama(
id: 'incomplete-id-copy',
title: '古城',
synopsis: '中文简介',
titleLocales: {'zh': '古城'},
synopsisLocales: {'zh': '中文简介'},
posterUrl: '/files/incomplete.png',
);
expect(isDramaCompleteForLocale(complete, 'id'), isTrue);
expect(isDramaCompleteForLocale(incomplete, 'id'), isFalse);
expect(visibleDramasForLocale([complete, incomplete], 'id'), [complete]);
expect(visibleDramasForLocale([complete, incomplete], 'en'), [
complete,
incomplete,
]);
expect(drama.localizedTitle('id'), '古城');
expect(drama.localizedSynopsis('id'), '中文简介');
});
test('should_ignore_raw_catalog_text_when_drama_is_seeded', () {
+4 -4
View File
@@ -136,7 +136,7 @@ void _expectExpandedSynopsis(WidgetTester tester) {
final expanded = find.byKey(const Key('home-feed-synopsis-expanded'));
expect(
tester.widget<Text>(expanded).textSpan?.style?.color,
AppColors.overlaySecondary,
AppColors.homeFeedVideoCopy,
);
_expectNoShadows(tester);
_expectWatchGap(tester);
@@ -187,7 +187,7 @@ void main() {
matching: find.byType(Text),
),
);
expect(tag.style?.color, AppColors.overlaySecondary);
expect(tag.style?.color, AppColors.homeFeedVideoCopy);
});
testWidgets('should_skip_motion_when_system_disables_animations', (
@@ -343,7 +343,7 @@ void main() {
final synopsis = tester.widget<Text>(
find.byKey(const Key('home-feed-synopsis')),
);
expect(synopsis.style?.color, AppColors.overlaySecondary);
expect(synopsis.style?.color, AppColors.homeFeedVideoCopy);
expect(synopsis.style?.fontSize, 15);
expect(synopsis.maxLines, 2);
final episode = tester.widget<Text>(
@@ -353,7 +353,7 @@ void main() {
expect(episode.style?.fontSize, 14);
expect(episode.data, 'Episode 2 / 24');
final tag = tester.widget<Text>(find.text('Romance'));
expect(tag.style?.color, AppColors.overlaySecondary);
expect(tag.style?.color, AppColors.homeFeedVideoCopy);
expect(tag.style?.color, synopsis.style?.color);
expect(tag.style?.fontSize, 13);
final hotSearch = tester.widget<Text>(
+1 -1
View File
@@ -373,7 +373,7 @@ void main() {
expect(
tester.getRect(find.byKey(const Key('home-feed-action-rail'))).right,
closeTo(
tester.getRect(find.byKey(const Key('home-feed-gesture'))).right - 14,
tester.getRect(find.byKey(const Key('home-feed-gesture'))).right - 6,
0.1,
),
);
@@ -2,6 +2,7 @@
// 职责:验证共用简介的短文案、展开声明、内容更新与减少动画行为。
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.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/player/widgets/expandable_synopsis.dart';
@@ -54,6 +55,14 @@ Future<void> _pump(
}
void main() {
testWidgets('should_use_video_copy_color_when_player_does_not_override', (
tester,
) async {
await _pump(tester, synopsis: _shortCopy);
final synopsis = tester.widget<Text>(find.byKey(_collapsed));
expect(synopsis.style?.color, AppColors.homeFeedVideoCopy);
});
testWidgets('should_hide_toggle_when_synopsis_fits_two_lines', (
tester,
) async {
+1 -1
View File
@@ -1517,7 +1517,7 @@ void main() {
final style = line.style ?? const TextStyle();
expect(style.fontSize, 15);
expect(style.fontWeight, FontWeight.w400);
expect(style.color, AppColors.overlaySecondary);
expect(style.color, AppColors.homeFeedVideoCopy);
expect(style.shadows ?? <Shadow>[], isEmpty);
expect(line.textSpan!.toPlainText(), isNot(startsWith('Episode ')));
expect(line.textSpan!.toPlainText(), isNot(contains(' | ')));