Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 79 additions & 87 deletions lib/pages/home_album_section.dart
Original file line number Diff line number Diff line change
Expand Up @@ -177,101 +177,93 @@ class _HomeAlbumCard extends StatelessWidget {
'${_count(stats!.subscribeCount!)} 订阅',
if (stats?.finished == true) '完结',
];
return Tooltip(
message: album.title,
child: Material(
color: ranking ? colors.surfaceContainerLow : Colors.transparent,
borderRadius: BorderRadius.circular(16),
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
hoverColor: colors.primary.withValues(alpha: .045),
focusColor: colors.primary.withValues(alpha: .09),
highlightColor: colors.primary.withValues(alpha: .07),
child: Padding(
padding: EdgeInsets.all(ranking ? 8 : 4),
child: ranking
? Row(
children: [
SizedBox(
width: MediaQuery.textScalerOf(context).scale(22) * 1.5,
child: Text(
'$rank'.padLeft(2, '0'),
style: theme.textTheme.titleLarge?.copyWith(
fontSize: 22,
fontWeight: FontWeight.w600,
color: rank! <= 3
? colors.primary
: colors.onSurfaceVariant.withValues(
alpha: .65,
),
),
),
),
if (showCover) ...[
CoverImage(uri: album.coverUrl, size: coverSize),
const SizedBox(width: 8),
],
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title,
const SizedBox(height: 4),
Row(
children: [
Expanded(child: artist),
if (stats?.score != null) ...[
const SizedBox(width: 8),
_score(context),
],
],
),
],
return Material(
color: ranking ? colors.surfaceContainerLow : Colors.transparent,
borderRadius: BorderRadius.circular(16),
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
hoverColor: colors.primary.withValues(alpha: .045),
focusColor: colors.primary.withValues(alpha: .09),
highlightColor: colors.primary.withValues(alpha: .07),
child: Padding(
padding: EdgeInsets.all(ranking ? 8 : 4),
child: ranking
? Row(
children: [
SizedBox(
width: MediaQuery.textScalerOf(context).scale(22) * 1.5,
child: Text(
'$rank'.padLeft(2, '0'),
style: theme.textTheme.titleLarge?.copyWith(
fontSize: 22,
fontWeight: FontWeight.w600,
color: rank! <= 3
? colors.primary
: colors.onSurfaceVariant.withValues(alpha: .65),
),
),
),
if (showCover) ...[
CoverImage(uri: album.coverUrl, size: coverSize),
const SizedBox(width: 8),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (showCover) ...[
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: CoverImage(
uri: album.coverUrl,
size: coverSize,
),
),
const SizedBox(height: 6),
],
SizedBox(height: titleHeight, child: title),
const SizedBox(height: 4),
artist,
const SizedBox(height: 4),
Row(
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
details.join(' · '),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall?.copyWith(
height: 1.4,
color: colors.onSurfaceVariant,
),
),
title,
const SizedBox(height: 4),
Row(
children: [
Expanded(child: artist),
if (stats?.score != null) ...[
const SizedBox(width: 8),
_score(context),
],
],
),
if (stats?.score != null) ...[
const SizedBox(width: 8),
_score(context),
],
],
),
),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (showCover) ...[
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: CoverImage(uri: album.coverUrl, size: coverSize),
),
const SizedBox(height: 6),
],
),
),
SizedBox(height: titleHeight, child: title),
const SizedBox(height: 4),
artist,
const SizedBox(height: 4),
Row(
children: [
Expanded(
child: Text(
details.join(' · '),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall?.copyWith(
height: 1.4,
color: colors.onSurfaceVariant,
),
),
),
if (stats?.score != null) ...[
const SizedBox(width: 8),
_score(context),
],
],
),
],
),
),
),
);
Expand Down
65 changes: 40 additions & 25 deletions lib/pages/track_comments_panel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ class _TrackCommentsPanelState extends State<TrackCommentsPanel> {
final _scroll = ScrollController();
final _compose = TextEditingController();
bool _sending = false;
// Switching away and back can reuse the same client while a send is pending.
int _sendRequest = 0;
String? _composeError;
late _CommentFeed _feed;
String? _expandedComment;
Expand Down Expand Up @@ -115,6 +117,9 @@ class _TrackCommentsPanelState extends State<TrackCommentsPanel> {
if (identical(_feed.client, widget.runtime.client)) return;
_feed.dispose();
_expandedComment = null;
_sendRequest++;
_sending = false;
_composeError = null;
_openFeed();
}

Expand Down Expand Up @@ -182,6 +187,7 @@ class _TrackCommentsPanelState extends State<TrackCommentsPanel> {
if (_sending) return;
final client = widget.runtime.client, trackId = widget.track.id;
final content = _compose.text;
final request = ++_sendRequest;
setState(() {
AppLog.instance.trace(
"lib/pages/track_comments_panel.dart::_TrackCommentsPanelState._send.closure#1",
Expand All @@ -196,6 +202,7 @@ class _TrackCommentsPanelState extends State<TrackCommentsPanel> {
}
await XmlyComments(client).create(trackId, content);
if (!mounted ||
request != _sendRequest ||
widget.track.id != trackId ||
!identical(client, widget.runtime.client))
return;
Expand All @@ -205,6 +212,7 @@ class _TrackCommentsPanelState extends State<TrackCommentsPanel> {
} catch (error, stack) {
_recordCommentsError('Creating comment failed', error, stack);
if (mounted &&
request == _sendRequest &&
widget.track.id == trackId &&
identical(client, widget.runtime.client)) {
setState(() {
Expand All @@ -216,7 +224,7 @@ class _TrackCommentsPanelState extends State<TrackCommentsPanel> {
});
}
} finally {
if (mounted)
if (mounted && request == _sendRequest)
setState(() {
AppLog.instance.trace(
"lib/pages/track_comments_panel.dart::_TrackCommentsPanelState._send.closure#3",
Expand All @@ -242,6 +250,8 @@ class _TrackCommentsPanelState extends State<TrackCommentsPanel> {
oldWidget.track.id != widget.track.id) {
_feed.dispose();
_expandedComment = null;
_sendRequest++;
_sending = false;
_compose.clear();
_composeError = null;
if (_scroll.hasClients) _scroll.jumpTo(0);
Expand Down Expand Up @@ -353,6 +363,7 @@ class _TrackCommentsPanelState extends State<TrackCommentsPanel> {
comment: comment,
runtime: widget.runtime,
trackId: widget.track.id,
onLiked: _feed.setLiked,
onChanged: () {
AppLog.instance.trace(
"lib/pages/track_comments_panel.dart::_TrackCommentsPanelState._buildPanel.closure#1.closure#1",
Expand Down Expand Up @@ -525,13 +536,15 @@ class _CommentContent extends StatefulWidget {
required this.comment,
required this.runtime,
required this.trackId,
required this.onLiked,
required this.onChanged,
this.action,
this.compact = false,
});
final TrackComment comment;
final AppRuntime runtime;
final String trackId;
final void Function(String commentId, bool liked) onLiked;
final Future<void> Function() onChanged;
final Widget? action;
final bool compact;
Expand All @@ -547,25 +560,16 @@ class _CommentContent extends StatefulWidget {

class _CommentContentState extends State<_CommentContent> {
bool _busy = false;
TrackComment? _updated;
String? _error;

@override
void didUpdateWidget(covariant _CommentContent oldWidget) {
AppLog.instance.trace(
'lib/pages/track_comments_panel.dart::_CommentContentState.didUpdateWidget',
);
super.didUpdateWidget(oldWidget);
if (!identical(oldWidget.comment, widget.comment)) _updated = null;
}

Future<void> _mutate({bool delete = false}) async {
AppLog.instance.trace(
'lib/pages/track_comments_panel.dart::_CommentContentState._mutate',
);
if (_busy) return;
final runtime = widget.runtime, client = widget.runtime.client;
final comment = _updated ?? widget.comment;
final comment = widget.comment;
final onLiked = widget.onLiked;
setState(() {
AppLog.instance.trace(
"lib/pages/track_comments_panel.dart::_CommentContentState._mutate.closure#1",
Expand Down Expand Up @@ -626,25 +630,20 @@ class _CommentContentState extends State<_CommentContent> {
final api = XmlyComments(client);
if (delete) {
await api.delete(widget.trackId, comment);
if (mounted && identical(client, runtime.client))
await widget.onChanged();
} else {
await api.setLiked(widget.trackId, comment, liked: !comment.liked);
if (mounted)
setState(() {
AppLog.instance.trace(
"lib/pages/track_comments_panel.dart::_CommentContentState._mutate.closure#3",
);

_updated = comment.withLike(!comment.liked);
});
// The feed owns the result even if this row scrolled out of view.
if (identical(client, runtime.client))
onLiked(comment.id, !comment.liked);
}
if (mounted && identical(client, runtime.client))
await widget.onChanged();
} catch (error, stack) {
_recordCommentsError('Comment mutation failed', error, stack);
if (mounted)
setState(() {
AppLog.instance.trace(
"lib/pages/track_comments_panel.dart::_CommentContentState._mutate.closure#4",
"lib/pages/track_comments_panel.dart::_CommentContentState._mutate.closure#3",
);

_error = error is XmlyException ? error.message : '评论操作失败,请重试';
Expand All @@ -653,7 +652,7 @@ class _CommentContentState extends State<_CommentContent> {
if (mounted)
setState(() {
AppLog.instance.trace(
"lib/pages/track_comments_panel.dart::_CommentContentState._mutate.closure#5",
"lib/pages/track_comments_panel.dart::_CommentContentState._mutate.closure#4",
);

_busy = false;
Expand All @@ -666,7 +665,7 @@ class _CommentContentState extends State<_CommentContent> {
AppLog.instance.trace(
'lib/pages/track_comments_panel.dart::_CommentContentState.build',
);
final theme = Theme.of(context), comment = _updated ?? widget.comment;
final theme = Theme.of(context), comment = widget.comment;
final own =
comment.uid.isNotEmpty &&
comment.uid == widget.runtime.client.credentials?.uid;
Expand Down Expand Up @@ -918,6 +917,7 @@ class _InlineRepliesState extends State<_InlineReplies>
runtime: widget.runtime,
trackId: widget.trackId,
compact: true,
onLiked: _feed.setLiked,
onChanged: () {
AppLog.instance.trace(
"lib/pages/track_comments_panel.dart::_InlineRepliesState.build.closure#1.closure#1",
Expand Down Expand Up @@ -1057,6 +1057,21 @@ class _CommentFeed extends ChangeNotifier {
Completer<void>? _pendingRefresh;
String? error;

void setLiked(String id, bool liked) {
AppLog.instance.trace(
'lib/pages/track_comments_panel.dart::_CommentFeed.setLiked',
);
if (_disposed) return;
for (var index = 0; index < comments.length; index++) {
final comment = comments[index];
if (comment.id != id) continue;
// A refresh may have replaced the row while the like was pending.
comments[index] = comment.withLike(liked);
notifyListeners();
return;
}
}

Future<void> load({bool reset = false}) {
AppLog.instance.trace(
"lib/pages/track_comments_panel.dart::_CommentFeed.load",
Expand Down
Loading