初始化
This commit is contained in:
@@ -0,0 +1,510 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:carousel_slider/carousel_slider.dart';
|
||||
import 'package:easy_rich_text/easy_rich_text.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
import 'package:hgdj/tools_base/unique_tag_mixin.dart';
|
||||
import 'package:hgdj/tools_base/widget/sheet_handle_bar.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../hj_model/splash/domain_source_model.dart';
|
||||
import '../../hj_page/main_page/provider/msg_provider.dart';
|
||||
import '../../hj_page/mine/mine_vip/mine_charge_vip_logic.dart';
|
||||
import '../../hj_page/mine/mine_vip/pay_order_source.dart';
|
||||
import '../../hj_page/mine/mine_vip/vip_card_item.dart';
|
||||
import '../../hj_page/mine/mine_vip/vip_pay_button.dart';
|
||||
import '../../hj_page/mine/mine_vip/vip_product_manager.dart';
|
||||
import '../../hj_page/mine/mine_vip/vip_support_model.dart';
|
||||
import '../../hj_page/mine/mine_vip/vip_ui_kit.dart';
|
||||
import '../../routers/jump_router.dart';
|
||||
import '../../tools_base/loading/loading_center_widget.dart';
|
||||
import 'buy_vip_alert_logic.dart';
|
||||
|
||||
///购买视频对话框
|
||||
class BuyVipAlert extends StatefulWidget {
|
||||
final String? vipId; //外部指定卡ID(可选),不传走分层卡/升级卡兜底
|
||||
final String? videoId; //在看的那条内容 id,下单时透传给服务端做归因
|
||||
final PayOrderTrackInfo?
|
||||
orderTrack; //整份下单上下文,短剧付费墙要带 mediaId/contentId/checkoutContextId
|
||||
const BuyVipAlert({super.key, this.vipId, this.videoId, this.orderTrack});
|
||||
|
||||
static Future show(
|
||||
{String? vipId, String? videoId, PayOrderTrackInfo? orderTrack}) {
|
||||
return Get.bottomSheet(
|
||||
BuyVipAlert(vipId: vipId, videoId: videoId, orderTrack: orderTrack),
|
||||
isScrollControlled: true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
State<BuyVipAlert> createState() => _BuyVipAlertState();
|
||||
}
|
||||
|
||||
// 可叠弹(试看结束/超时长等多处触发),用 per-实例唯一 tag 隔离 controller
|
||||
class _BuyVipAlertState extends State<BuyVipAlert> with UniqueTagMixin {
|
||||
late final logic = BuyVipAlertLogic(
|
||||
vId: widget.vipId,
|
||||
videoId: widget.videoId,
|
||||
orderTrack: widget.orderTrack);
|
||||
|
||||
static const _sheetRadius = 26.0;
|
||||
|
||||
/// 顶部可见边:上→下 #66EBD097 → #15EBD097 → #00EBD097(顶边最实,圆角向下渐隐)
|
||||
static const _borderGradient = LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Color(0x66EBD097),
|
||||
Color(0x15EBD097),
|
||||
Color(0x00EBD097),
|
||||
],
|
||||
);
|
||||
|
||||
/// 只订阅「选中卡变化」的局部刷新:标题/价格/卡片选中态/权益/支付按钮用它,
|
||||
/// 滑卡时不再整个弹窗重建(与会员中心页共用 [MineChargeVipLogic.kSelection])
|
||||
Widget _selectionBuilder(Widget Function(BuyVipAlertLogic logic) builder) {
|
||||
return GetBuilder<BuyVipAlertLogic>(
|
||||
tag: uniqueTag,
|
||||
id: MineChargeVipLogic.kSelection,
|
||||
builder: builder,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<BuyVipAlertLogic>(
|
||||
tag: uniqueTag,
|
||||
init: logic,
|
||||
builder: (_) => Material(
|
||||
color: Colors.transparent,
|
||||
child: SizedBox(
|
||||
height: screen.screenHeight * 0.8,
|
||||
width: screen.screenWidth,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(_sheetRadius),
|
||||
topRight: Radius.circular(_sheetRadius),
|
||||
),
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Color(0xff2B2010),
|
||||
Color(0xff1A140B),
|
||||
Color(0xff100C07),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
16.sizeBoxH,
|
||||
const SheetHandleBar(color: Color(0x22FFFFFF)),
|
||||
16.sizeBoxH,
|
||||
// 标题 + 分层倒计时(对齐设计稿:新人特惠/卡名 + 红块 HH:MM:SS)
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_selectionBuilder(
|
||||
(_) => Text(
|
||||
_title,
|
||||
style: const TextStyle(
|
||||
color: Color(0xE5FFFFFF),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
_buildCountdown(),
|
||||
],
|
||||
),
|
||||
14.sizeBoxH,
|
||||
_selectionBuilder(
|
||||
(_) => _buildPriceInfo()), //原价 / 限时特价 / 优惠
|
||||
16.sizeBoxH,
|
||||
Expanded(
|
||||
child: _buildContent(),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// 只描顶部 + 左右上圆角;竖向渐变让顶边最亮、沿圆角向下淡出
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: _sheetRadius + 2,
|
||||
child: IgnorePointer(
|
||||
child: CustomPaint(
|
||||
painter: _BuyVipTopBorderPainter(
|
||||
gradient: _borderGradient,
|
||||
strokeWidth: 1,
|
||||
topRadius: _sheetRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent() {
|
||||
return Consumer<VipProductManager>(
|
||||
builder: (_, mgr, __) {
|
||||
// 与会员中心一致:仅 ACTIVE+B 用改版;A / DISABLED 用 classic
|
||||
final classic = !mgr.isNewVipUi;
|
||||
if (mgr.isLoading) return LoadingCenterWidget();
|
||||
if (mgr.vipCards.isEmpty) return CErrorWidget();
|
||||
return Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
// 会员卡实体
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 6),
|
||||
child: CarouselSlider.builder(
|
||||
carouselController: logic.pageCtr,
|
||||
itemCount: mgr.vipCards.length + 2,
|
||||
itemBuilder: (_, index, __) => _selectionBuilder(
|
||||
(l) => l.instanceChildItem(index)),
|
||||
options: CarouselOptions(
|
||||
height: logic.cardHeight,
|
||||
viewportFraction: logic.itemRatio,
|
||||
initialPage: 0,
|
||||
enableInfiniteScroll: false,
|
||||
reverse: false,
|
||||
autoPlay: false,
|
||||
enlargeCenterPage: false,
|
||||
padEnds: false,
|
||||
clipBehavior: Clip.none,
|
||||
onPageChanged: (index, __) =>
|
||||
logic.onPageChanged(index),
|
||||
scrollDirection: Axis.horizontal,
|
||||
),
|
||||
),
|
||||
),
|
||||
// B:卡皮与核心权益标题间距略收;A 保持原 14
|
||||
(classic ? 14 : 4).sizeBoxH,
|
||||
_buildPrivilegeSections(classic: classic),
|
||||
100.sizeBoxH,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
bottom: screen.paddingBottom + 12,
|
||||
left: 12,
|
||||
right: 12,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_selectionBuilder((l) => VipPayButton(l, classic: classic)),
|
||||
12.sizeBoxH,
|
||||
EasyRichText(
|
||||
'支付问题反馈,点击联系 在线客服',
|
||||
patternList: [
|
||||
EasyRichTextPattern(
|
||||
targetString: '在线客服',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFFFFD460), fontSize: 12),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => pushToCustomService(),
|
||||
)
|
||||
],
|
||||
defaultStyle:
|
||||
const TextStyle(color: Color(0xffDCDCDC), fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 弹窗标题:新人分层固定"新人特惠",其他分层跟随当前选中卡名
|
||||
String get _title {
|
||||
if (MineMsgProvider().payTier?.status == PayTier.newUnpay) return '新人特惠';
|
||||
return logic.currentProductModel?.productName ?? '会员特惠';
|
||||
}
|
||||
|
||||
/// 价格行:原价(标题+价格删除线) / 限时特价 / 优惠
|
||||
Widget _buildPriceInfo() {
|
||||
final p = logic.currentProductModel;
|
||||
if (p == null) return const SizedBox();
|
||||
final original = p.originalPriceUI;
|
||||
final discounted = p.discountedPriceUI;
|
||||
const muted = TextStyle(
|
||||
color: Color(0x8cFFFFFF),
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.lineThrough,
|
||||
decorationColor: Color(0x8cFFFFFF),
|
||||
);
|
||||
const normal = TextStyle(
|
||||
color: Color(0x8cFFFFFF), fontSize: 14, fontWeight: FontWeight.w500);
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_priceItem("原价", "¥$original", muted, titleStyle: muted),
|
||||
24.sizeBoxW,
|
||||
_priceItem("限时特价", "¥$discounted", normal),
|
||||
24.sizeBoxW,
|
||||
_priceItem(
|
||||
"优惠", "¥${(original - discounted).clamp(0, original)}", normal),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _priceItem(String title, String value, TextStyle valueStyle,
|
||||
{TextStyle? titleStyle}) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(title,
|
||||
style: titleStyle ??
|
||||
const TextStyle(color: Color(0x99FFFFFF), fontSize: 14)),
|
||||
4.sizeBoxW,
|
||||
Text(value, style: valueStyle),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 分层倒计时:按 config.lastDiscountTime,有则显示(监听 tick 每秒刷新),过期自动收起
|
||||
Widget _buildCountdown() {
|
||||
return ValueListenableBuilder<int>(
|
||||
valueListenable: MineMsgProvider().tick,
|
||||
builder: (_, __, ___) {
|
||||
final c = MineMsgProvider().payTier?.config;
|
||||
if (c == null || !c.hasDiscountCountdown)
|
||||
return const SizedBox.shrink();
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
12.sizeBoxW,
|
||||
_timeItem(c.discountHour),
|
||||
_colon(),
|
||||
_timeItem(c.discountMin),
|
||||
_colon(),
|
||||
_timeItem(c.discountSec),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _timeItem(String value) {
|
||||
return Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xffE1351F),
|
||||
borderRadius: BorderRadius.circular(3)),
|
||||
child: Text(value,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _colon() => const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2),
|
||||
child:
|
||||
Text(":", style: TextStyle(color: Color(0xffDD001B), fontSize: 20)),
|
||||
);
|
||||
|
||||
/// 权益区:与会员中心同实验分支 —— classic=A/DISABLED,改版=ACTIVE+B
|
||||
Widget _buildPrivilegeSections({required bool classic}) {
|
||||
return _selectionBuilder((l) {
|
||||
final privileges = l.currentProductModel?.newPrivilege ?? [];
|
||||
final coreList = privileges.where((p) => p.isCore == true).toList();
|
||||
final moreList = privileges.where((p) => p.isCore != true).toList();
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
classic
|
||||
? _buildClassicCorePrivileges(coreList)
|
||||
: _buildCorePrivileges(coreList),
|
||||
classic
|
||||
? _buildClassicMorePrivileges(moreList)
|
||||
: _buildMorePrivileges(moreList),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 核心权益:横向滚动卡片 —— B 改版
|
||||
Widget _buildCorePrivileges(List<NewPrivilege> list) {
|
||||
if (list.isEmpty) return const SizedBox.shrink();
|
||||
return Column(
|
||||
children: [
|
||||
const VipCoreSectionTitleImage(),
|
||||
16.sizeBoxH,
|
||||
SizedBox(
|
||||
height: 92,
|
||||
child: ListView.separated(
|
||||
clipBehavior: Clip.none,
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.fromLTRB(16, 2, 16, 26),
|
||||
itemCount: list.length,
|
||||
separatorBuilder: (_, __) => 8.sizeBoxW,
|
||||
itemBuilder: (_, i) => VipCorePrivilegeCard(list[i]),
|
||||
),
|
||||
),
|
||||
16.sizeBoxH,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 核心权益:一行金边方卡横向滑动 —— A / DISABLED
|
||||
Widget _buildClassicCorePrivileges(List<NewPrivilege> list) {
|
||||
if (list.isEmpty) return const SizedBox.shrink();
|
||||
return Column(
|
||||
children: [
|
||||
const VipSectionTitle('我的核心权益', classic: true),
|
||||
16.sizeBoxH,
|
||||
SizedBox(
|
||||
//设计稿一行放得下 4 张多一点,第 5 张露半张提示可以划
|
||||
height: 72,
|
||||
child: ListView.separated(
|
||||
clipBehavior: Clip.none, // 金边有外发光,裁了会缺一圈
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
itemCount: list.length,
|
||||
separatorBuilder: (_, __) => 8.sizeBoxW,
|
||||
itemBuilder: (_, i) => SizedBox(
|
||||
width: 72, child: VipCorePrivilegeCard(list[i], classic: true)),
|
||||
),
|
||||
),
|
||||
16.sizeBoxH,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 更多权益:深色圆角 + 四列网格 —— B 改版
|
||||
Widget _buildMorePrivileges(List<NewPrivilege> list) {
|
||||
if (list.isEmpty) return const SizedBox.shrink();
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0x0DFFFFFF),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const VipMoreSectionTitleImage(),
|
||||
12.sizeBoxH,
|
||||
GridView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 4,
|
||||
mainAxisSpacing: 0,
|
||||
crossAxisSpacing: 14,
|
||||
childAspectRatio: 66 / 110,
|
||||
),
|
||||
itemCount: list.length,
|
||||
itemBuilder: (_, i) => VipProductPrivilegeItem(list[i]),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 会员特权:直接四列网格 —— A / DISABLED
|
||||
Widget _buildClassicMorePrivileges(List<NewPrivilege> list) {
|
||||
if (list.isEmpty) return const SizedBox.shrink();
|
||||
return Column(
|
||||
children: [
|
||||
const VipSectionTitle('我的会员特权', classic: true),
|
||||
12.sizeBoxH,
|
||||
GridView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 4,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 18,
|
||||
childAspectRatio: 63 / 100,
|
||||
),
|
||||
itemCount: list.length,
|
||||
itemBuilder: (_, i) =>
|
||||
VipProductPrivilegeItem(list[i], classic: true),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 仅绘制底部弹窗顶部可见边界(上边 + 左右上圆角)
|
||||
class _BuyVipTopBorderPainter extends CustomPainter {
|
||||
final Gradient gradient;
|
||||
final double strokeWidth;
|
||||
final double topRadius;
|
||||
|
||||
_BuyVipTopBorderPainter({
|
||||
required this.gradient,
|
||||
required this.strokeWidth,
|
||||
required this.topRadius,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width;
|
||||
final r = topRadius;
|
||||
final half = strokeWidth / 2;
|
||||
// Flutter arcTo:0=右、正角度顺时针。左上圆心(r,r) 由 π 扫到 3π/2,右上圆心(w-r,r) 由 3π/2 扫到 2π
|
||||
final path = Path()
|
||||
..moveTo(half, r)
|
||||
..arcTo(
|
||||
Rect.fromCircle(center: Offset(r, r), radius: r - half),
|
||||
math.pi,
|
||||
math.pi / 2,
|
||||
false,
|
||||
)
|
||||
..lineTo(w - r, half)
|
||||
..arcTo(
|
||||
Rect.fromCircle(center: Offset(w - r, r), radius: r - half),
|
||||
math.pi * 1.5,
|
||||
math.pi / 2,
|
||||
false,
|
||||
);
|
||||
|
||||
final paint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = strokeWidth
|
||||
..isAntiAlias = true
|
||||
..strokeJoin = StrokeJoin.round
|
||||
..shader = gradient.createShader(Rect.fromLTWH(0, 0, w, r + 2));
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _BuyVipTopBorderPainter oldDelegate) {
|
||||
return oldDelegate.gradient != gradient ||
|
||||
oldDelegate.strokeWidth != strokeWidth ||
|
||||
oldDelegate.topRadius != topRadius;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import '../../hj_page/mine/mine_vip/mine_charge_vip_logic.dart';
|
||||
import '../../hj_page/mine/mine_vip/pay_order_source.dart';
|
||||
|
||||
class BuyVipAlertLogic extends MineChargeVipLogic {
|
||||
//外部指定卡ID(可选);不传则走分层卡/升级卡兜底,对齐 mrhs
|
||||
//视频场景购买弹窗 → VIDEO_BOTTOM_SHEET
|
||||
BuyVipAlertLogic({String? vId, String? videoId, PayOrderTrackInfo? orderTrack})
|
||||
: super(vipID: vId, sourcePage: PaySourcePage.videoBottomSheet, videoId: videoId, orderTrack: orderTrack);
|
||||
|
||||
@override
|
||||
bool get preferFreshData => false; //弹窗优先用 manager 已加载的缓存,避免重复请求
|
||||
|
||||
@override
|
||||
bool get enableEntryGuidePopup => false; //购买弹窗本身就是付费引导,不再叠「进会员页」倒计时弹窗
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/extension/extensions.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
import 'package:hgdj/tools_base/toast.dart';
|
||||
import 'package:hgdj/tools_base/widget/sheet_handle_bar.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../assets_tool/app_colors.dart';
|
||||
import '../../hj_model/acg/comic_chapters_model.dart';
|
||||
import '../../hj_model/cartoon_media_info.dart';
|
||||
import '../../hj_utils/pay/pay_manager.dart';
|
||||
import '../../routers/jump_router.dart';
|
||||
import '../../tools_base/global_store/store.dart';
|
||||
|
||||
///购买漫画/小说弹窗
|
||||
class ComicBuyAlert extends StatefulWidget {
|
||||
//// 返回值: 1 购买单集, 2 购买全集,null 啥都没购买
|
||||
static Future<int?> show({
|
||||
ComicChapterInfo? curModel, // 单集数据
|
||||
CartoonMediaInfo? mediaInfo, // 全集数据
|
||||
}) async {
|
||||
return await Get.bottomSheet(ComicBuyAlert(
|
||||
mediaInfo: mediaInfo,
|
||||
curModel: curModel,
|
||||
));
|
||||
}
|
||||
|
||||
final ComicChapterInfo? curModel; // 单集数据
|
||||
final CartoonMediaInfo? mediaInfo; // 全集数据
|
||||
|
||||
const ComicBuyAlert({
|
||||
super.key,
|
||||
this.curModel,
|
||||
this.mediaInfo,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ComicBuyAlert> createState() => _ComicBuyAlertState();
|
||||
}
|
||||
|
||||
class _ComicBuyAlertState extends State<ComicBuyAlert> {
|
||||
bool _isBuying = false;
|
||||
|
||||
//整套价格(产品当前只开整套购买)
|
||||
int get _price => widget.mediaInfo?.price ?? 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
//刷完钱包会 notifyListeners,下面的 Consumer 自动重建,不用再 setState
|
||||
globalStore.refreshWallet();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: Consumer<GlobalStore>(
|
||||
builder: (_, store, __) {
|
||||
final enough = (store.wallet?.amount ?? 0) >= _price;
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 18),
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.primaryColor,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SheetHandleBar(),
|
||||
18.sizeBoxH,
|
||||
const Text(
|
||||
'购买作品',
|
||||
style: TextStyle(
|
||||
color: Color(0xE5ffffff),
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
36.sizeBoxH,
|
||||
//作品解锁价
|
||||
Row(
|
||||
children: [
|
||||
const Text('作品解锁',
|
||||
style:
|
||||
TextStyle(color: Color(0xE5ffffff), fontSize: 14)),
|
||||
const Spacer(),
|
||||
GestureDetector(
|
||||
onTap: () => pushToWalletPage(tabPosition: 1),
|
||||
child: Text(
|
||||
'$_price 金币',
|
||||
style: const TextStyle(
|
||||
color: Color(0xffFF9000),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
18.sizeBoxH,
|
||||
Divider(
|
||||
height: 0.5, color: Colors.white.withValues(alpha: .05)),
|
||||
18.sizeBoxH,
|
||||
//我的金币余额 + 充值入口
|
||||
Row(
|
||||
children: [
|
||||
const Text('我的金币',
|
||||
style:
|
||||
TextStyle(color: Color(0xE5ffffff), fontSize: 14)),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'${store.wallet?.amount?.toDouble().fixed(2) ?? 0}',
|
||||
style: const TextStyle(
|
||||
color: Color(0xE5ffffff),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
12.sizeBoxW,
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => pushToWalletPage(tabPosition: 1),
|
||||
child: const Text(
|
||||
'充值',
|
||||
style: TextStyle(
|
||||
color: Color(0xffA4634D),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: Color(0xffA4634D),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
18.sizeBoxH,
|
||||
//支付按钮:余额不足直接去充值
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () => enough
|
||||
? _buyProduct(true)
|
||||
: pushToWalletPage(tabPosition: 1),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 47,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xffF68804),
|
||||
borderRadius: BorderRadius.circular(3)),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
enough ? '$_price金币/立即支付' : '余额不足,前往充值',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
///购买作品:[isAll] true 整本(19),false 单集(25)
|
||||
Future<void> _buyProduct(bool isAll) async {
|
||||
if (_isBuying) return; //纯防重入,build 不依赖它,改了不用 setState
|
||||
_isBuying = true;
|
||||
final productType = isAll ? ProductType.mediaAll : ProductType.mediaChapter;
|
||||
final productID = isAll
|
||||
? (widget.mediaInfo?.id ?? widget.curModel?.mediaId)
|
||||
: widget.curModel?.id;
|
||||
await PayManager().buy(
|
||||
productID,
|
||||
productType,
|
||||
source: 'comic_buy',
|
||||
onSuccess: (data) {
|
||||
globalStore.refreshWallet();
|
||||
showToast("购买成功");
|
||||
Get.back(result: isAll ? 2 : 1);
|
||||
},
|
||||
);
|
||||
_isBuying = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/assets_tool/app_colors.dart';
|
||||
import 'package:hgdj/assets_tool/images.dart';
|
||||
import 'package:hgdj/config/config.dart';
|
||||
import 'package:hgdj/hj_model/user/user_info_model.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
import 'package:hgdj/tools_base/toast.dart';
|
||||
import 'package:hgdj/tools_base/widget/common_dialog.dart';
|
||||
import 'package:hgdj/tools_base/widget/net_image_widget.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
|
||||
import '../../config/address.dart';
|
||||
import '../../hj_model/video_model.dart';
|
||||
import '../../hj_utils/image_util.dart';
|
||||
import '../../tools_base/global_store/store.dart';
|
||||
|
||||
///分享二维码对话框
|
||||
class ShareMediaDialog extends StatefulWidget {
|
||||
final VideoModel? videoModel;
|
||||
final bool needDecrypt; //封面是否需要解密(直播封面不加密,传 false)
|
||||
|
||||
const ShareMediaDialog({
|
||||
super.key,
|
||||
this.videoModel,
|
||||
this.needDecrypt = true,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ShareMediaDialog> createState() => _ShareMediaDialogState();
|
||||
}
|
||||
|
||||
class _ShareMediaDialogState extends State<ShareMediaDialog> {
|
||||
final _shotKey = GlobalKey(); //保存图片时截取的范围
|
||||
|
||||
UserInfoModel? get _me => globalStore.meInfo;
|
||||
|
||||
//推广链接:弹窗打开时取一次即可(二维码/复制都用它)
|
||||
late final String _shareUrl = _me?.promoteURL ?? "";
|
||||
|
||||
/// 复制链接
|
||||
void _onCopyLink() {
|
||||
if (_shareUrl.isEmpty) {
|
||||
showToast("链接生成错误");
|
||||
return;
|
||||
}
|
||||
Clipboard.setData(ClipboardData(text: _shareUrl));
|
||||
showToast("复制成功,快去分享吧");
|
||||
Get.back();
|
||||
}
|
||||
|
||||
/// 保存图片(必须等截图完成再关弹窗,否则 _shotKey 对应的 widget 已销毁)
|
||||
Future<void> _onSaveImg() async {
|
||||
final ok = await ImageUtil.saveWidgetToAlbum(_shotKey);
|
||||
showToast(ok ? "保存成功,快去分享吧" : "保存失败,请重试");
|
||||
if (mounted) Get.back(); // 保存期间弹窗可能已被关掉(连点/点了复制),别多 pop 一层
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonDialog(
|
||||
padding: EdgeInsets.zero,
|
||||
child: RepaintBoundary(
|
||||
key: _shotKey,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 23, vertical: 32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
//app 图标 + 名称 + 标语
|
||||
Row(
|
||||
children: [
|
||||
Image.asset('ic_launcher.webp'.commonImgPath,
|
||||
width: 48.w, height: 48.w),
|
||||
10.w.sizeBoxW,
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
Config.appName,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .9),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
),
|
||||
4.sizeBoxH,
|
||||
Text("亚洲最火成人网站",
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .55),
|
||||
fontSize: 10)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
18.sizeBoxH,
|
||||
//视频封面
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 27),
|
||||
child: NetworkImageLoader(
|
||||
width: double.infinity,
|
||||
imageUrl: widget.videoModel?.cover ?? "",
|
||||
fit: BoxFit.fitHeight,
|
||||
encrypt: widget.needDecrypt,
|
||||
),
|
||||
),
|
||||
18.sizeBoxH,
|
||||
//二维码 + 邀请码 + 官网地址
|
||||
SizedBox(
|
||||
height: 94,
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 94,
|
||||
height: 94,
|
||||
padding: const EdgeInsets.all(2),
|
||||
color: Colors.white,
|
||||
child: QrImageView(
|
||||
data: _shareUrl, padding: EdgeInsets.zero),
|
||||
),
|
||||
16.sizeBoxW,
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"邀请码${_me?.promotionCode ?? ""}",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white.withValues(alpha: .9)),
|
||||
),
|
||||
20.sizeBoxH,
|
||||
Text(
|
||||
'官方网址:${Address.groundUrl ?? ""}',
|
||||
maxLines: 2,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.white.withValues(alpha: .55)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
24.sizeBoxH,
|
||||
//底部两个按钮
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _button(
|
||||
"复制链接",
|
||||
bgColor: Colors.white.withValues(alpha: .1),
|
||||
textColor: const Color(0xff989898),
|
||||
onTap: _onCopyLink,
|
||||
),
|
||||
),
|
||||
16.sizeBoxW,
|
||||
Expanded(
|
||||
child: _button(
|
||||
"保存图片",
|
||||
bgColor: AppColors.actionRed,
|
||||
textColor: Colors.white,
|
||||
onTap: _onSaveImg,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _button(String text,
|
||||
{required Color bgColor,
|
||||
required Color textColor,
|
||||
required VoidCallback onTap}) {
|
||||
return InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 44,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
color: bgColor,
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 16, color: textColor, fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/assets_tool/app_colors.dart';
|
||||
import 'package:hgdj/extension/extensions.dart';
|
||||
import 'package:hgdj/hj_model/video_model.dart';
|
||||
import 'package:hgdj/hj_page/mine/mine_vip/widgets/coin_pay_bottom_sheet.dart';
|
||||
import 'package:hgdj/hj_page/mine/widgets/gradient_text.dart';
|
||||
import 'package:hgdj/hj_utils/pay/pay_manager.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
import 'package:hgdj/routers/jump_router.dart';
|
||||
import 'package:hgdj/tools_base/global_store/store.dart';
|
||||
import 'package:hgdj/tools_base/widget/sheet_handle_bar.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
///金币购买作品面板
|
||||
class VideoBuyBottomSheet extends StatefulWidget {
|
||||
final VideoModel video;
|
||||
|
||||
const VideoBuyBottomSheet({super.key, required this.video});
|
||||
|
||||
@override
|
||||
State<VideoBuyBottomSheet> createState() => _VideoBuyBottomSheetState();
|
||||
}
|
||||
|
||||
class _VideoBuyBottomSheetState extends State<VideoBuyBottomSheet> {
|
||||
VideoModel get _video => widget.video;
|
||||
|
||||
bool get _isCartoon => _video.videoType == 1;
|
||||
|
||||
bool get _isSeedLink => _video.newsType == 'SEED_LINK';
|
||||
|
||||
//图集和黄游金币解锁,没有vip折扣
|
||||
bool get _noDiscount => _video.newsType == "PIC" || _isSeedLink;
|
||||
|
||||
//实际售价:无折扣品类按原价,其余按折后价
|
||||
String get _priceText =>
|
||||
'${(_noDiscount ? _video.originCoins : _video.coins) ?? 0} 金币';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
globalStore.updateUserInfo();
|
||||
globalStore.refreshWallet();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<GlobalStore>(
|
||||
builder: (_, store, __) {
|
||||
final enough = (store.wallet?.amount ?? 0) >= (_video.realCoins ?? 0);
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 18),
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.primaryColor,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SheetHandleBar(),
|
||||
18.sizeBoxH,
|
||||
const Text(
|
||||
'购买作品',
|
||||
style: TextStyle(
|
||||
color: Color(0xE5ffffff),
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
18.sizeBoxH,
|
||||
//作品解锁价
|
||||
Row(
|
||||
children: [
|
||||
const Text('作品解锁',
|
||||
style: TextStyle(color: Color(0xE5ffffff), fontSize: 14)),
|
||||
const Spacer(),
|
||||
GestureDetector(
|
||||
onTap: () => CoinPayBottomSheet.show(),
|
||||
child: Text(
|
||||
_priceText,
|
||||
style: const TextStyle(
|
||||
color: Color(0xffFF9000),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
18.sizeBoxH,
|
||||
//图集和黄游金币解锁,没有vip折扣,不展示原价和折扣说明
|
||||
if (!_noDiscount) ..._discountRows(store),
|
||||
Divider(height: 0.5, color: Colors.white.withValues(alpha: .05)),
|
||||
18.sizeBoxH,
|
||||
//我的金币余额 + 充值入口
|
||||
Row(
|
||||
children: [
|
||||
const Text('我的金币',
|
||||
style: TextStyle(color: Color(0xE5ffffff), fontSize: 14)),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'${store.wallet?.amount?.toDouble().fixed(2) ?? 0}',
|
||||
style: const TextStyle(
|
||||
color: Color(0xE5ffffff),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
12.sizeBoxW,
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => CoinPayBottomSheet.show(),
|
||||
child: const Text(
|
||||
'充值',
|
||||
style: TextStyle(
|
||||
color: Color(0xffA4634D),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: Color(0xffA4634D),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
18.sizeBoxH,
|
||||
//底部两个按钮:立即支付 / 开通会员
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _button(
|
||||
enough ? '$_priceText/立即支付' : '余额不足,前往充值',
|
||||
textColor: const Color(0xff7B0000),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xfff8f3ae), Color(0xffddac44)],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
onTap: () => _onPay(enough),
|
||||
),
|
||||
),
|
||||
12.sizeBoxW,
|
||||
Expanded(
|
||||
child: _button(
|
||||
'开通会员免费看',
|
||||
textColor: Colors.white,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xfff68804),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
onTap: () => pushToWalletPage(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
//原价 + 折扣说明(有 vip 折扣的品类才展示)
|
||||
List<Widget> _discountRows(GlobalStore store) {
|
||||
final isNormalUser = store.meInfo == null || store.meInfo?.vipLevel == 0;
|
||||
final isVip = store.isVIP;
|
||||
final String discountText;
|
||||
if ((_video.coins ?? 0) < (_video.originCoins ?? 0) && isVip) {
|
||||
discountText = '您当前享受${store.meInfo?.payVidDiscount}折优惠';
|
||||
} else if (isVip) {
|
||||
discountText = '您当前不享受免费观看';
|
||||
} else {
|
||||
discountText = '您当前不享受折扣优惠';
|
||||
}
|
||||
return [
|
||||
Row(
|
||||
children: [
|
||||
const Text('作品原价',
|
||||
style: TextStyle(color: Color(0xE5FFFFFF), fontSize: 14)),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'${_video.originCoins ?? 0} 金币',
|
||||
style: TextStyle(
|
||||
color: const Color(0x8CFFFFFF),
|
||||
fontSize: 14,
|
||||
decoration: isNormalUser ? null : TextDecoration.lineThrough,
|
||||
decorationColor: const Color(0x8CFFFFFF),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
18.sizeBoxH,
|
||||
Row(
|
||||
children: [
|
||||
GradientText(
|
||||
discountText,
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xffAC7E6E), Color(0xffA4634D)]),
|
||||
),
|
||||
const Spacer(),
|
||||
GestureDetector(
|
||||
onTap: () => pushToWalletPage(),
|
||||
child: GradientText(
|
||||
isVip ? '升级会员免费看' : '购买VIP免费看',
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xffAC7E6E), Color(0xffA4634D)]),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: Color(0xffA4634D),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
18.sizeBoxH,
|
||||
];
|
||||
}
|
||||
|
||||
Widget _button(String text,
|
||||
{required Color textColor,
|
||||
required BoxDecoration decoration,
|
||||
required VoidCallback onTap}) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 44,
|
||||
alignment: Alignment.center,
|
||||
decoration: decoration,
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
color: textColor, fontSize: 16, fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onPay(bool enough) async {
|
||||
if (!enough) {
|
||||
await CoinPayBottomSheet.show();
|
||||
return;
|
||||
}
|
||||
//黄游(SEED_LINK)和普通视频都按单片算,动漫按整部算
|
||||
final type =
|
||||
(!_isSeedLink && _isCartoon) ? ProductType.mediaAll : ProductType.media;
|
||||
await PayManager().buy(
|
||||
_video.id,
|
||||
type,
|
||||
source: 'video_buy',
|
||||
goldVideoCouponNum: 0,
|
||||
jumpWalletOnInsufficient: false,
|
||||
onSuccess: (data) {
|
||||
_video.vidStatus?.hasPaid = true;
|
||||
Get.back(result: true);
|
||||
},
|
||||
onFailure: (data) {
|
||||
if (data?.code == 8000) {
|
||||
CoinPayBottomSheet.show();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
import 'package:hgdj/tools_base/widget/sheet_handle_bar.dart';
|
||||
|
||||
import '../../config/address.dart';
|
||||
import '../../hj_model/splash/domain_source_model.dart';
|
||||
import '../../tools_base/global_store/store.dart';
|
||||
import '../mine/vip_level_dialog.dart';
|
||||
|
||||
class VideoLineMenuAlert extends StatelessWidget {
|
||||
/// 返回 true 表示切换了线路,调用方据此重新起播
|
||||
static Future<bool> show() async {
|
||||
return await Get.bottomSheet<bool>(VideoLineMenuAlert()) ?? false;
|
||||
}
|
||||
|
||||
VideoLineMenuAlert({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: Container(
|
||||
width: screen.screenWidth,
|
||||
padding: EdgeInsets.only(bottom: screen.paddingBottom),
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xff0F0F0F),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
18.sizeBoxH,
|
||||
const SheetHandleBar(),
|
||||
4.sizeBoxH,
|
||||
...Address.cdnAddressLists.map(_lineItem),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _lineItem(Domain domain) {
|
||||
final isSelected = domain.url == Address.cdnAddress;
|
||||
return InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () {
|
||||
if (!globalStore.isVIP) {
|
||||
showVipLevelDialog("该线路仅限会员使用\n\n"
|
||||
"开通会员立享 极速观影体验");
|
||||
} else if (isSelected) {
|
||||
Get.back();
|
||||
} else {
|
||||
Address.cdnAddress = domain.url;
|
||||
Get.back(result: true); // 带回切换结果,由调用方重新起播
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
height: 56,
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Text(
|
||||
domain.desc ?? "",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: isSelected
|
||||
? Color(0xfff68804)
|
||||
: Colors.white.withValues(alpha: 0.45),
|
||||
fontWeight: isSelected ? FontWeight.w500 : FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
color: Colors.white.withValues(alpha: 0.05),
|
||||
height: 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hgdj/assets_tool/app_colors.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
import 'package:hgdj/routers/jump_router.dart';
|
||||
import 'package:hgdj/tools_base/widget/common_dialog.dart';
|
||||
|
||||
/// 动漫/图集的 VIP 门槛提示弹窗:只有文案和一个开通按钮
|
||||
class VipACGDialog extends StatelessWidget {
|
||||
const VipACGDialog({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonDialog(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
"温馨提示",
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: Colors.white.withValues(alpha: .9),
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
24.sizeBoxH,
|
||||
Text(
|
||||
"本内容需要开通VIP会员\n开通会员 即可解锁查看完整版",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.white.withValues(alpha: .45),
|
||||
height: 2),
|
||||
),
|
||||
24.sizeBoxH,
|
||||
//开通按钮。margin 留在 InkWell 外,否则左右各 12 的空白会被算进点击热区
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => pushToWalletPage(),
|
||||
child: Container(
|
||||
height: 44,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.actionRed,
|
||||
borderRadius: BorderRadius.circular(3)),
|
||||
child: const Text(
|
||||
"立即开通",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user