show audio playlist parts

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-10-21 11:13:56 +08:00
parent cf86bb7e13
commit 9808f50816
3 changed files with 197 additions and 94 deletions

View File

@@ -583,13 +583,13 @@ class AudioController extends GetxController
return false; return false;
} }
void playIndex(int index) { void playIndex(int index, {List<Int64>? subId}) {
if (index == this.index) return; if (index == this.index && subId == null) return;
this.index = index; this.index = index;
final audioItem = playlist![index]; final audioItem = playlist![index];
final item = audioItem.item; final item = audioItem.item;
oid = item.oid; oid = item.oid;
subId = item.subId; this.subId = subId ?? item.subId;
itemType = item.itemType; itemType = item.itemType;
_queryPlayUrl().then((res) { _queryPlayUrl().then((res) {
if (res) { if (res) {

View File

@@ -147,7 +147,8 @@ class _AudioPageState extends State<AudioPage> {
maxWidth: min(640, context.mediaQueryShortestSide), maxWidth: min(640, context.mediaQueryShortestSide),
), ),
builder: (context) { builder: (context) {
final colorScheme = ColorScheme.of(context); final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return FractionallySizedBox( return FractionallySizedBox(
heightFactor: !context.mediaQuerySize.isPortrait && Utils.isMobile heightFactor: !context.mediaQuerySize.isPortrait && Utils.isMobile
? 1.0 ? 1.0
@@ -177,101 +178,200 @@ class _AudioPageState extends State<AudioPage> {
Expanded( Expanded(
child: Material( child: Material(
type: MaterialType.transparency, type: MaterialType.transparency,
child: refreshIndicator( child: Theme(
onRefresh: () => _controller.loadPrev(context), data: theme.copyWith(
child: CustomScrollView( dividerColor: Colors.transparent,
controller: scrollController, ),
physics: _controller.reachStart child: refreshIndicator(
? const ClampingScrollPhysics() onRefresh: () => _controller.loadPrev(context),
: const AlwaysScrollableScrollPhysics( child: CustomScrollView(
parent: ClampingScrollPhysics(), controller: scrollController,
physics: _controller.reachStart
? const ClampingScrollPhysics()
: const AlwaysScrollableScrollPhysics(
parent: ClampingScrollPhysics(),
),
slivers: [
SliverPadding(
padding: EdgeInsets.only(
bottom:
MediaQuery.paddingOf(context).bottom + 100,
), ),
slivers: [ sliver: SliverList.builder(
SliverPadding( itemCount: playlist.length,
padding: EdgeInsets.only( itemBuilder: (_, index) {
bottom: if (index == playlist.length - 1) {
MediaQuery.paddingOf(context).bottom + 100, _controller.loadNext(context);
), }
sliver: SliverList.builder( final isCurr = index == _controller.index;
itemCount: playlist.length, final item = playlist[index];
itemBuilder: (_, index) { if (item.parts.length > 1) {
if (index == playlist.length - 1) { final subId = _controller.subId.firstOrNull;
_controller.loadNext(context); return ExpansionTile(
} dense: true,
final isCurr = index == _controller.index; minTileHeight: 45,
final item = playlist[index]; initiallyExpanded: isCurr,
return ListTile( collapsedIconColor: isCurr
dense: true, ? colorScheme.primary
minTileHeight: 45, : null,
onTap: () { iconColor: isCurr
Get.back(); ? null
if (!isCurr) { : colorScheme.onSurfaceVariant,
_controller.playIndex(index); controlAffinity:
} ListTileControlAffinity.leading,
}, title: Text(
title: Text.rich( item.arc.title,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: isCurr style: isCurr
? TextStyle( ? TextStyle(
height: 1, fontSize: 14,
fontSize: 14, color: colorScheme.primary,
color: colorScheme.primary, fontWeight: FontWeight.bold,
fontWeight: FontWeight.bold, )
) : const TextStyle(fontSize: 14),
: const TextStyle( ),
height: 1, trailing: isCurr
fontSize: 14, ? null
), : iconButton(
strutStyle: const StrutStyle( icon: const Icon(Icons.clear),
height: 1, onPressed: () {
leading: 0, if (index <
fontSize: 14, _controller.index!) {
), _controller.index -= 1;
TextSpan( }
children: [ _controller.playlist!.removeAt(
if (isCurr) ...[ index,
WidgetSpan( );
alignment: (context as Element)
PlaceholderAlignment.bottom, .markNeedsBuild();
child: Image.asset( },
'assets/images/live.gif', iconColor: colorScheme.outline,
width: 16, size: 28,
height: 16, iconSize: 18,
color: colorScheme.primary, ),
children: item.parts.map((e) {
final isCurr = e.subId == subId;
return ListTile(
dense: true,
minTileHeight: 45,
contentPadding:
const EdgeInsetsDirectional.only(
start: 56.0,
end: 24.0,
),
onTap: () {
Get.back();
if (!isCurr) {
_controller.playIndex(
index,
subId: [e.subId],
);
}
},
title: Text.rich(
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: isCurr
? TextStyle(
fontSize: 14,
color: colorScheme.primary,
fontWeight: FontWeight.bold,
)
: TextStyle(
fontSize: 14,
color: colorScheme
.onSurfaceVariant,
),
TextSpan(
children: [
if (isCurr) ...[
WidgetSpan(
alignment:
PlaceholderAlignment
.bottom,
child: Image.asset(
'assets/images/live.gif',
width: 16,
height: 16,
color:
colorScheme.primary,
),
),
const TextSpan(text: ' '),
],
TextSpan(text: e.title),
],
), ),
), ),
const TextSpan(text: ' '), );
}).toList(),
);
}
return ListTile(
dense: true,
minTileHeight: 45,
onTap: () {
Get.back();
if (!isCurr) {
_controller.playIndex(index);
}
},
title: Text.rich(
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: isCurr
? TextStyle(
fontSize: 14,
color: colorScheme.primary,
fontWeight: FontWeight.bold,
)
: const TextStyle(fontSize: 14),
TextSpan(
children: [
if (isCurr) ...[
WidgetSpan(
alignment:
PlaceholderAlignment.bottom,
child: Image.asset(
'assets/images/live.gif',
width: 16,
height: 16,
color: colorScheme.primary,
),
),
const TextSpan(text: ' '),
],
TextSpan(
text: item.arc.title,
),
], ],
TextSpan( ),
text: item.arc.title,
),
],
), ),
), trailing: isCurr
trailing: isCurr ? null
? null : iconButton(
: iconButton( icon: const Icon(Icons.clear),
icon: const Icon(Icons.clear), onPressed: () {
onPressed: () { if (index < _controller.index!) {
if (index < _controller.index!) { _controller.index -= 1;
_controller.index -= 1; }
} _controller.playlist!.removeAt(
_controller.playlist!.removeAt( index,
index, );
); (context as Element)
(context as Element) .markNeedsBuild();
.markNeedsBuild(); },
}, iconColor: colorScheme.outline,
iconColor: colorScheme.outline, size: 28,
size: 28, iconSize: 18,
iconSize: 18, ),
), );
); },
}, ),
), ),
), ],
], ),
), ),
), ),
), ),

View File

@@ -291,7 +291,10 @@ class _ReplyPageState extends CommonRichTextPubPageState<ReplyPage> {
Text( Text(
title, title,
maxLines: 1, maxLines: 1,
style: const TextStyle(fontSize: 13), style: TextStyle(
fontSize: 13,
color: theme.colorScheme.onSurface,
),
), ),
], ],
), ),