- Drama 新增 coverLandscape,WatchProgress 新增 dramaCoverLandscape - 继续观看横幅优先使用横版封面,缺省回退竖版 - 底部导航栏图标与文字间距由 5dp 收紧至 1dp
This commit is contained in:
1 parent
3590cd0c55
commit
26321e3348
8 files changed
+64
-12
No files matched your search
@@ -97,6 +97,7 @@ class Drama {
|
|||||||
required this.title,
|
required this.title,
|
||||||
required this.synopsis,
|
required this.synopsis,
|
||||||
required this.posterUrl,
|
required this.posterUrl,
|
||||||
|
this.coverLandscape = '',
|
||||||
this.episodes = const [],
|
this.episodes = const [],
|
||||||
this.titleLocales = const {},
|
this.titleLocales = const {},
|
||||||
this.synopsisLocales = const {},
|
this.synopsisLocales = const {},
|
||||||
@@ -133,6 +134,9 @@ class Drama {
|
|||||||
final String tagCode;
|
final String tagCode;
|
||||||
final String tagColor;
|
final String tagColor;
|
||||||
final String posterUrl;
|
final String posterUrl;
|
||||||
|
|
||||||
|
/// 横版封面,用于继续观看横幅等横向展示位;为空时回退竖版 `posterUrl`。
|
||||||
|
final String coverLandscape;
|
||||||
final List<Episode> episodes;
|
final List<Episode> episodes;
|
||||||
final String category;
|
final String category;
|
||||||
final Map<String, String> categoryLocales;
|
final Map<String, String> categoryLocales;
|
||||||
@@ -179,6 +183,7 @@ class Drama {
|
|||||||
tagCode: (json['tagCode'] as String? ?? '').trim(),
|
tagCode: (json['tagCode'] as String? ?? '').trim(),
|
||||||
tagColor: json['tagColor'] as String? ?? '',
|
tagColor: json['tagColor'] as String? ?? '',
|
||||||
posterUrl: json['cover'] as String? ?? '',
|
posterUrl: json['cover'] as String? ?? '',
|
||||||
|
coverLandscape: json['coverLandscape'] as String? ?? '',
|
||||||
episodes: preview is Map<String, dynamic>
|
episodes: preview is Map<String, dynamic>
|
||||||
? [Episode.fromJson(preview)]
|
? [Episode.fromJson(preview)]
|
||||||
: const [],
|
: const [],
|
||||||
@@ -212,6 +217,7 @@ class Drama {
|
|||||||
tagCode: (json['tagCode'] as String? ?? '').trim(),
|
tagCode: (json['tagCode'] as String? ?? '').trim(),
|
||||||
tagColor: json['tagColor'] as String? ?? '',
|
tagColor: json['tagColor'] as String? ?? '',
|
||||||
posterUrl: json['cover'] as String? ?? '',
|
posterUrl: json['cover'] as String? ?? '',
|
||||||
|
coverLandscape: json['coverLandscape'] as String? ?? '',
|
||||||
category: text.category,
|
category: text.category,
|
||||||
categoryLocales: text.categoryLocales,
|
categoryLocales: text.categoryLocales,
|
||||||
types: text.types,
|
types: text.types,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class WatchProgress {
|
|||||||
this.dramaTitle,
|
this.dramaTitle,
|
||||||
this.titleLocales = const {},
|
this.titleLocales = const {},
|
||||||
this.dramaCover,
|
this.dramaCover,
|
||||||
|
this.dramaCoverLandscape,
|
||||||
this.episodeNumber,
|
this.episodeNumber,
|
||||||
this.totalEpisodes,
|
this.totalEpisodes,
|
||||||
this.finished = false,
|
this.finished = false,
|
||||||
@@ -29,6 +30,9 @@ class WatchProgress {
|
|||||||
final String? dramaTitle;
|
final String? dramaTitle;
|
||||||
final Map<String, String> titleLocales;
|
final Map<String, String> titleLocales;
|
||||||
final String? dramaCover;
|
final String? dramaCover;
|
||||||
|
|
||||||
|
/// 横版封面,用于继续观看横幅等横向位;为空时回退 [dramaCover]。
|
||||||
|
final String? dramaCoverLandscape;
|
||||||
final int? episodeNumber;
|
final int? episodeNumber;
|
||||||
final int? totalEpisodes;
|
final int? totalEpisodes;
|
||||||
final Duration position;
|
final Duration position;
|
||||||
@@ -60,6 +64,7 @@ class WatchProgress {
|
|||||||
dramaTitle: json['dramaTitle']?.toString(),
|
dramaTitle: json['dramaTitle']?.toString(),
|
||||||
titleLocales: localeMap(json['titleLocales']),
|
titleLocales: localeMap(json['titleLocales']),
|
||||||
dramaCover: json['dramaCover']?.toString(),
|
dramaCover: json['dramaCover']?.toString(),
|
||||||
|
dramaCoverLandscape: json['dramaCoverLandscape']?.toString(),
|
||||||
episodeNumber: json['episodeNumber'] == null
|
episodeNumber: json['episodeNumber'] == null
|
||||||
? null
|
? null
|
||||||
: jsonToInt(json['episodeNumber']),
|
: jsonToInt(json['episodeNumber']),
|
||||||
@@ -83,6 +88,7 @@ class WatchProgress {
|
|||||||
if (dramaTitle != null) 'dramaTitle': dramaTitle,
|
if (dramaTitle != null) 'dramaTitle': dramaTitle,
|
||||||
if (titleLocales.isNotEmpty) 'titleLocales': titleLocales,
|
if (titleLocales.isNotEmpty) 'titleLocales': titleLocales,
|
||||||
if (dramaCover != null) 'dramaCover': dramaCover,
|
if (dramaCover != null) 'dramaCover': dramaCover,
|
||||||
|
if (dramaCoverLandscape != null) 'dramaCoverLandscape': dramaCoverLandscape,
|
||||||
if (episodeNumber != null) 'episodeNumber': episodeNumber,
|
if (episodeNumber != null) 'episodeNumber': episodeNumber,
|
||||||
if (totalEpisodes != null) 'totalEpisodes': totalEpisodes,
|
if (totalEpisodes != null) 'totalEpisodes': totalEpisodes,
|
||||||
'positionMs': position.inMilliseconds,
|
'positionMs': position.inMilliseconds,
|
||||||
|
|||||||
@@ -46,6 +46,14 @@ class WatchRecord {
|
|||||||
return pageCover.isNotEmpty ? pageCover : drama?.posterUrl ?? '';
|
return pageCover.isNotEmpty ? pageCover : drama?.posterUrl ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 横向展示位(继续观看横幅)优先用横版封面,缺省回退竖版。
|
||||||
|
String get displayLandscapeCover {
|
||||||
|
final pageLandscape = progress.dramaCoverLandscape?.trim() ?? '';
|
||||||
|
if (pageLandscape.isNotEmpty) return pageLandscape;
|
||||||
|
final landscape = drama?.coverLandscape.trim() ?? '';
|
||||||
|
return landscape.isNotEmpty ? landscape : displayCover;
|
||||||
|
}
|
||||||
|
|
||||||
/// 分页接口已带展示字段时无需等待详情加载。
|
/// 分页接口已带展示字段时无需等待详情加载。
|
||||||
bool get hasDisplayMetadata =>
|
bool get hasDisplayMetadata =>
|
||||||
displayTitle.isNotEmpty ||
|
displayTitle.isNotEmpty ||
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ abstract final class AppDimens {
|
|||||||
static const double previewSectionTitle = 14;
|
static const double previewSectionTitle = 14;
|
||||||
static const double chipFont = 10;
|
static const double chipFont = 10;
|
||||||
static const double navLabel = 13;
|
static const double navLabel = 13;
|
||||||
static const double navLabelGap = 5;
|
static const double navLabelGap = 1;
|
||||||
|
|
||||||
static const double buttonHeight = 48;
|
static const double buttonHeight = 48;
|
||||||
static const double primaryButtonHeight = 52;
|
static const double primaryButtonHeight = 52;
|
||||||
|
|||||||
@@ -33,13 +33,12 @@ class ContinueWatchingBanner extends StatelessWidget {
|
|||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: AppDimens.followingBannerHeight,
|
height: AppDimens.followingBannerHeight,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius:
|
borderRadius: BorderRadius.circular(AppDimens.followingBannerRadius),
|
||||||
BorderRadius.circular(AppDimens.followingBannerRadius),
|
|
||||||
child: Stack(
|
child: Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
RemoteImage(
|
RemoteImage(
|
||||||
record.displayCover,
|
record.displayLandscapeCover,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
showFallbackIcon: true,
|
showFallbackIcon: true,
|
||||||
fallbackColor: AppColors.contentSurface,
|
fallbackColor: AppColors.contentSurface,
|
||||||
@@ -82,8 +81,7 @@ class ContinueWatchingBanner extends StatelessWidget {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: AppColors.bannerTitleWarm,
|
color: AppColors.bannerTitleWarm,
|
||||||
fontSize:
|
fontSize: AppDimens.followingBannerTitleFont,
|
||||||
AppDimens.followingBannerTitleFont,
|
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -143,8 +141,7 @@ class _ContinueButton extends StatelessWidget {
|
|||||||
height: AppDimens.followingBannerButtonHeight,
|
height: AppDimens.followingBannerButtonHeight,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal:
|
horizontal: AppDimens.followingBannerButtonHorizontalPadding,
|
||||||
AppDimens.followingBannerButtonHorizontalPadding,
|
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -188,9 +185,7 @@ class _ProgressBar extends StatelessWidget {
|
|||||||
height: AppDimens.followingBannerProgressHeight,
|
height: AppDimens.followingBannerProgressHeight,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: DecoratedBox(
|
child: DecoratedBox(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(color: Color(0x66FFFFFF)),
|
||||||
color: Color(0x66FFFFFF),
|
|
||||||
),
|
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: FractionallySizedBox(
|
child: FractionallySizedBox(
|
||||||
|
|||||||
@@ -46,6 +46,23 @@ void main() {
|
|||||||
expect(drama.episodeCount, 12);
|
expect(drama.episodeCount, 12);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should_parse_landscape_cover_for_horizontal_slots', () {
|
||||||
|
final card = Drama.fromCardJson(const {
|
||||||
|
'id': 5,
|
||||||
|
'title': '闪婚后',
|
||||||
|
'cover': '/files/cover.jpg',
|
||||||
|
'coverLandscape': '/files/cover-wide.jpg',
|
||||||
|
});
|
||||||
|
final legacy = Drama.fromDetailJson(const {
|
||||||
|
'id': 6,
|
||||||
|
'title': '替嫁新娘',
|
||||||
|
'cover': '/files/cover-2.jpg',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(card.coverLandscape, '/files/cover-wide.jpg');
|
||||||
|
expect(legacy.coverLandscape, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
test('should_count_shipped_episodes_when_detail_response_is_parsed', () {
|
test('should_count_shipped_episodes_when_detail_response_is_parsed', () {
|
||||||
final drama = Drama.fromDetailJson(const {
|
final drama = Drama.fromDetailJson(const {
|
||||||
'id': 5,
|
'id': 5,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ void main() {
|
|||||||
expect(AppDimens.sectionTitle, 16);
|
expect(AppDimens.sectionTitle, 16);
|
||||||
expect(AppDimens.pageTitle, 20);
|
expect(AppDimens.pageTitle, 20);
|
||||||
expect(AppDimens.navLabel, 13);
|
expect(AppDimens.navLabel, 13);
|
||||||
expect(AppDimens.navLabelGap, 5);
|
expect(AppDimens.navLabelGap, 1);
|
||||||
expect(AppDimens.navIcon, 30);
|
expect(AppDimens.navIcon, 30);
|
||||||
expect(AppDimens.buttonHeight, 48);
|
expect(AppDimens.buttonHeight, 48);
|
||||||
expect(AppDimens.primaryButtonHeight, 52);
|
expect(AppDimens.primaryButtonHeight, 52);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:fruitshorts/core/data/models/feed_page.dart';
|
|||||||
import 'package:fruitshorts/core/data/models/home_feed.dart';
|
import 'package:fruitshorts/core/data/models/home_feed.dart';
|
||||||
import 'package:fruitshorts/core/data/models/page_result.dart';
|
import 'package:fruitshorts/core/data/models/page_result.dart';
|
||||||
import 'package:fruitshorts/core/data/models/watch_progress.dart';
|
import 'package:fruitshorts/core/data/models/watch_progress.dart';
|
||||||
|
import 'package:fruitshorts/core/data/models/watch_record.dart';
|
||||||
import 'package:fruitshorts/core/data/repositories/catalog_repository.dart';
|
import 'package:fruitshorts/core/data/repositories/catalog_repository.dart';
|
||||||
import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart';
|
import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart';
|
||||||
import 'package:fruitshorts/core/data/remote/api_client.dart';
|
import 'package:fruitshorts/core/data/remote/api_client.dart';
|
||||||
@@ -278,6 +279,25 @@ void main() {
|
|||||||
|
|
||||||
expect(controller.error, '账号不存在,请先注册');
|
expect(controller.error, '账号不存在,请先注册');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should_prefer_page_landscape_cover_and_fall_back_to_portrait', () {
|
||||||
|
WatchRecord record(String? landscape) => WatchRecord(
|
||||||
|
progress: WatchProgress(
|
||||||
|
dramaId: '1',
|
||||||
|
episodeId: 'episode-1',
|
||||||
|
position: const Duration(seconds: 12),
|
||||||
|
duration: const Duration(seconds: 60),
|
||||||
|
dramaCover: '/files/poster.webp',
|
||||||
|
dramaCoverLandscape: landscape,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
record('/files/wide.webp').displayLandscapeCover,
|
||||||
|
'/files/wide.webp',
|
||||||
|
);
|
||||||
|
expect(record(null).displayLandscapeCover, '/files/poster.webp');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class ControlledWatchHistory implements WatchHistoryRepository {
|
class ControlledWatchHistory implements WatchHistoryRepository {
|
||||||
|
|||||||
Reference in new issue
Block a user