feat(player): 点赞与评论改为按集
This commit is contained in:
1 parent
e6f34977f5
commit
83822a0cbb
25 files changed
+337
-69
No files matched your search
@@ -15,6 +15,8 @@ class Episode {
|
||||
this.videoUrl = '',
|
||||
this.durationSec = 0,
|
||||
this.locked = false,
|
||||
this.likeCount,
|
||||
this.commentCount,
|
||||
});
|
||||
|
||||
final String id;
|
||||
@@ -28,16 +30,21 @@ class Episode {
|
||||
|
||||
/// 后端判定需要解锁;解锁能力(金币/VIP)在 M3 接入。
|
||||
final bool locked;
|
||||
final int? likeCount;
|
||||
final int? commentCount;
|
||||
|
||||
Episode withVideoUrl(String url) => Episode(
|
||||
id: id,
|
||||
number: number,
|
||||
title: title,
|
||||
isFree: isFree,
|
||||
videoUrl: url,
|
||||
durationSec: durationSec,
|
||||
locked: locked,
|
||||
);
|
||||
Episode withVideoUrl(String url, {int? likeCount, int? commentCount}) =>
|
||||
Episode(
|
||||
id: id,
|
||||
number: number,
|
||||
title: title,
|
||||
isFree: isFree,
|
||||
videoUrl: url,
|
||||
durationSec: durationSec,
|
||||
locked: locked,
|
||||
likeCount: likeCount ?? this.likeCount,
|
||||
commentCount: commentCount ?? this.commentCount,
|
||||
);
|
||||
|
||||
factory Episode.fromJson(Map<String, dynamic> json) {
|
||||
return Episode(
|
||||
@@ -48,6 +55,8 @@ class Episode {
|
||||
durationSec: (json['durationSec'] as num?)?.toInt() ?? 0,
|
||||
locked: json['locked'] == true,
|
||||
videoUrl: json['playUrl'] as String? ?? '',
|
||||
likeCount: _optionalInt(json['likeCount']),
|
||||
commentCount: _optionalInt(json['commentCount']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
import 'json_numbers.dart';
|
||||
|
||||
/// 互动目标类型,契约只允许短剧与评论。
|
||||
/// 互动目标类型。
|
||||
enum InteractionTargetType {
|
||||
drama('DRAMA'),
|
||||
episode('EPISODE'),
|
||||
comment('COMMENT');
|
||||
|
||||
const InteractionTargetType(this.apiValue);
|
||||
|
||||
@@ -14,6 +14,8 @@ class PlayTicket {
|
||||
this.unlockCoin = 0,
|
||||
this.previewSeconds = 0,
|
||||
this.qualities = const [],
|
||||
this.likeCount,
|
||||
this.commentCount,
|
||||
});
|
||||
|
||||
static const String free = 'FREE';
|
||||
@@ -38,6 +40,8 @@ class PlayTicket {
|
||||
|
||||
/// 后端下发的清晰度档位(如 AUTO / 720P / 1080P),HLS 由播放器自动切换。
|
||||
final List<String> qualities;
|
||||
final int? likeCount;
|
||||
final int? commentCount;
|
||||
|
||||
bool get needsUnlock =>
|
||||
accessType == coinRequired || accessType == vipRequired;
|
||||
@@ -58,6 +62,12 @@ class PlayTicket {
|
||||
.whereType<String>()
|
||||
.where((quality) => quality.isNotEmpty)
|
||||
.toList(),
|
||||
likeCount: json['likeCount'] == null
|
||||
? null
|
||||
: jsonToInt(json['likeCount']),
|
||||
commentCount: json['commentCount'] == null
|
||||
? null
|
||||
: jsonToInt(json['commentCount']),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ class CommentApi {
|
||||
/// 拉取游标分页;`rootId` 用于加载某一楼层的回复。
|
||||
Future<CommentPage> page({
|
||||
required String dramaId,
|
||||
String? episodeId,
|
||||
String? rootId,
|
||||
String? cursor,
|
||||
int size = commentPageSize,
|
||||
@@ -21,6 +22,7 @@ class CommentApi {
|
||||
'/app/comments',
|
||||
query: {
|
||||
'dramaId': dramaId,
|
||||
'episodeId': ?episodeId,
|
||||
'rootId': ?rootId,
|
||||
'cursor': ?cursor,
|
||||
'size': '$size',
|
||||
@@ -31,10 +33,10 @@ class CommentApi {
|
||||
}
|
||||
|
||||
/// 查询单部剧的有效评论总数;后端 Long 兼容数字与字符串。
|
||||
Future<int> count(String dramaId) async {
|
||||
Future<int> count(String dramaId, {String? episodeId}) async {
|
||||
final data = await _client.get(
|
||||
'/app/comments/count',
|
||||
query: {'dramaId': dramaId},
|
||||
query: {'dramaId': dramaId, 'episodeId': ?episodeId},
|
||||
);
|
||||
return jsonToInt(data);
|
||||
}
|
||||
|
||||
@@ -8,13 +8,14 @@ abstract interface class CommentRepository {
|
||||
/// 拉取评论或回复的分页。
|
||||
Future<CommentPage> page({
|
||||
required String dramaId,
|
||||
String? episodeId,
|
||||
String? rootId,
|
||||
String? cursor,
|
||||
int size = commentPageSize,
|
||||
});
|
||||
|
||||
/// 查询单部剧的有效评论总数。
|
||||
Future<int> count(String dramaId);
|
||||
Future<int> count(String dramaId, {String? episodeId});
|
||||
|
||||
/// 发表评论或回复。
|
||||
Future<DramaComment> publish({
|
||||
@@ -37,18 +38,21 @@ class HttpCommentRepository implements CommentRepository {
|
||||
@override
|
||||
Future<CommentPage> page({
|
||||
required String dramaId,
|
||||
String? episodeId,
|
||||
String? rootId,
|
||||
String? cursor,
|
||||
int size = commentPageSize,
|
||||
}) => _api.page(
|
||||
dramaId: dramaId,
|
||||
episodeId: episodeId,
|
||||
rootId: rootId,
|
||||
cursor: cursor,
|
||||
size: size,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<int> count(String dramaId) => _api.count(dramaId);
|
||||
Future<int> count(String dramaId, {String? episodeId}) =>
|
||||
_api.count(dramaId, episodeId: episodeId);
|
||||
|
||||
@override
|
||||
Future<DramaComment> publish({
|
||||
|
||||
@@ -75,6 +75,7 @@ class CommentController extends ChangeNotifier {
|
||||
try {
|
||||
final page = await _commentRepository.page(
|
||||
dramaId: dramaId,
|
||||
episodeId: episodeId,
|
||||
size: commentPageSize,
|
||||
);
|
||||
_comments
|
||||
@@ -101,6 +102,7 @@ class CommentController extends ChangeNotifier {
|
||||
try {
|
||||
final page = await _commentRepository.page(
|
||||
dramaId: dramaId,
|
||||
episodeId: episodeId,
|
||||
cursor: cursor,
|
||||
size: commentPageSize,
|
||||
);
|
||||
@@ -128,6 +130,7 @@ class CommentController extends ChangeNotifier {
|
||||
try {
|
||||
final page = await _commentRepository.page(
|
||||
dramaId: dramaId,
|
||||
episodeId: episodeId,
|
||||
rootId: rootId,
|
||||
size: commentPageSize,
|
||||
);
|
||||
@@ -157,6 +160,7 @@ class CommentController extends ChangeNotifier {
|
||||
try {
|
||||
final page = await _commentRepository.page(
|
||||
dramaId: dramaId,
|
||||
episodeId: episodeId,
|
||||
rootId: rootId,
|
||||
cursor: cursor,
|
||||
size: commentPageSize,
|
||||
|
||||
@@ -17,6 +17,9 @@ class InteractionController extends ChangeNotifier {
|
||||
final Set<String> _likedDramaIds = {};
|
||||
final Set<String> _busyDramaIds = {};
|
||||
final Map<String, int> _likeCounts = {};
|
||||
final Set<String> _likedEpisodeIds = {};
|
||||
final Set<String> _busyEpisodeIds = {};
|
||||
final Map<String, int> _episodeLikeCounts = {};
|
||||
bool _disposed = false;
|
||||
|
||||
/// 当前会话内该剧是否已点赞。
|
||||
@@ -28,42 +31,75 @@ class InteractionController extends ChangeNotifier {
|
||||
/// 服务端最新点赞数,仅在点赞接口成功返回后可用。
|
||||
int? likeCount(String dramaId) => _likeCounts[dramaId];
|
||||
|
||||
/// 当前会话内该集是否已点赞。
|
||||
bool isEpisodeLiked(String episodeId) => _likedEpisodeIds.contains(episodeId);
|
||||
|
||||
/// 该集点赞请求是否进行中。
|
||||
bool isEpisodeBusy(String episodeId) => _busyEpisodeIds.contains(episodeId);
|
||||
|
||||
/// 该集服务端最新点赞数。
|
||||
int? episodeLikeCount(String episodeId) => _episodeLikeCounts[episodeId];
|
||||
|
||||
/// 乐观切换剧集点赞,失败时回滚并以服务端返回状态校准。
|
||||
Future<OperationResult<LikeToggleResult>> toggleDramaLike(
|
||||
String dramaId,
|
||||
) async {
|
||||
if (_busyDramaIds.contains(dramaId)) {
|
||||
Future<OperationResult<LikeToggleResult>> toggleDramaLike(String dramaId) =>
|
||||
_toggleLike(
|
||||
id: dramaId,
|
||||
targetType: InteractionTargetType.drama,
|
||||
likedIds: _likedDramaIds,
|
||||
busyIds: _busyDramaIds,
|
||||
counts: _likeCounts,
|
||||
);
|
||||
|
||||
/// 乐观切换分集点赞,失败时回滚并以服务端返回状态校准。
|
||||
Future<OperationResult<LikeToggleResult>> toggleEpisodeLike(
|
||||
String episodeId,
|
||||
) => _toggleLike(
|
||||
id: episodeId,
|
||||
targetType: InteractionTargetType.episode,
|
||||
likedIds: _likedEpisodeIds,
|
||||
busyIds: _busyEpisodeIds,
|
||||
counts: _episodeLikeCounts,
|
||||
);
|
||||
|
||||
Future<OperationResult<LikeToggleResult>> _toggleLike({
|
||||
required String id,
|
||||
required InteractionTargetType targetType,
|
||||
required Set<String> likedIds,
|
||||
required Set<String> busyIds,
|
||||
required Map<String, int> counts,
|
||||
}) async {
|
||||
if (busyIds.contains(id)) {
|
||||
return const OperationResult.failure(null);
|
||||
}
|
||||
final wasLiked = _likedDramaIds.contains(dramaId);
|
||||
_busyDramaIds.add(dramaId);
|
||||
final wasLiked = likedIds.contains(id);
|
||||
busyIds.add(id);
|
||||
if (wasLiked) {
|
||||
_likedDramaIds.remove(dramaId);
|
||||
likedIds.remove(id);
|
||||
} else {
|
||||
_likedDramaIds.add(dramaId);
|
||||
likedIds.add(id);
|
||||
}
|
||||
_notify();
|
||||
try {
|
||||
final result = await _repository.toggleLike(
|
||||
targetType: InteractionTargetType.drama,
|
||||
targetId: dramaId,
|
||||
targetType: targetType,
|
||||
targetId: id,
|
||||
);
|
||||
if (result.liked) {
|
||||
_likedDramaIds.add(dramaId);
|
||||
likedIds.add(id);
|
||||
} else {
|
||||
_likedDramaIds.remove(dramaId);
|
||||
likedIds.remove(id);
|
||||
}
|
||||
_likeCounts[dramaId] = result.count;
|
||||
counts[id] = result.count;
|
||||
return OperationResult.success(result);
|
||||
} catch (failure) {
|
||||
if (wasLiked) {
|
||||
_likedDramaIds.add(dramaId);
|
||||
likedIds.add(id);
|
||||
} else {
|
||||
_likedDramaIds.remove(dramaId);
|
||||
likedIds.remove(id);
|
||||
}
|
||||
return OperationResult.failure(_errorMessage(failure));
|
||||
} finally {
|
||||
_busyDramaIds.remove(dramaId);
|
||||
busyIds.remove(id);
|
||||
_notify();
|
||||
}
|
||||
}
|
||||
@@ -93,12 +129,18 @@ class InteractionController extends ChangeNotifier {
|
||||
void clear() {
|
||||
if (_likedDramaIds.isEmpty &&
|
||||
_busyDramaIds.isEmpty &&
|
||||
_likeCounts.isEmpty) {
|
||||
_likeCounts.isEmpty &&
|
||||
_likedEpisodeIds.isEmpty &&
|
||||
_busyEpisodeIds.isEmpty &&
|
||||
_episodeLikeCounts.isEmpty) {
|
||||
return;
|
||||
}
|
||||
_likedDramaIds.clear();
|
||||
_busyDramaIds.clear();
|
||||
_likeCounts.clear();
|
||||
_likedEpisodeIds.clear();
|
||||
_busyEpisodeIds.clear();
|
||||
_episodeLikeCounts.clear();
|
||||
_notify();
|
||||
}
|
||||
|
||||
|
||||
@@ -116,6 +116,12 @@ class _DramaDetailPageState extends State<DramaDetailPage> {
|
||||
final saved = context.watch<FavoriteController>().isSaved(drama.id);
|
||||
final continueIndex = logic.continueIndex;
|
||||
final episodesReady = drama.detailLoaded && drama.episodes.isNotEmpty;
|
||||
final statsEpisode = episodesReady
|
||||
? drama.episodes[continueIndex != null &&
|
||||
continueIndex < drama.episodes.length
|
||||
? continueIndex
|
||||
: 0]
|
||||
: null;
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.ink,
|
||||
bottomNavigationBar: DramaDetailBottomBar(
|
||||
@@ -146,6 +152,7 @@ class _DramaDetailPageState extends State<DramaDetailPage> {
|
||||
l10n: l10n,
|
||||
drama: drama,
|
||||
favoriteCount: logic.adjustedFavoriteCount,
|
||||
likeCount: statsEpisode?.likeCount,
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
|
||||
@@ -34,11 +34,13 @@ class DramaStatsCard extends StatelessWidget {
|
||||
required this.l10n,
|
||||
required this.drama,
|
||||
this.favoriteCount,
|
||||
this.likeCount,
|
||||
});
|
||||
|
||||
final AppLocalizations l10n;
|
||||
final Drama drama;
|
||||
final int? favoriteCount;
|
||||
final int? likeCount;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -46,9 +48,9 @@ class DramaStatsCard extends StatelessWidget {
|
||||
final favorite = currentFavoriteCount == null
|
||||
? l10n.episodeCountLabel(drama.episodeCount)
|
||||
: '${formatInteractionCount(currentFavoriteCount)} ${l10n.favorite}';
|
||||
final like = drama.likeCount == null
|
||||
final like = likeCount == null
|
||||
? '—'
|
||||
: '${formatInteractionCount(drama.likeCount!)} ${l10n.like}';
|
||||
: '${formatInteractionCount(likeCount!)} ${l10n.like}';
|
||||
return Container(
|
||||
key: const Key('detail-stats'),
|
||||
margin: const EdgeInsets.fromLTRB(
|
||||
|
||||
@@ -230,7 +230,11 @@ class PlayerLogic extends ChangeNotifier {
|
||||
qualities = ticket.qualities;
|
||||
if (ticket.accessible && ticket.playUrl.isNotEmpty) {
|
||||
// 签名地址原样写回,不做任何拼接/改写(AppConfig.mediaUrl 只补 host)
|
||||
sessionEpisodes[index] = episode.withVideoUrl(ticket.playUrl);
|
||||
sessionEpisodes[index] = episode.withVideoUrl(
|
||||
ticket.playUrl,
|
||||
likeCount: ticket.likeCount,
|
||||
commentCount: ticket.commentCount,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
blockedTicket = ticket;
|
||||
@@ -797,7 +801,11 @@ class PlayerLogic extends ChangeNotifier {
|
||||
notify();
|
||||
}
|
||||
|
||||
Future<void> showComments(BuildContext context, Drama drama) async {
|
||||
Future<void> showComments(
|
||||
BuildContext context,
|
||||
Drama drama, {
|
||||
String? episodeId,
|
||||
}) async {
|
||||
session.setOverlayOpen(true);
|
||||
await session.playbackSettled;
|
||||
if (!context.mounted) {
|
||||
@@ -812,7 +820,7 @@ class PlayerLogic extends ChangeNotifier {
|
||||
showDragHandle: false,
|
||||
isDismissible: false,
|
||||
enableDrag: false,
|
||||
builder: (_) => DramaCommentsSheet(drama: drama),
|
||||
builder: (_) => DramaCommentsSheet(drama: drama, episodeId: episodeId),
|
||||
);
|
||||
} finally {
|
||||
session.setOverlayOpen(false);
|
||||
|
||||
@@ -205,13 +205,22 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _openComments(BuildContext context, Drama drama) async {
|
||||
Future<void> _openComments(
|
||||
BuildContext context,
|
||||
Drama drama,
|
||||
Episode episode,
|
||||
) async {
|
||||
final comments = context.read<CommentRepository>();
|
||||
await openCommentsWithAuth(context, drama, show: logic.showComments);
|
||||
await openCommentsWithAuth(
|
||||
context,
|
||||
drama,
|
||||
show: (context, drama) =>
|
||||
logic.showComments(context, drama, episodeId: episode.id),
|
||||
);
|
||||
try {
|
||||
final count = await comments.count(drama.id);
|
||||
final count = await comments.count(drama.id, episodeId: episode.id);
|
||||
if (!mounted) return;
|
||||
setState(() => _commentCounts[drama.id] = count);
|
||||
setState(() => _commentCounts[episode.id] = count);
|
||||
} catch (_) {
|
||||
// 评论面板已经关闭,计数刷新失败时保留旧值即可。
|
||||
}
|
||||
@@ -456,18 +465,19 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
onBoostEnd: _endBoost,
|
||||
onTogglePlay: () =>
|
||||
logic.session.setUserWantsPlay(!logic.session.userWantsPlay),
|
||||
liked: interactions.isLiked(drama.id),
|
||||
likeCount: interactions.likeCount(drama.id) ?? drama.likeCount,
|
||||
liked: interactions.isEpisodeLiked(episode.id),
|
||||
likeCount:
|
||||
interactions.episodeLikeCount(episode.id) ?? episode.likeCount,
|
||||
favoriteCount: drama.favoriteCount == null
|
||||
? null
|
||||
: drama.favoriteCount! + (_favoriteDelta[drama.id] ?? 0),
|
||||
commentCount: _commentCounts[drama.id] ?? drama.commentCount,
|
||||
commentCount: _commentCounts[episode.id] ?? episode.commentCount,
|
||||
saved: context.watch<FavoriteController>().isSaved(drama.id),
|
||||
onLike: () async {
|
||||
if (await ensureSignedIn(context)) {
|
||||
if (!context.mounted) return;
|
||||
unawaited(context.read<Haptics>().lightImpact());
|
||||
final result = await interactions.toggleDramaLike(drama.id);
|
||||
final result = await interactions.toggleEpisodeLike(episode.id);
|
||||
if (!result.isSuccess && context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
@@ -481,7 +491,7 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
}
|
||||
},
|
||||
onSave: () => unawaited(_toggleSave(context, drama)),
|
||||
onComments: () => unawaited(_openComments(context, drama)),
|
||||
onComments: () => unawaited(_openComments(context, drama, episode)),
|
||||
onShare: () => logic.shareDrama(context, drama),
|
||||
onDetail: () => unawaited(logic.openDetail(context)),
|
||||
onOpenFullPlayer: () async {
|
||||
@@ -502,9 +512,11 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
drama: drama,
|
||||
episode: episode,
|
||||
controller: ready ? videoPort!.controller : null,
|
||||
liked: interactions.isLiked(drama.id),
|
||||
likeCount: interactions.likeCount(drama.id) ?? drama.likeCount,
|
||||
commentCount: _commentCounts[drama.id] ?? drama.commentCount,
|
||||
liked: interactions.isEpisodeLiked(episode.id),
|
||||
likeCount:
|
||||
interactions.episodeLikeCount(episode.id) ??
|
||||
episode.likeCount,
|
||||
commentCount: _commentCounts[episode.id] ?? episode.commentCount,
|
||||
favoriteCount: drama.favoriteCount == null
|
||||
? null
|
||||
: drama.favoriteCount! + (_favoriteDelta[drama.id] ?? 0),
|
||||
@@ -525,9 +537,10 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
onDetail: () => unawaited(logic.openDetail(context)),
|
||||
onSpeed: () => unawaited(_openSpeedSheet(context)),
|
||||
onEpisodes: () => logic.showEpisodes(context),
|
||||
onComments: () => unawaited(_openComments(context, drama)),
|
||||
onComments: () =>
|
||||
unawaited(_openComments(context, drama, episode)),
|
||||
onLike: () =>
|
||||
unawaited(_toggleLike(context, interactions, drama)),
|
||||
unawaited(_toggleLike(context, interactions, episode)),
|
||||
onSave: () => unawaited(_toggleSave(context, drama)),
|
||||
onShare: () => logic.shareDrama(context, drama),
|
||||
onMore: () => _openMore(context, episode),
|
||||
|
||||
@@ -81,11 +81,11 @@ extension _PlayerViewActions on _PlayerPageState {
|
||||
Future<void> _toggleLike(
|
||||
BuildContext context,
|
||||
InteractionController interactions,
|
||||
Drama drama,
|
||||
Episode episode,
|
||||
) async {
|
||||
if (!await ensureSignedIn(context) || !context.mounted) return;
|
||||
unawaited(context.read<Haptics>().lightImpact());
|
||||
final result = await interactions.toggleDramaLike(drama.id);
|
||||
final result = await interactions.toggleEpisodeLike(episode.id);
|
||||
if (result.isSuccess || !context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
|
||||
@@ -31,9 +31,10 @@ const double _composerTopGap = 8;
|
||||
|
||||
/// 服务端评论底部弹窗:匿名列表、游标翻页、回复与登录发布。
|
||||
class DramaCommentsSheet extends StatefulWidget {
|
||||
const DramaCommentsSheet({super.key, required this.drama});
|
||||
const DramaCommentsSheet({super.key, required this.drama, this.episodeId});
|
||||
|
||||
final Drama drama;
|
||||
final String? episodeId;
|
||||
|
||||
@override
|
||||
State<DramaCommentsSheet> createState() => _DramaCommentsSheetState();
|
||||
@@ -56,6 +57,7 @@ class _DramaCommentsSheetState extends State<DramaCommentsSheet> {
|
||||
comments: _comments,
|
||||
interactions: context.read<InteractionRepository>(),
|
||||
dramaId: widget.drama.id,
|
||||
episodeId: widget.episodeId,
|
||||
);
|
||||
unawaited(_controller.load());
|
||||
unawaited(_refreshCount());
|
||||
@@ -77,7 +79,10 @@ class _DramaCommentsSheetState extends State<DramaCommentsSheet> {
|
||||
Future<void> _refreshCount() async {
|
||||
final request = ++_countRequest;
|
||||
try {
|
||||
final count = await _comments.count(widget.drama.id);
|
||||
final count = await _comments.count(
|
||||
widget.drama.id,
|
||||
episodeId: widget.episodeId,
|
||||
);
|
||||
if (!mounted || request != _countRequest) return;
|
||||
setState(() => _serverCount = count);
|
||||
} catch (_) {
|
||||
@@ -464,8 +469,9 @@ class _CommentThread extends StatelessWidget {
|
||||
Text(
|
||||
expanded
|
||||
? AppLocalizations.of(context)!.hideReplies
|
||||
: AppLocalizations.of(context)!
|
||||
.viewReplies(comment.replyCount),
|
||||
: AppLocalizations.of(
|
||||
context,
|
||||
)!.viewReplies(comment.replyCount),
|
||||
style: const TextStyle(
|
||||
color: AppColors.muted,
|
||||
fontSize: 12,
|
||||
|
||||
Reference in new issue
Block a user