161 lines
4.7 KiB
Dart
161 lines
4.7 KiB
Dart
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();
|
||
}
|
||
}
|