Files
video/app/lib/screens/feed/widgets/interaction_bar.dart
T
2026-10-16 16:05:01 +08:00

161 lines
4.7 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../core/theme/app_theme.dart';
import '../../../models/video_item.dart';
import '../../../providers/auth_provider.dart';
/// 右侧互动栏(《01-开发文档》5.2.1):
/// 作者头像(+关注预留)、点赞(红心+计数)、评论(气泡+计数)、收藏(星标+计数)、分享
class InteractionBar extends ConsumerWidget {
const InteractionBar({
super.key,
required this.item,
required this.onLike,
this.onFavorite,
this.onComment,
this.onShare,
});
final VideoItem item;
final void Function(int videoId) onLike;
/// 收藏回调(与点赞一样由上层接当前策略的 feedProvider)
final void Function(int videoId)? onFavorite;
/// 评论入口(游客也可打开查看,发表时才守卫登录)
final void Function(VideoItem item)? onComment;
/// 分享入口(系统分享面板,无需登录)
final void Function(VideoItem item)? onShare;
@override
Widget build(BuildContext context, WidgetRef ref) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
// 作者头像(关注预留:右下角 + 号占位)
_AuthorAvatar(item: item),
const SizedBox(height: 20),
_ActionItem(
icon: item.isLiked ? Icons.favorite : Icons.favorite_border,
color: item.isLiked ? AppColors.primary : Colors.white,
count: item.likeCount,
onTap: () => _guard(context, ref, () => onLike(item.videoId)),
),
const SizedBox(height: 16),
_ActionItem(
icon: Icons.chat_bubble_outline,
count: item.commentCount,
onTap: () => onComment?.call(item),
),
const SizedBox(height: 16),
_ActionItem(
icon: item.isFavorited ? Icons.star : Icons.star_border,
color: item.isFavorited ? AppColors.gold : Colors.white,
count: item.favoriteCount,
onTap: () => _guard(context, ref, () => onFavorite?.call(item.videoId)),
),
],
);
}
/// 登录守卫:未登录跳登录页
void _guard(BuildContext context, WidgetRef ref, VoidCallback action) {
if (!ref.read(authProvider).isLoggedIn) {
context.go('/login');
return;
}
action();
}
}
class _AuthorAvatar extends StatelessWidget {
const _AuthorAvatar({required this.item});
final VideoItem item;
@override
Widget build(BuildContext context) {
// 点头像进作者公开主页(游客可看)
return GestureDetector(
onTap: () => context.push('/user/${item.author.userId}'),
child: SizedBox(
width: 48,
height: 56,
child: Stack(
children: [
CircleAvatar(
radius: 24,
backgroundColor: AppColors.surface,
backgroundImage: item.author.avatarUrl.isNotEmpty
? CachedNetworkImageProvider(item.author.avatarUrl)
: null,
child: item.author.avatarUrl.isEmpty
? Text(
item.author.nickname.isNotEmpty
? item.author.nickname.characters.first
: '友',
style: const TextStyle(color: Colors.white, fontSize: 18),
)
: null,
),
const Positioned(
bottom: 0,
left: 12,
child: Icon(Icons.add_circle, size: 20, color: AppColors.primary),
),
],
),
),
);
}
}
class _ActionItem extends StatelessWidget {
const _ActionItem({
required this.icon,
required this.onTap,
this.count,
this.label,
this.color = Colors.white,
});
final IconData icon;
final VoidCallback onTap;
final int? count;
final String? label;
final Color color;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 32, color: color),
const SizedBox(height: 2),
Text(
label ?? _formatCount(count ?? 0),
style: const TextStyle(
color: Colors.white,
fontSize: 12,
shadows: [Shadow(color: Colors.black54, blurRadius: 2)],
),
),
],
),
);
}
/// 计数格式化:≥10000 显示 x.xw
static String _formatCount(int n) {
if (n >= 10000) return '${(n / 10000).toStringAsFixed(1)}w';
return n.toString();
}
}