初始化
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
import 'package:hgdj/tools_base/widget/net_image_widget.dart';
|
||||
|
||||
import '../../assets_tool/images.dart';
|
||||
import '../../hj_page/mine/mine_vip/pay_order_source.dart';
|
||||
import '../../hj_page/mine/mine_vip/vip_support_model.dart';
|
||||
import '../../hj_page/mine/widgets/gradient_text.dart';
|
||||
import 'guide_common.dart';
|
||||
import 'guide_config.dart';
|
||||
import 'guide_manager.dart';
|
||||
|
||||
/// 优惠倒计时:时长前端写死,起点由 app 启动时 reset 一次,回前台不重置
|
||||
class PromoCountdown {
|
||||
static const Duration _duration = Duration(hours: 2);
|
||||
static DateTime? _deadline;
|
||||
|
||||
static DateTime get _end => _deadline ??= DateTime.now().add(_duration);
|
||||
|
||||
/// 剩余时长,归零停在 0
|
||||
static Duration get remain {
|
||||
final d = _end.difference(DateTime.now());
|
||||
return d.isNegative ? Duration.zero : d;
|
||||
}
|
||||
|
||||
/// app 启动时调一次(见 main),进程存活期间自然倒数
|
||||
static void reset() => _deadline = DateTime.now().add(_duration);
|
||||
}
|
||||
|
||||
/// 优惠倒计时弹窗(居中):主图骑顶 + 优惠文案 + 倒计时 + 开通按钮。
|
||||
/// 长视频返回挽留 / 进会员页时触发
|
||||
class GuideCountdownDialog extends StatelessWidget {
|
||||
final VipProductModel card;
|
||||
final ValueChanged<VipProductModel>?
|
||||
onConfirm; // 默认跳会员页选卡;已在会员页的场景自己处理(关弹窗+就地选中该卡)
|
||||
final PaySourcePage sourcePage;
|
||||
final String cover; // 场景下发的主图,相对路径由 ImageCacheManager 拼域名
|
||||
const GuideCountdownDialog({
|
||||
super.key,
|
||||
required this.card,
|
||||
required this.sourcePage,
|
||||
required this.cover,
|
||||
this.onConfirm,
|
||||
});
|
||||
|
||||
/// [scene] 决定主图/卡/下单来源,调用方须先用开关判过能弹
|
||||
/// 返回 true = 点了「立即开通」(点X/点蒙层/没弹成都是 false),返回挽留场景据此决定要不要放行返回
|
||||
static Future<bool> show(
|
||||
{required GuideScene scene,
|
||||
ValueChanged<VipProductModel>? onConfirm}) async {
|
||||
final card = await loadGuideCard(scene);
|
||||
if (card == null) return false; // 配置有误不弹
|
||||
final confirmed = await Get.dialog<bool>(
|
||||
GuideCountdownDialog(
|
||||
card: card,
|
||||
sourcePage: guideSourcePage(scene),
|
||||
cover: GuideManager().configOf(scene)?.cover ?? '', //已校验非空
|
||||
onConfirm: onConfirm,
|
||||
),
|
||||
);
|
||||
return confirmed ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 311,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
// 主体(顶部留 61 给主图骑顶溢出)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 61),
|
||||
padding: const EdgeInsets.fromLTRB(24, 80, 24, 24),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xff0F0F0F),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 现在下单获得[优惠]福利:「优惠」金渐变、更大号
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
children: [
|
||||
const Text('现在下单获得',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.w800)),
|
||||
2.sizeBoxW,
|
||||
const GradientText(
|
||||
'优惠',
|
||||
gradient: LinearGradient(colors: [
|
||||
Color(0xffFFE3A0),
|
||||
Color(0xffF0A93C)
|
||||
]),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.w800),
|
||||
),
|
||||
2.sizeBoxW,
|
||||
const Text('福利',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.w800)),
|
||||
],
|
||||
),
|
||||
15.sizeBoxH,
|
||||
const _CountdownRow(),
|
||||
15.sizeBoxH,
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
if (onConfirm != null) {
|
||||
onConfirm!(card);
|
||||
} else {
|
||||
goVipDetail(card, sourcePage: sourcePage);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: 269,
|
||||
height: 52,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(colors: [
|
||||
Color(0xffFFB03F),
|
||||
Color(0xffF2680C)
|
||||
]),
|
||||
borderRadius: BorderRadius.circular(26),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0x73F2680C),
|
||||
blurRadius: 18,
|
||||
offset: const Offset(0, 6))
|
||||
], // rgba(242,104,12,0.45)
|
||||
),
|
||||
child: Text(
|
||||
'立即开通 · 仅需¥${card.discountedPriceUI}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w700),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 主图骑在顶部
|
||||
Positioned(
|
||||
top: 0,
|
||||
child: SizedBox(
|
||||
width: 270,
|
||||
height: 122,
|
||||
child: NetworkImageLoader(
|
||||
imageUrl: cover, fit: BoxFit.fill, borderRadius: 8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
20.sizeBoxH,
|
||||
GestureDetector(
|
||||
onTap: () => Get.back(),
|
||||
child: Image.asset("close_button.png".commonImgPath, width: 32),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 距优惠结束 HH:MM:SS:每秒只重建这一行,归零后停表
|
||||
class _CountdownRow extends StatefulWidget {
|
||||
const _CountdownRow();
|
||||
|
||||
@override
|
||||
State<_CountdownRow> createState() => _CountdownRowState();
|
||||
}
|
||||
|
||||
class _CountdownRowState extends State<_CountdownRow> {
|
||||
final _remain = ValueNotifier<Duration>(PromoCountdown.remain);
|
||||
Timer? _timer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (t) {
|
||||
final r = PromoCountdown.remain;
|
||||
if (r == Duration.zero) t.cancel(); //归零停表(含弹窗打开时就已过期)
|
||||
if (r.inSeconds == _remain.value.inSeconds) return; //秒数没变不通知
|
||||
_remain.value = r;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
_remain.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('距优惠结束',
|
||||
style: TextStyle(
|
||||
color: Color(0xffB8AE9C),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500)),
|
||||
8.sizeBoxW,
|
||||
ValueListenableBuilder<Duration>(
|
||||
valueListenable: _remain,
|
||||
builder: (_, r, __) => Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_timeCell(r.inHours),
|
||||
_colon,
|
||||
_timeCell(r.inMinutes % 60),
|
||||
_colon,
|
||||
_timeCell(r.inSeconds % 60),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 时/分/秒块
|
||||
Widget _timeCell(int v) => Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xffE23B2E),
|
||||
borderRadius: BorderRadius.circular(6)),
|
||||
child: Text(
|
||||
v.toString().padLeft(2, '0'),
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 14, fontWeight: FontWeight.w700),
|
||||
),
|
||||
);
|
||||
|
||||
Widget get _colon => const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 3),
|
||||
child: Text(':',
|
||||
style: TextStyle(
|
||||
color: Color(0xffE85B4F),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700)),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user