174 lines
5.3 KiB
Dart
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),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|