593 lines
18 KiB
Dart
593 lines
18 KiB
Dart
|
|
part of 'danmaku_search_dialog.dart';
|
||
|
|
|
||
|
|
extension _DanmakuSearchDialogView on _DanmakuSearchDialogState {
|
||
|
|
Widget _slideFade(Widget child, Animation<double> animation) {
|
||
|
|
final curved = CurvedAnimation(
|
||
|
|
parent: animation,
|
||
|
|
curve: Curves.easeOutCubic,
|
||
|
|
reverseCurve: Curves.easeInCubic,
|
||
|
|
);
|
||
|
|
return FadeTransition(
|
||
|
|
opacity: curved,
|
||
|
|
child: SlideTransition(
|
||
|
|
position: Tween<Offset>(
|
||
|
|
begin: _transitionOffset,
|
||
|
|
end: Offset.zero,
|
||
|
|
).animate(curved),
|
||
|
|
child: child,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildHeader() {
|
||
|
|
return Padding(
|
||
|
|
padding: const EdgeInsets.fromLTRB(16, 12, 8, 10),
|
||
|
|
child: Row(
|
||
|
|
children: [
|
||
|
|
const Icon(Icons.search, color: Colors.white, size: 20),
|
||
|
|
const SizedBox(width: 8),
|
||
|
|
const Text(
|
||
|
|
'搜索弹幕源',
|
||
|
|
style: TextStyle(
|
||
|
|
color: Colors.white,
|
||
|
|
fontSize: 16,
|
||
|
|
fontWeight: FontWeight.w600,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
const Spacer(),
|
||
|
|
IconButton(
|
||
|
|
iconSize: 18,
|
||
|
|
visualDensity: VisualDensity.compact,
|
||
|
|
onPressed: _dismiss,
|
||
|
|
icon: const Icon(Icons.close, color: Colors.white70),
|
||
|
|
tooltip: '关闭',
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildSearchField() {
|
||
|
|
return SizedBox(
|
||
|
|
height: 44,
|
||
|
|
child: TextField(
|
||
|
|
controller: _queryCtrl,
|
||
|
|
focusNode: _focusNode,
|
||
|
|
autofocus: true,
|
||
|
|
enabled: !_busy,
|
||
|
|
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||
|
|
cursorColor: Colors.lightBlueAccent,
|
||
|
|
textInputAction: TextInputAction.search,
|
||
|
|
decoration: InputDecoration(
|
||
|
|
filled: true,
|
||
|
|
fillColor: Colors.white.withValues(alpha: 0.08),
|
||
|
|
hintText: '输入番剧名后按 Enter 搜索',
|
||
|
|
hintStyle: const TextStyle(color: Colors.white38, fontSize: 13),
|
||
|
|
prefixIcon: const Icon(Icons.search, color: Colors.white54, size: 18),
|
||
|
|
suffixIcon: IconButton(
|
||
|
|
onPressed: _busy ? null : _submit,
|
||
|
|
icon: const Icon(Icons.arrow_forward, size: 18),
|
||
|
|
color: Colors.white70,
|
||
|
|
tooltip: '搜索',
|
||
|
|
),
|
||
|
|
contentPadding: const EdgeInsets.symmetric(
|
||
|
|
horizontal: 12,
|
||
|
|
vertical: 0,
|
||
|
|
),
|
||
|
|
enabledBorder: OutlineInputBorder(
|
||
|
|
borderRadius: BorderRadius.circular(10),
|
||
|
|
borderSide: const BorderSide(color: _kDivider),
|
||
|
|
),
|
||
|
|
disabledBorder: OutlineInputBorder(
|
||
|
|
borderRadius: BorderRadius.circular(10),
|
||
|
|
borderSide: const BorderSide(color: _kDivider),
|
||
|
|
),
|
||
|
|
focusedBorder: OutlineInputBorder(
|
||
|
|
borderRadius: BorderRadius.circular(10),
|
||
|
|
borderSide: const BorderSide(color: _kAccent),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
onSubmitted: (_) => _submit(),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildBody() {
|
||
|
|
switch (_phase) {
|
||
|
|
case _Phase.idle:
|
||
|
|
return _buildStateBody(
|
||
|
|
key: const ValueKey('idle'),
|
||
|
|
icon: Icons.search,
|
||
|
|
title: '输入番剧名,按 Enter 搜索',
|
||
|
|
subtitle: '可手动选择具体集数',
|
||
|
|
showMessage: true,
|
||
|
|
);
|
||
|
|
case _Phase.searching:
|
||
|
|
return _buildLoadingBody(
|
||
|
|
key: const ValueKey('searching'),
|
||
|
|
text: '正在搜索…',
|
||
|
|
);
|
||
|
|
case _Phase.results:
|
||
|
|
return _buildResultsBody(
|
||
|
|
key: const ValueKey('results'),
|
||
|
|
disabled: false,
|
||
|
|
);
|
||
|
|
case _Phase.empty:
|
||
|
|
return _buildStateBody(
|
||
|
|
key: const ValueKey('empty'),
|
||
|
|
icon: Icons.search_off,
|
||
|
|
title: '未找到匹配的弹幕源',
|
||
|
|
subtitle: '换个剧名试试',
|
||
|
|
);
|
||
|
|
case _Phase.error:
|
||
|
|
return _buildStateBody(
|
||
|
|
key: const ValueKey('error'),
|
||
|
|
icon: Icons.error_outline,
|
||
|
|
title: _messageText ?? '搜索失败',
|
||
|
|
subtitle: '请检查弹幕服务地址',
|
||
|
|
);
|
||
|
|
case _Phase.episodesLoading:
|
||
|
|
return _buildEpisodesLoadingBody();
|
||
|
|
case _Phase.episodes:
|
||
|
|
return _buildEpisodesBody(
|
||
|
|
key: const ValueKey('episodes'),
|
||
|
|
disabled: false,
|
||
|
|
);
|
||
|
|
case _Phase.applying:
|
||
|
|
return _buildApplyingBody();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildStateBody({
|
||
|
|
required Key key,
|
||
|
|
required IconData icon,
|
||
|
|
required String title,
|
||
|
|
String? subtitle,
|
||
|
|
bool showMessage = false,
|
||
|
|
}) {
|
||
|
|
return Center(
|
||
|
|
key: key,
|
||
|
|
child: ConstrainedBox(
|
||
|
|
constraints: const BoxConstraints(maxWidth: 280),
|
||
|
|
child: Column(
|
||
|
|
mainAxisSize: MainAxisSize.min,
|
||
|
|
children: [
|
||
|
|
Icon(icon, color: Colors.white24, size: 32),
|
||
|
|
const SizedBox(height: 12),
|
||
|
|
Text(
|
||
|
|
title,
|
||
|
|
textAlign: TextAlign.center,
|
||
|
|
style: const TextStyle(
|
||
|
|
color: Colors.white70,
|
||
|
|
fontSize: 13,
|
||
|
|
fontWeight: FontWeight.w500,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
if (subtitle != null) ...[
|
||
|
|
const SizedBox(height: 6),
|
||
|
|
Text(
|
||
|
|
subtitle,
|
||
|
|
textAlign: TextAlign.center,
|
||
|
|
style: const TextStyle(color: Colors.white54, fontSize: 12),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
if (showMessage && _messageText != null) ...[
|
||
|
|
const SizedBox(height: 16),
|
||
|
|
_buildMessageBanner(),
|
||
|
|
],
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildLoadingBody({required Key key, required String text}) {
|
||
|
|
return Center(
|
||
|
|
key: key,
|
||
|
|
child: Column(
|
||
|
|
mainAxisSize: MainAxisSize.min,
|
||
|
|
children: [
|
||
|
|
const AppLoadingRing(size: 22, color: Colors.white),
|
||
|
|
const SizedBox(height: 12),
|
||
|
|
Text(
|
||
|
|
text,
|
||
|
|
style: const TextStyle(color: Colors.white54, fontSize: 12),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildResultsBody({required Key key, required bool disabled}) {
|
||
|
|
return Column(
|
||
|
|
key: key,
|
||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||
|
|
children: [
|
||
|
|
Text(
|
||
|
|
'搜索结果 ${_results.length} 条',
|
||
|
|
style: const TextStyle(
|
||
|
|
color: Colors.white70,
|
||
|
|
fontSize: 14,
|
||
|
|
fontWeight: FontWeight.w600,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
if (_messageText != null) ...[
|
||
|
|
const SizedBox(height: 12),
|
||
|
|
_buildMessageBanner(),
|
||
|
|
],
|
||
|
|
const SizedBox(height: 12),
|
||
|
|
Expanded(
|
||
|
|
child: ListView.separated(
|
||
|
|
padding: EdgeInsets.zero,
|
||
|
|
itemCount: _results.length,
|
||
|
|
separatorBuilder: (_, _) => const SizedBox(height: 8),
|
||
|
|
itemBuilder: (_, i) => _buildResultTile(_results[i], disabled),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildEpisodesLoadingBody() {
|
||
|
|
return Column(
|
||
|
|
key: const ValueKey('episodes-loading'),
|
||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||
|
|
children: [
|
||
|
|
_buildEpisodesHeader(disabled: false),
|
||
|
|
const SizedBox(height: 16),
|
||
|
|
const Expanded(
|
||
|
|
child: Center(
|
||
|
|
child: Column(
|
||
|
|
mainAxisSize: MainAxisSize.min,
|
||
|
|
children: [
|
||
|
|
AppLoadingRing(size: 22, color: Colors.white),
|
||
|
|
SizedBox(height: 12),
|
||
|
|
Text(
|
||
|
|
'正在加载分集…',
|
||
|
|
style: TextStyle(color: Colors.white54, fontSize: 12),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildEpisodesBody({
|
||
|
|
required Key key,
|
||
|
|
required bool disabled,
|
||
|
|
bool useScrollController = true,
|
||
|
|
}) {
|
||
|
|
return Column(
|
||
|
|
key: key,
|
||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||
|
|
children: [
|
||
|
|
_buildEpisodesHeader(disabled: disabled),
|
||
|
|
if (_messageText != null) ...[
|
||
|
|
const SizedBox(height: 12),
|
||
|
|
_buildMessageBanner(),
|
||
|
|
],
|
||
|
|
const SizedBox(height: 12),
|
||
|
|
Expanded(
|
||
|
|
child: ListView.separated(
|
||
|
|
controller: useScrollController ? _episodesScrollCtrl : null,
|
||
|
|
padding: EdgeInsets.zero,
|
||
|
|
itemCount: _episodes.length,
|
||
|
|
separatorBuilder: (_, _) => const SizedBox(height: 8),
|
||
|
|
itemBuilder: (_, i) => _buildEpisodeTile(_episodes[i], i, disabled),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildApplyingBody() {
|
||
|
|
final base = _activeMatch != null && _episodes.isNotEmpty
|
||
|
|
? _buildEpisodesBody(
|
||
|
|
key: const ValueKey('applying-episodes'),
|
||
|
|
disabled: true,
|
||
|
|
useScrollController: false,
|
||
|
|
)
|
||
|
|
: _buildResultsBody(
|
||
|
|
key: const ValueKey('applying-results'),
|
||
|
|
disabled: true,
|
||
|
|
);
|
||
|
|
|
||
|
|
return Stack(
|
||
|
|
key: const ValueKey('applying'),
|
||
|
|
fit: StackFit.expand,
|
||
|
|
children: [
|
||
|
|
base,
|
||
|
|
Positioned.fill(
|
||
|
|
child: ColoredBox(color: Colors.black.withValues(alpha: 0.18)),
|
||
|
|
),
|
||
|
|
const Center(
|
||
|
|
child: Column(
|
||
|
|
mainAxisSize: MainAxisSize.min,
|
||
|
|
children: [
|
||
|
|
AppLoadingRing(size: 22, color: Colors.white),
|
||
|
|
SizedBox(height: 12),
|
||
|
|
Text(
|
||
|
|
'正在加载弹幕…',
|
||
|
|
style: TextStyle(color: Colors.white70, fontSize: 12),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildEpisodesHeader({required bool disabled}) {
|
||
|
|
return Row(
|
||
|
|
children: [
|
||
|
|
IconButton(
|
||
|
|
onPressed: disabled ? null : _backToResults,
|
||
|
|
iconSize: 18,
|
||
|
|
visualDensity: VisualDensity.compact,
|
||
|
|
icon: const Icon(Icons.arrow_back, color: Colors.white70),
|
||
|
|
tooltip: '返回',
|
||
|
|
),
|
||
|
|
const SizedBox(width: 4),
|
||
|
|
Expanded(
|
||
|
|
child: Column(
|
||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||
|
|
children: [
|
||
|
|
Text(
|
||
|
|
_activeMatch?.animeTitle ?? '选择分集',
|
||
|
|
maxLines: 1,
|
||
|
|
overflow: TextOverflow.ellipsis,
|
||
|
|
style: const TextStyle(
|
||
|
|
color: Colors.white,
|
||
|
|
fontSize: 14,
|
||
|
|
fontWeight: FontWeight.w600,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
const SizedBox(height: 2),
|
||
|
|
const Text(
|
||
|
|
'选择要加载的集数',
|
||
|
|
style: TextStyle(color: Colors.white54, fontSize: 12),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildMessageBanner() {
|
||
|
|
final color = _messageIsError ? Colors.redAccent : _kAccent;
|
||
|
|
final icon = _messageIsError ? Icons.error_outline : Icons.info_outline;
|
||
|
|
|
||
|
|
return Container(
|
||
|
|
width: double.infinity,
|
||
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||
|
|
decoration: BoxDecoration(
|
||
|
|
color: color.withValues(alpha: 0.12),
|
||
|
|
borderRadius: BorderRadius.circular(10),
|
||
|
|
border: Border.all(color: color.withValues(alpha: 0.35)),
|
||
|
|
),
|
||
|
|
child: Row(
|
||
|
|
children: [
|
||
|
|
Icon(icon, size: 16, color: color),
|
||
|
|
const SizedBox(width: 8),
|
||
|
|
Expanded(
|
||
|
|
child: Text(
|
||
|
|
_messageText ?? '',
|
||
|
|
style: const TextStyle(color: Colors.white70, fontSize: 12),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildResultTile(DanmakuMatchCandidate match, bool disabled) {
|
||
|
|
final highlighted =
|
||
|
|
_flashEpisodeId == match.episodeId &&
|
||
|
|
_flashSource?.id == match.source.id;
|
||
|
|
final canDrill = match.animeId > 0;
|
||
|
|
final subtitle = match.episodeTitle.isNotEmpty
|
||
|
|
? match.episodeTitle
|
||
|
|
: canDrill
|
||
|
|
? '进入分集列表后选择具体集数'
|
||
|
|
: '直接加载当前搜索结果';
|
||
|
|
final showTargetHint = widget.targetEpisode != null && canDrill;
|
||
|
|
|
||
|
|
return InkWell(
|
||
|
|
onTap: disabled
|
||
|
|
? null
|
||
|
|
: () {
|
||
|
|
if (canDrill) {
|
||
|
|
_openAnime(match);
|
||
|
|
} else {
|
||
|
|
_applyDirect(match);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
borderRadius: BorderRadius.circular(10),
|
||
|
|
child: AnimatedContainer(
|
||
|
|
duration: const Duration(milliseconds: 100),
|
||
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||
|
|
decoration: BoxDecoration(
|
||
|
|
color: highlighted
|
||
|
|
? Colors.white12
|
||
|
|
: Colors.white.withValues(alpha: 0.04),
|
||
|
|
borderRadius: BorderRadius.circular(10),
|
||
|
|
border: Border.all(
|
||
|
|
color: highlighted
|
||
|
|
? Colors.lightBlueAccent.withValues(alpha: 0.7)
|
||
|
|
: Colors.white.withValues(alpha: 0.08),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
child: Row(
|
||
|
|
children: [
|
||
|
|
Expanded(
|
||
|
|
child: Column(
|
||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||
|
|
children: [
|
||
|
|
Text(
|
||
|
|
match.animeTitle,
|
||
|
|
maxLines: 1,
|
||
|
|
overflow: TextOverflow.ellipsis,
|
||
|
|
style: const TextStyle(
|
||
|
|
color: Colors.white,
|
||
|
|
fontSize: 13,
|
||
|
|
fontWeight: FontWeight.w600,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
const SizedBox(height: 4),
|
||
|
|
Text(
|
||
|
|
subtitle,
|
||
|
|
maxLines: 2,
|
||
|
|
overflow: TextOverflow.ellipsis,
|
||
|
|
style: const TextStyle(
|
||
|
|
color: Colors.white54,
|
||
|
|
fontSize: 12,
|
||
|
|
height: 1.35,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
const SizedBox(height: 4),
|
||
|
|
Text(
|
||
|
|
match.source.displayName,
|
||
|
|
maxLines: 1,
|
||
|
|
overflow: TextOverflow.ellipsis,
|
||
|
|
style: const TextStyle(color: Colors.white38, fontSize: 11),
|
||
|
|
),
|
||
|
|
if (showTargetHint) ...[
|
||
|
|
const SizedBox(height: 4),
|
||
|
|
Row(
|
||
|
|
children: [
|
||
|
|
const Icon(
|
||
|
|
Icons.adjust,
|
||
|
|
size: 12,
|
||
|
|
color: Colors.white38,
|
||
|
|
),
|
||
|
|
const SizedBox(width: 4),
|
||
|
|
Expanded(
|
||
|
|
child: Text(
|
||
|
|
'目标:${formatEpisodeNumber(widget.targetEpisode)}(点击选择)',
|
||
|
|
maxLines: 1,
|
||
|
|
overflow: TextOverflow.ellipsis,
|
||
|
|
style: const TextStyle(
|
||
|
|
color: Colors.white38,
|
||
|
|
fontSize: 11,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
],
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
const SizedBox(width: 12),
|
||
|
|
_buildResultAffordance(canDrill: canDrill),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildResultAffordance({required bool canDrill}) {
|
||
|
|
if (canDrill) {
|
||
|
|
return const Row(
|
||
|
|
mainAxisSize: MainAxisSize.min,
|
||
|
|
children: [
|
||
|
|
Text('选择集数', style: TextStyle(color: Colors.white54, fontSize: 11)),
|
||
|
|
SizedBox(width: 2),
|
||
|
|
Icon(Icons.chevron_right, size: 16, color: Colors.white54),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return Container(
|
||
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||
|
|
decoration: BoxDecoration(
|
||
|
|
color: _kAccent.withValues(alpha: 0.16),
|
||
|
|
borderRadius: BorderRadius.circular(999),
|
||
|
|
border: Border.all(color: _kAccent.withValues(alpha: 0.4)),
|
||
|
|
),
|
||
|
|
child: const Text(
|
||
|
|
'直接加载',
|
||
|
|
style: TextStyle(
|
||
|
|
color: Colors.white,
|
||
|
|
fontSize: 11,
|
||
|
|
fontWeight: FontWeight.w600,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildEpisodeTile(
|
||
|
|
DanmakuBangumiEpisode episode,
|
||
|
|
int index,
|
||
|
|
bool disabled,
|
||
|
|
) {
|
||
|
|
final highlighted = _flashEpisodeId == episode.episodeId;
|
||
|
|
final epNum = episode.episodeNumber.trim();
|
||
|
|
final episodeLabel = epNum.isEmpty
|
||
|
|
? formatEpisodeNumber(index + 1)!
|
||
|
|
: '第$epNum集';
|
||
|
|
|
||
|
|
return InkWell(
|
||
|
|
onTap: disabled || _activeMatch == null
|
||
|
|
? null
|
||
|
|
: () => _applyEpisode(_activeMatch!, episode),
|
||
|
|
borderRadius: BorderRadius.circular(10),
|
||
|
|
child: AnimatedContainer(
|
||
|
|
duration: const Duration(milliseconds: 100),
|
||
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||
|
|
decoration: BoxDecoration(
|
||
|
|
color: highlighted
|
||
|
|
? Colors.white12
|
||
|
|
: Colors.white.withValues(alpha: 0.04),
|
||
|
|
borderRadius: BorderRadius.circular(10),
|
||
|
|
border: Border.all(
|
||
|
|
color: highlighted
|
||
|
|
? Colors.lightBlueAccent.withValues(alpha: 0.7)
|
||
|
|
: Colors.white.withValues(alpha: 0.08),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
child: Row(
|
||
|
|
children: [
|
||
|
|
Expanded(
|
||
|
|
child: Column(
|
||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||
|
|
children: [
|
||
|
|
Text(
|
||
|
|
episodeLabel,
|
||
|
|
style: const TextStyle(
|
||
|
|
color: Colors.white,
|
||
|
|
fontSize: 13,
|
||
|
|
fontWeight: FontWeight.w600,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
if (episode.episodeTitle.isNotEmpty) ...[
|
||
|
|
const SizedBox(height: 4),
|
||
|
|
Text(
|
||
|
|
episode.episodeTitle,
|
||
|
|
maxLines: 2,
|
||
|
|
overflow: TextOverflow.ellipsis,
|
||
|
|
style: const TextStyle(
|
||
|
|
color: Colors.white54,
|
||
|
|
fontSize: 12,
|
||
|
|
height: 1.35,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
const SizedBox(width: 12),
|
||
|
|
const Text(
|
||
|
|
'点击加载',
|
||
|
|
style: TextStyle(color: Colors.white54, fontSize: 11),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|