初始化

This commit is contained in:
谢宇宁
2026-09-15 15:44:13 +07:00
commit a23ba685e9
1065 changed files with 101122 additions and 0 deletions
@@ -0,0 +1,138 @@
import 'package:flutter/material.dart';
import 'package:hgdj/config/config.dart';
import 'package:hgdj/hj_model/home/update_marker_model.dart';
import 'package:hgdj/hj_utils/api_service/common_service.dart';
import 'package:hgdj/hj_utils/light_model.dart';
import 'package:hgdj/hj_utils/store_keys.dart';
import 'package:hgdj/tools_base/debug_log.dart';
import 'package:hgdj/tools_base/global_store/store.dart';
/// 首页「最新 / 今日最新」等内容更新红点
class HomeUpdateMarkerProvider with ChangeNotifier {
static final HomeUpdateMarkerProvider _instance =
HomeUpdateMarkerProvider._();
HomeUpdateMarkerProvider._();
factory HomeUpdateMarkerProvider() => _instance;
HomeUpdateMarkersResp? _markers;
String? _lastViewHomeLatestAt;
String? _lastViewTodayLatestAt;
final Map<String, String> _lastViewModuleAt = {};
bool get showHomeLatestDot =>
_isNewer(_markers?.homeLatestAt, _lastViewHomeLatestAt);
bool get showTodayLatestDot =>
_isNewer(_markers?.todayLatestAt, _lastViewTodayLatestAt);
bool showModuleDot(String? moduleId) {
if (moduleId == null || moduleId.isEmpty) return false;
return _isNewer(_latestAtOf(moduleId), _lastViewModuleAt[moduleId]);
}
/// 热门短剧专题 Tab:仅对 `dramaPage` 里的模块判断未读
bool showDramaTopicDot(String? topicId) {
if (!_isDramaPageModule(topicId)) return false;
return showModuleDot(topicId);
}
String? _latestAtOf(String moduleId) {
for (final m in _markers?.modules ?? const <HomeModuleUpdateMarker>[]) {
if (m.moduleId == moduleId) return m.latestAt;
}
return null;
}
bool _isDramaPageModule(String? id) {
if (id == null || id.isEmpty) return false;
return Config.plateModule?.dramaPage.any((e) => e.id == id) == true;
}
/// 拉取标记并与本地最后查看时间对比
Future<void> loadData() async {
try {
final resp = await CommonService.fetchUpdateMarkers();
if (resp != null) {
_markers = resp;
}
await _loadLocalLastViewed();
notifyListeners();
} catch (e) {
debugLog('HomeUpdateMarkerProvider.loadData error: $e');
}
}
Future<void> markHomeLatestViewed() async {
final at = _markers?.homeLatestAt;
if (at == null || at.isEmpty) return;
if (_lastViewHomeLatestAt == at) return;
_lastViewHomeLatestAt = at;
await lightKV.setString(StoreKeys.HOME_LATEST_LAST_VIEW_AT, at);
notifyListeners();
}
Future<void> markTodayLatestViewed() async {
final at = _markers?.todayLatestAt;
if (at == null || at.isEmpty) return;
if (_lastViewTodayLatestAt == at) return;
_lastViewTodayLatestAt = at;
await lightKV.setString(StoreKeys.HOME_TODAY_LATEST_LAST_VIEW_AT, at);
notifyListeners();
}
Future<void> markModuleViewed(String? moduleId) async {
if (moduleId == null || moduleId.isEmpty) return;
final latest = _latestAtOf(moduleId);
if (latest == null || latest.isEmpty) return;
if (_lastViewModuleAt[moduleId] == latest) return;
_lastViewModuleAt[moduleId] = latest;
await lightKV.setString(_moduleViewKey(moduleId), latest);
notifyListeners();
}
/// 8×8 红点,与消息中心一致
static Widget buildRedDot({bool visible = true}) {
if (!visible) return const SizedBox.shrink();
return Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
color: Color(0xfff74f49),
shape: BoxShape.circle,
),
);
}
Future<void> _loadLocalLastViewed() async {
_lastViewHomeLatestAt =
await lightKV.getString(StoreKeys.HOME_LATEST_LAST_VIEW_AT);
_lastViewTodayLatestAt =
await lightKV.getString(StoreKeys.HOME_TODAY_LATEST_LAST_VIEW_AT);
_lastViewModuleAt.clear();
for (final m in _markers?.modules ?? const <HomeModuleUpdateMarker>[]) {
final id = m.moduleId;
if (id == null || id.isEmpty) continue;
final v = await lightKV.getString(_moduleViewKey(id));
if (v != null && v.isNotEmpty) {
_lastViewModuleAt[id] = v;
}
}
}
String _moduleViewKey(String moduleId) {
final uid = globalStore.meInfo?.uid ?? 0;
return '${StoreKeys.HOME_MODULE_LAST_VIEW_AT_PREFIX}${uid}_$moduleId';
}
/// 服务端最新时间晚于本地已查看时间则显示红点;本地为空且服务端有值也显示
static bool _isNewer(String? serverAt, String? localAt) {
if (serverAt == null || serverAt.isEmpty) return false;
if (localAt == null || localAt.isEmpty) return true;
final server = DateTime.tryParse(serverAt);
final local = DateTime.tryParse(localAt);
if (server == null || local == null) {
return serverAt != localAt;
}
return server.isAfter(local);
}
}