166 lines
6.6 KiB
Dart
166 lines
6.6 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:get/get.dart';
|
||
import 'package:hgdj/assets_tool/images.dart';
|
||
import 'package:hgdj/hj_model/splash/domain_source_model.dart';
|
||
import 'package:hgdj/hj_page/home/activity_float/home_activity_float.dart';
|
||
import 'package:hgdj/hj_page/main_page/provider/msg_provider.dart';
|
||
import 'package:hgdj/hj_utils/screen.dart';
|
||
import 'package:hgdj/tools_base/widget/fly_to_overlay.dart';
|
||
import 'package:hgdj/tools_base/widget/net_image_widget.dart';
|
||
|
||
/// 支付分层首页弹窗:展示后端 homePage 图,点击跳会员页并选中 config.vipCard。
|
||
/// 倒计时读 config.lastDiscountTime,秒级刷新由 MineMsgProvider().tick 驱动,过期自动停
|
||
class GuideHomeDialog extends StatefulWidget {
|
||
final PayTierConfig config;
|
||
|
||
const GuideHomeDialog({super.key, required this.config});
|
||
|
||
@override
|
||
State<GuideHomeDialog> createState() => _GuideHomeDialogState();
|
||
}
|
||
|
||
class _GuideHomeDialogState extends State<GuideHomeDialog> {
|
||
final _posterKey = GlobalKey(); // 关闭动画的起飞位置
|
||
|
||
/// 关闭中:① 防连点,pop 过渡期弹窗还在树上,再点会把首页也 pop 掉
|
||
/// ② 藏掉原海报,否则它随弹窗淡出、和飞行副本重影
|
||
bool _closing = false;
|
||
|
||
PayTierConfig get config => widget.config;
|
||
|
||
/// 关闭:海报缩小飞进右下角浮窗,告诉用户同款入口收在哪
|
||
void _close() {
|
||
if (_closing) return;
|
||
_closing = true;
|
||
final target = _floatRect();
|
||
if (target != null) {
|
||
FlyToOverlay.play(
|
||
context: context,
|
||
sourceKey: _posterKey,
|
||
target: target,
|
||
// 副本只放海报,倒计时胶囊不跟着飞(缩到 60 宽也看不清)
|
||
// 占位给透明:没命中内存缓存时别飞一个灰块过去
|
||
child: NetworkImageLoader(
|
||
imageUrl: config.homePage ?? "",
|
||
fit: BoxFit.fill,
|
||
borderRadius: 0,
|
||
placeHolderWidget: const SizedBox(),
|
||
),
|
||
);
|
||
}
|
||
setState(() {}); // 原海报立刻消失,只留副本在飞
|
||
Get.back();
|
||
}
|
||
|
||
/// 落点:容器右下角一块浮窗大小的区域(浮窗都 bottomRight 对齐)。
|
||
/// 认容器不认轮播子项——子项会轮到屏幕外,容器位置才稳定。
|
||
/// 浮窗没展示时容器不存在,返回 null → 直接关不飞
|
||
Rect? _floatRect() {
|
||
final box = homeFloatKey.currentContext?.findRenderObject() as RenderBox?;
|
||
if (box == null || !box.hasSize) return null;
|
||
final origin = box.localToGlobal(Offset.zero);
|
||
const w = 60.0, h = 66.0; // 与 _LayeredFloatView 的尺寸一致
|
||
return Rect.fromLTWH(
|
||
origin.dx + box.size.width - w, origin.dy + box.size.height - h, w, h);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
// 点蒙层、返回键都走 _close,保证任何关闭方式都有飞入动画
|
||
// (canPop:false 只拦 maybePop,_close 里的 Get.back() 是强制 pop,不会被自己拦住)
|
||
return PopScope(
|
||
canPop: false,
|
||
onPopInvokedWithResult: (didPop, _) {
|
||
if (!didPop) _close();
|
||
},
|
||
child: Center(
|
||
// 关闭中内容交给飞行副本。必须同时 IgnorePointer:
|
||
// Opacity(0) 照样吃手势,退出动画那百来毫秒点到海报会再 pop 一次并跳会员页
|
||
child: IgnorePointer(
|
||
ignoring: _closing,
|
||
child: Opacity(
|
||
opacity: _closing ? 0 : 1,
|
||
child: Material(
|
||
color: Colors.transparent,
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: <Widget>[
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 30),
|
||
child: GestureDetector(
|
||
// 只回传"去开通",跳会员页交给调用方(_showPayDialog)——它要 await 到从会员页
|
||
// 返回才继续弹窗链,否则后面的首页吸底引导会弹在会员页上
|
||
onTap: () {
|
||
if (_closing) return; // 防连点:pop 过渡期弹窗还在树上,再点会把首页也 pop 掉
|
||
_closing = true;
|
||
Get.back(result: true);
|
||
},
|
||
child: AspectRatio(
|
||
aspectRatio: 3 / 4,
|
||
child: Stack(
|
||
key: _posterKey,
|
||
fit: StackFit.expand,
|
||
children: [
|
||
NetworkImageLoader(
|
||
imageUrl: config.homePage ?? "",
|
||
fit: BoxFit.fill,
|
||
borderRadius: 0,
|
||
placeHolderWidget:
|
||
Container(color: const Color(0x00000000)),
|
||
),
|
||
// 倒计时胶囊:海报右上角,听 tick 每秒刷新,过期自动收起
|
||
Positioned(
|
||
top: 0,
|
||
right: 0,
|
||
child: ValueListenableBuilder<int>(
|
||
valueListenable: MineMsgProvider().tick,
|
||
builder: (_, __, ___) =>
|
||
config.hasDiscountCountdown
|
||
? _buildCountdownPill()
|
||
: const SizedBox.shrink(),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
20.sizeBoxH,
|
||
GestureDetector(
|
||
onTap: _close,
|
||
child: Image.asset("close_button.png".commonImgPath,
|
||
width: 32),
|
||
)
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 倒计时胶囊:红渐变 + 奶白边,HH:MM:SS
|
||
Widget _buildCountdownPill() {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 2),
|
||
decoration: BoxDecoration(
|
||
gradient: const LinearGradient(
|
||
colors: [Color(0xffE03017), Color(0xffF27952)],
|
||
begin: Alignment.topLeft,
|
||
end: Alignment.bottomRight,
|
||
),
|
||
borderRadius: BorderRadius.circular(8),
|
||
border: Border.all(color: const Color(0xffFFFAEE), width: 0.5),
|
||
),
|
||
child: Text(
|
||
'${config.discountHour}:${config.discountMin}:${config.discountSec}',
|
||
style: const TextStyle(
|
||
color: Color(0xffFFF0D1),
|
||
fontSize: 20,
|
||
fontWeight: FontWeight.w600),
|
||
),
|
||
);
|
||
}
|
||
}
|