add
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user