初始化
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hgdj/hj_utils/const.dart';
|
||||
|
||||
class BaseBottomProvider extends ChangeNotifier {
|
||||
bool showTop = false; //选中的 tab 是否变成「回到顶部」
|
||||
|
||||
final _edge = 500.0; //滚动临界值
|
||||
final _ctrCaches = <int, ScrollController>{};
|
||||
ScrollController? _curCtr;
|
||||
|
||||
/// 绑定第 [index] 个 tab 的滚动控制器,[controller] 不传则复用缓存里的
|
||||
void setScrollController(int index,
|
||||
{ScrollController? controller, bool forceToTop = false}) {
|
||||
controller ??= _ctrCaches[index];
|
||||
if (controller == null) {
|
||||
_curCtr?.removeListener(_onScroll); //没有可绑的目标,旧监听也一并摘掉
|
||||
return;
|
||||
}
|
||||
_ctrCaches[index] = controller;
|
||||
showTop = !forceToTop && controller.hasClients && controller.offset > _edge;
|
||||
_bind(controller);
|
||||
}
|
||||
|
||||
//换绑监听目标:摘旧挂新,notify 排到下一帧,避免在 build 期间通知
|
||||
void _bind(ScrollController ctr) {
|
||||
_curCtr?.removeListener(_onScroll);
|
||||
_curCtr = ctr;
|
||||
ctr.addListener(_onScroll);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => notifyListeners());
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
final overEdge = (_curCtr?.offset ?? 0) > _edge;
|
||||
if (overEdge != showTop) {
|
||||
showTop = overEdge;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void scrollToTop() {
|
||||
if (_curCtr?.hasClients == true) {
|
||||
_curCtr?.jumpTo(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HomeBottomProvider extends BaseBottomProvider {
|
||||
static final HomeBottomProvider _instance = HomeBottomProvider._();
|
||||
|
||||
factory HomeBottomProvider() => _instance;
|
||||
|
||||
HomeBottomProvider._();
|
||||
}
|
||||
|
||||
class DarkwebBottomProvider extends BaseBottomProvider {
|
||||
static final DarkwebBottomProvider _instance = DarkwebBottomProvider._();
|
||||
|
||||
factory DarkwebBottomProvider() => _instance;
|
||||
|
||||
DarkwebBottomProvider._();
|
||||
}
|
||||
|
||||
class CommunityBottomProvider extends BaseBottomProvider {
|
||||
static final CommunityBottomProvider _instance = CommunityBottomProvider._();
|
||||
|
||||
factory CommunityBottomProvider() => _instance;
|
||||
|
||||
CommunityBottomProvider._();
|
||||
|
||||
//社区分区(社区/图集/黄游/小说) → 各 tab 的滚动控制器
|
||||
final _caches = <MediaStyle, Map<int, ScrollController>>{};
|
||||
final _typeIndexes = <MediaStyle, int>{};
|
||||
|
||||
MediaStyle _type = MediaStyle.Community;
|
||||
|
||||
/// 取某分区某 tab 的滚动控制器(没有就建并缓存),第一个 tab 顺带绑定监听
|
||||
ScrollController scrollCtr(MediaStyle type, int tabIndex) {
|
||||
final ctr = (_caches[type] ??= {})[tabIndex] ??= ScrollController();
|
||||
if (tabIndex == 0) _bind(ctr);
|
||||
return ctr;
|
||||
}
|
||||
|
||||
set communityType(MediaStyle t) {
|
||||
_type = t;
|
||||
_updateState();
|
||||
}
|
||||
|
||||
set subIndex(int index) {
|
||||
_typeIndexes[_type] = index;
|
||||
_updateState();
|
||||
}
|
||||
|
||||
void _updateState() {
|
||||
final ctr = _caches[_type]?[_typeIndexes[_type] ?? 0];
|
||||
if (ctr == null) return;
|
||||
showTop = ctr.hasClients && ctr.offset > _edge;
|
||||
_bind(ctr);
|
||||
}
|
||||
|
||||
@Deprecated('请使用 scrollCtr / subIndex')
|
||||
@override
|
||||
void setScrollController(int index,
|
||||
{ScrollController? controller, bool forceToTop = false}) {}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hgdj/hj_model/splash/domain_source_model.dart';
|
||||
import 'package:hgdj/hj_page/mine/mine_vip/vip_product_manager.dart';
|
||||
import 'package:hgdj/hj_utils/api_service/common_service.dart';
|
||||
import 'package:hgdj/hj_utils/api_service/mine_service.dart';
|
||||
import 'package:hgdj/tools_base/global_store/store.dart';
|
||||
|
||||
import '../../../hj_utils/light_model.dart';
|
||||
|
||||
/// 我的未读消息,和分层优惠倒计时
|
||||
class MineMsgProvider with ChangeNotifier {
|
||||
static final MineMsgProvider _instance = MineMsgProvider._();
|
||||
MineMsgProvider._();
|
||||
factory MineMsgProvider() => _instance;
|
||||
|
||||
bool _hasNewMsg = false; //消息中心小红点
|
||||
|
||||
/// 优惠倒计时配置(全站唯一数据源),无配置/已过期时为 null
|
||||
PayTierConfig? get countdownConfig {
|
||||
final config = payTier?.config;
|
||||
return config?.hasDiscountCountdown == true ? config : null;
|
||||
}
|
||||
|
||||
void loadData() {
|
||||
getMessageTip();
|
||||
refreshPayPopup();
|
||||
}
|
||||
|
||||
Future<void> getMessageTip() async {
|
||||
final res = await MineService.checkMessageTip();
|
||||
_hasNewMsg = res?.newsTip ?? false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// ========== 支付状态分层(PayTier)==========
|
||||
/// 支付状态分层配置(分层/卡ID/各展示位图片)——全站唯一数据源,原 Config.paymentStatusPopup 迁来此统一管理
|
||||
PayTierModel? payTier;
|
||||
|
||||
static String get _payPopupShownKey =>
|
||||
"PayLayeredPopup_shown_${globalStore.meInfo?.uid}"; // 支付分层首弹是否已弹过
|
||||
final ValueNotifier<int> tick = ValueNotifier<int>(
|
||||
0); //分层倒计时秒级 tick,倒计时块用 ValueListenableBuilder 监听它,避免每秒重建整卡/图片
|
||||
Timer? _payTimer; //支付分层倒计时(基于 config.lastDiscountTime)
|
||||
int? _lastExpireRefreshAt; //上次"归零刷新"的时间戳(秒),用于 30s 防抖
|
||||
|
||||
/// 刷新用户分层弹窗配置(payTier),成功后写回并通知 UI 刷新
|
||||
Future<PayTierModel?> refreshPayPopup() async {
|
||||
final model = await CommonService.refreshPayPopup(
|
||||
keys: ['paymentPopup', 'paymentGuide']);
|
||||
// 容错:接口返回空数据时 model 为 null,保留本地状态不替换
|
||||
if (model != null) {
|
||||
// 分层状态前后不一致 → 用户权益/升级状态已变(VIP 支付是异步的,以分层变化为准),清会员卡缓存下次重开强制重拉
|
||||
final statusChanged = payTier?.status != model.status;
|
||||
payTier = model;
|
||||
if (statusChanged) {
|
||||
vipProductManager.invalidate();
|
||||
}
|
||||
startPayTimer(); //刷新后按新的截止时刻重启倒计时
|
||||
notifyListeners();
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
/// 启动支付分层倒计时:仅当优惠尚未截止才开,每秒自增 [tick] 驱动倒计时块刷新,过期自动停。
|
||||
/// 注意:倒计时块用 ValueListenableBuilder(MineMsgProvider().tick) 监听(只重建倒计时);
|
||||
/// 整卡/横幅/浮窗的图片仍用 Consumer<MineMsgProvider>(配置刷新时才重建)。
|
||||
void startPayTimer() {
|
||||
_payTimer?.cancel();
|
||||
_payTimer = null;
|
||||
final config = payTier?.config;
|
||||
if (config == null || !config.hasDiscountCountdown) return;
|
||||
_payTimer = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||
// 过期:停表 + 收起倒计时,并拉一次最新分层(优惠结束后分层大概率已变)
|
||||
if (!(payTier?.config?.hasDiscountCountdown ?? false)) {
|
||||
_payTimer?.cancel();
|
||||
_payTimer = null;
|
||||
_refreshOnExpire();
|
||||
}
|
||||
tick.value++;
|
||||
});
|
||||
}
|
||||
|
||||
/// 支付分层首弹是否已弹过(不分新人/老人,全局只弹一次)
|
||||
static Future<bool> isPayPopupShown() async =>
|
||||
await lightKV.getBool(_payPopupShownKey) ?? false;
|
||||
|
||||
static Future<void> markPayPopupShown() async =>
|
||||
await lightKV.setBool(_payPopupShownKey, true);
|
||||
|
||||
/// 倒计时归零后刷新分层配置(带 30s 防抖,避免后端返回临界时间导致频繁刷新)
|
||||
void _refreshOnExpire() {
|
||||
final nowSec = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
if (_lastExpireRefreshAt != null && nowSec - _lastExpireRefreshAt! < 30)
|
||||
return;
|
||||
_lastExpireRefreshAt = nowSec;
|
||||
refreshPayPopup(); // 成功后内部会按新 lastDiscountTime 重启/不重启计时
|
||||
}
|
||||
|
||||
//红点view
|
||||
Widget buildTipsView() {
|
||||
return _hasNewMsg
|
||||
? Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xfff74f49),
|
||||
borderRadius: BorderRadius.all(Radius.circular(6)),
|
||||
),
|
||||
)
|
||||
: SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
class NewMessageTip {
|
||||
bool? newsTip;
|
||||
|
||||
NewMessageTip({this.newsTip});
|
||||
|
||||
NewMessageTip.fromJson(dynamic json) {
|
||||
newsTip = json['newsTip'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user