Files
video/app/lib/screens/feed/widgets/feed_overlays.dart
T
2026-10-15 20:36:59 +08:00

174 lines
5.3 KiB
Dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../../core/theme/app_theme.dart';
/// 顶部轻提示(扣积分/操作失败等),自动消失,不依赖第三方 Toast 库
void showTopToast(BuildContext context, String message) {
final overlay = Overlay.of(context);
final entry = OverlayEntry(
builder: (_) => Positioned(
top: MediaQuery.of(context).padding.top + 16,
left: 48,
right: 48,
child: Material(
color: Colors.transparent,
child: Center(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.75),
borderRadius: BorderRadius.circular(20),
),
child: Text(
message,
style: const TextStyle(color: Colors.white, fontSize: 14),
),
),
),
),
),
);
overlay.insert(entry);
Future.delayed(const Duration(seconds: 2), entry.remove);
}
/// 游客今日免费次数已用完:登录引导(2002)
void showGuestLimitDialog(BuildContext context) {
showDialog<void>(
context: context,
barrierDismissible: false,
builder: (ctx) => AlertDialog(
backgroundColor: AppColors.surface,
title: const Text('今日免费次数已用完'),
content: const Text('注册解锁更多视频,今日已看 10/10'),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('再逛逛', style: TextStyle(color: AppColors.textSecondary)),
),
ElevatedButton(
onPressed: () {
Navigator.of(ctx).pop();
context.go('/login');
},
child: const Text('去登录/注册'),
),
],
),
);
}
/// 积分不足:引导充值 / 开通会员(2001)
void showPointsInsufficientDialog(BuildContext context, int points) {
showDialog<void>(
context: context,
builder: (ctx) => AlertDialog(
backgroundColor: AppColors.surface,
title: const Text('积分不足'),
content: Text('当前积分 $points,请充值或开通会员后继续观看'),
actions: [
TextButton(
onPressed: () {
Navigator.of(ctx).pop();
context.push('/membership');
},
child: const Text('开通会员', style: TextStyle(color: AppColors.gold)),
),
ElevatedButton(
onPressed: () {
Navigator.of(ctx).pop();
context.push('/recharge');
},
child: const Text('去充值'),
),
],
),
);
}
/// 底部免费剩余次数提示条:游客/非会员且剩余 ≤3 时显示(《01-开发文档》5.2.1)
class FreeQuotaBanner extends StatelessWidget {
const FreeQuotaBanner({super.key, required this.remaining});
/// 今日免费剩余次数;null(未上报过)或 >3 不显示
final int? remaining;
@override
Widget build(BuildContext context) {
final r = remaining;
if (r == null || r > 3) return const SizedBox.shrink();
return SafeArea(
child: Container(
width: double.infinity,
color: Colors.black.withValues(alpha: 0.6),
padding: const EdgeInsets.symmetric(vertical: 6),
child: Text(
r > 0 ? '今日免费观看剩余 $r 次' : '今日免费次数已用完,登录后继续观看',
textAlign: TextAlign.center,
style: const TextStyle(color: AppColors.gold, fontSize: 12),
),
),
);
}
}
/// 双击点赞的大爱心飘出动画
class LikeBurst extends StatefulWidget {
const LikeBurst({super.key, required this.trigger});
/// 每次双击递增触发动画(0 不显示)
final int trigger;
@override
State<LikeBurst> createState() => LikeBurstState();
}
class LikeBurstState extends State<LikeBurst>
with SingleTickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 700),
);
late final Animation<double> _scale = TweenSequence<double>([
TweenSequenceItem(tween: Tween(begin: 0.0, end: 1.2), weight: 30),
TweenSequenceItem(tween: Tween(begin: 1.2, end: 1.0), weight: 20),
TweenSequenceItem(tween: Tween(begin: 1.0, end: 1.0), weight: 50),
]).animate(_controller);
late final Animation<double> _opacity = TweenSequence<double>([
TweenSequenceItem(tween: Tween(begin: 1.0, end: 1.0), weight: 70),
TweenSequenceItem(tween: Tween(begin: 1.0, end: 0.0), weight: 30),
]).animate(_controller);
@override
void didUpdateWidget(LikeBurst oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.trigger != oldWidget.trigger && widget.trigger > 0) {
_controller.forward(from: 0);
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (widget.trigger <= 0) return const SizedBox.shrink();
return Center(
child: AnimatedBuilder(
animation: _controller,
builder: (_, __) => Opacity(
opacity: _opacity.value,
child: Transform.scale(
scale: _scale.value,
child: const Icon(Icons.favorite, size: 96, color: AppColors.primary),
),
),
),
);
}
}