refactor: 完成项目架构重构,统一模块导入路径
- 清理大量废弃的 barrel 导出文件,移除冗余的中间导出层 - 修复所有相对路径导入错误,统一调整为扁平化模块引用 - 更新多平台 pubspec 版本号与依赖库版本 - 补充后端功能问题管理后台与脚本工具 - 调整部分页面的快捷方式文案适配新功能 - 更新部分翻译覆盖率与API文档
This commit is contained in:
146
lib/core/services/notification/favorite_reminder_service.dart
Normal file
146
lib/core/services/notification/favorite_reminder_service.dart
Normal file
@@ -0,0 +1,146 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — 收藏过期提醒服务
|
||||
/// 创建时间: 2026-06-12
|
||||
/// 更新时间: 2026-06-12
|
||||
/// 作用: 定期检查长期未访问的收藏,发送提醒通知
|
||||
/// 上次更新: 新建
|
||||
/// ============================================================
|
||||
|
||||
import '../../storage/database/app_database.dart';
|
||||
import '../../storage/kv_storage.dart';
|
||||
import '../../utils/logger.dart';
|
||||
import 'notification_service.dart';
|
||||
|
||||
/// 收藏过期提醒服务
|
||||
class FavoriteReminderService {
|
||||
FavoriteReminderService._();
|
||||
|
||||
// ============================================================
|
||||
// 存储键
|
||||
// ============================================================
|
||||
|
||||
static const _lastCheckKey = 'favorite.reminder_last_check';
|
||||
static const _reminderDaysKey = 'favorite.reminder_days';
|
||||
static const _enabledKey = 'favorite.reminder_enabled';
|
||||
static const _dismissedKey = 'favorite.reminder_dismissed';
|
||||
|
||||
/// 默认过期天数
|
||||
static const defaultReminderDays = 30;
|
||||
|
||||
// ============================================================
|
||||
// 检查逻辑
|
||||
// ============================================================
|
||||
|
||||
/// 检查是否需要提醒(每天最多检查一次)
|
||||
static Future<bool> shouldCheck() async {
|
||||
final enabled = KvStorage.getBool(_enabledKey) ?? true;
|
||||
if (!enabled) return false;
|
||||
|
||||
final lastCheck = KvStorage.getInt(_lastCheckKey) ?? 0;
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
final diff = now - lastCheck;
|
||||
// 24小时检查一次
|
||||
return diff >= 24 * 60 * 60 * 1000;
|
||||
}
|
||||
|
||||
/// 执行过期检查,返回过期的收藏数量
|
||||
static Future<int> checkExpiredFavorites() async {
|
||||
try {
|
||||
if (!await shouldCheck()) return 0;
|
||||
|
||||
final db = AppDatabase.instance;
|
||||
final days = KvStorage.getInt(_reminderDaysKey) ?? defaultReminderDays;
|
||||
final cutoff = DateTime.now().subtract(Duration(days: days));
|
||||
|
||||
// 查询收藏时间早于cutoff的收藏
|
||||
final expiredCount = await _countOldFavorites(db, cutoff);
|
||||
|
||||
// 更新最后检查时间
|
||||
KvStorage.setInt(
|
||||
_lastCheckKey,
|
||||
DateTime.now().millisecondsSinceEpoch,
|
||||
);
|
||||
|
||||
// 如果有过期收藏,发送通知
|
||||
if (expiredCount > 0) {
|
||||
await _sendReminderNotification(expiredCount, days);
|
||||
}
|
||||
|
||||
return expiredCount;
|
||||
} catch (e) {
|
||||
Log.e('FavoriteReminderService.checkExpiredFavorites', e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// 统计过期收藏数量
|
||||
static Future<int> _countOldFavorites(
|
||||
AppDatabase db,
|
||||
DateTime cutoff,
|
||||
) async {
|
||||
try {
|
||||
final sentences = await db.getFavoriteSentences();
|
||||
final cutoffMs = cutoff.millisecondsSinceEpoch;
|
||||
return sentences.where((s) {
|
||||
final createdAt = s.createdAt;
|
||||
return createdAt.millisecondsSinceEpoch < cutoffMs;
|
||||
}).length;
|
||||
} catch (e) {
|
||||
Log.e('FavoriteReminderService._countOldFavorites', e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// 发送提醒通知
|
||||
static Future<void> _sendReminderNotification(int count, int days) async {
|
||||
try {
|
||||
await NotificationService.showImmediate(
|
||||
'💝 收藏回忆提醒',
|
||||
'你有 $count 个收藏超过 $days 天未访问,来看看吧',
|
||||
);
|
||||
Log.i('FavoriteReminderService: 已推送收藏过期提醒 ($count个过期)');
|
||||
} catch (e) {
|
||||
Log.e('FavoriteReminderService._sendReminderNotification', e);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Banner 显示控制
|
||||
// ============================================================
|
||||
|
||||
/// 是否已关闭当前提醒Banner
|
||||
static bool get isDismissed =>
|
||||
KvStorage.getBool(_dismissedKey) ?? false;
|
||||
|
||||
/// 关闭提醒Banner
|
||||
static void dismissBanner() => KvStorage.setBool(_dismissedKey, true);
|
||||
|
||||
/// 重置Banner关闭状态(新检查周期开始时调用)
|
||||
static void resetDismissed() => KvStorage.setBool(_dismissedKey, false);
|
||||
|
||||
// ============================================================
|
||||
// 设置读写
|
||||
// ============================================================
|
||||
|
||||
/// 是否启用提醒
|
||||
static bool get isEnabled => KvStorage.getBool(_enabledKey) ?? true;
|
||||
|
||||
/// 设置是否启用
|
||||
static void setEnabled(bool value) => KvStorage.setBool(_enabledKey, value);
|
||||
|
||||
/// 获取提醒天数
|
||||
static int get reminderDays =>
|
||||
KvStorage.getInt(_reminderDaysKey) ?? defaultReminderDays;
|
||||
|
||||
/// 设置提醒天数
|
||||
static void setReminderDays(int days) =>
|
||||
KvStorage.setInt(_reminderDaysKey, days);
|
||||
|
||||
/// 立即检查(用于手动触发)
|
||||
static Future<int> checkNow() async {
|
||||
// 重置检查时间,强制执行
|
||||
KvStorage.setInt(_lastCheckKey, 0);
|
||||
resetDismissed();
|
||||
return checkExpiredFavorites();
|
||||
}
|
||||
}
|
||||
@@ -163,6 +163,9 @@ class LocalNotificationService {
|
||||
|
||||
Log.i('通知点击: payload=$payload');
|
||||
|
||||
// 递增点击计数
|
||||
_trackClickCount(payload);
|
||||
|
||||
final context = rootNavigatorKey.currentContext;
|
||||
if (context == null || !context.mounted) return;
|
||||
|
||||
@@ -191,6 +194,28 @@ class LocalNotificationService {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 点击计数映射
|
||||
// ============================================================
|
||||
|
||||
/// payload → NotificationCenter 计数器类型映射
|
||||
static const _payloadToClickType = <String, String>{
|
||||
'daily_sentence': 'daily_recommend',
|
||||
'signin_reminder': 'signin',
|
||||
'study_progress': 'study_progress',
|
||||
'readlater': 'readlater',
|
||||
'daily_fortune': 'fortune',
|
||||
'marketing': 'marketing',
|
||||
};
|
||||
|
||||
/// 根据 payload 递增对应类型的点击计数
|
||||
static void _trackClickCount(String payload) {
|
||||
final type = _payloadToClickType[payload];
|
||||
if (type != null) {
|
||||
NotificationCenter.incrementClickCount(type);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 构建渠道通知详情
|
||||
// ============================================================
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — 统一通知中心
|
||||
/// 创建时间: 2026-05-22
|
||||
/// 更新时间: 2026-05-22
|
||||
/// 更新时间: 2026-06-12
|
||||
/// 作用: 合并 NotificationScheduler + DailyNotifyService,统一管理所有本地通知调度
|
||||
/// 上次更新: 修复Readlater开关未触发configureAll的问题
|
||||
/// 上次更新: 新增推送计数器(pushCount/clickCount),每次调度/点击时+1
|
||||
/// ============================================================
|
||||
|
||||
import 'local_notification_service.dart';
|
||||
@@ -36,6 +36,22 @@ class NotificationCenter {
|
||||
static const _keyFortuneMinute = 'fortune_minute';
|
||||
static const _keyStudyProgressEnabled = 'study_progress_enabled';
|
||||
static const _keyReadlaterEnabled = 'notif_charging_readlater';
|
||||
static const _keyMarketingPushEnabled = 'marketing_push_enabled';
|
||||
|
||||
// ── 推送计数器存储键 ──
|
||||
|
||||
static const _keyPushCountDailyRecommend = 'push_count_daily_recommend';
|
||||
static const _keyPushCountSignin = 'push_count_signin';
|
||||
static const _keyPushCountStudyProgress = 'push_count_study_progress';
|
||||
static const _keyPushCountReadlater = 'push_count_readlater';
|
||||
static const _keyPushCountFortune = 'push_count_fortune';
|
||||
static const _keyPushCountMarketing = 'push_count_marketing';
|
||||
static const _keyClickCountDailyRecommend = 'click_count_daily_recommend';
|
||||
static const _keyClickCountSignin = 'click_count_signin';
|
||||
static const _keyClickCountStudyProgress = 'click_count_study_progress';
|
||||
static const _keyClickCountReadlater = 'click_count_readlater';
|
||||
static const _keyClickCountFortune = 'click_count_fortune';
|
||||
static const _keyClickCountMarketing = 'click_count_marketing';
|
||||
|
||||
// ── 全局通知开关 ──
|
||||
|
||||
@@ -141,6 +157,80 @@ class NotificationCenter {
|
||||
await configureAll();
|
||||
}
|
||||
|
||||
// ── 营销推送 ──
|
||||
|
||||
static bool get isMarketingPushEnabled =>
|
||||
KvStorage.getBool(_keyMarketingPushEnabled) ?? false;
|
||||
|
||||
static Future<void> setMarketingPushEnabled(bool v) async {
|
||||
await KvStorage.setBool(_keyMarketingPushEnabled, v);
|
||||
}
|
||||
|
||||
// ── 推送计数器 ──
|
||||
|
||||
/// 各类型推送次数
|
||||
static int get pushCountDailyRecommend =>
|
||||
KvStorage.getInt(_keyPushCountDailyRecommend) ?? 0;
|
||||
static int get pushCountSignin =>
|
||||
KvStorage.getInt(_keyPushCountSignin) ?? 0;
|
||||
static int get pushCountStudyProgress =>
|
||||
KvStorage.getInt(_keyPushCountStudyProgress) ?? 0;
|
||||
static int get pushCountReadlater =>
|
||||
KvStorage.getInt(_keyPushCountReadlater) ?? 0;
|
||||
static int get pushCountFortune =>
|
||||
KvStorage.getInt(_keyPushCountFortune) ?? 0;
|
||||
static int get pushCountMarketing =>
|
||||
KvStorage.getInt(_keyPushCountMarketing) ?? 0;
|
||||
|
||||
/// 各类型点击次数
|
||||
static int get clickCountDailyRecommend =>
|
||||
KvStorage.getInt(_keyClickCountDailyRecommend) ?? 0;
|
||||
static int get clickCountSignin =>
|
||||
KvStorage.getInt(_keyClickCountSignin) ?? 0;
|
||||
static int get clickCountStudyProgress =>
|
||||
KvStorage.getInt(_keyClickCountStudyProgress) ?? 0;
|
||||
static int get clickCountReadlater =>
|
||||
KvStorage.getInt(_keyClickCountReadlater) ?? 0;
|
||||
static int get clickCountFortune =>
|
||||
KvStorage.getInt(_keyClickCountFortune) ?? 0;
|
||||
static int get clickCountMarketing =>
|
||||
KvStorage.getInt(_keyClickCountMarketing) ?? 0;
|
||||
|
||||
/// 递增推送计数
|
||||
static Future<void> incrementPushCount(String type) async {
|
||||
final key = 'push_count_$type';
|
||||
final current = KvStorage.getInt(key) ?? 0;
|
||||
await KvStorage.setInt(key, current + 1);
|
||||
}
|
||||
|
||||
/// 递增点击计数
|
||||
static Future<void> incrementClickCount(String type) async {
|
||||
final key = 'click_count_$type';
|
||||
final current = KvStorage.getInt(key) ?? 0;
|
||||
await KvStorage.setInt(key, current + 1);
|
||||
}
|
||||
|
||||
/// 重置所有计数器
|
||||
static Future<void> resetAllCounts() async {
|
||||
final keys = [
|
||||
_keyPushCountDailyRecommend,
|
||||
_keyPushCountSignin,
|
||||
_keyPushCountStudyProgress,
|
||||
_keyPushCountReadlater,
|
||||
_keyPushCountFortune,
|
||||
_keyPushCountMarketing,
|
||||
_keyClickCountDailyRecommend,
|
||||
_keyClickCountSignin,
|
||||
_keyClickCountStudyProgress,
|
||||
_keyClickCountReadlater,
|
||||
_keyClickCountFortune,
|
||||
_keyClickCountMarketing,
|
||||
];
|
||||
for (final key in keys) {
|
||||
await KvStorage.setInt(key, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 核心调度 ──
|
||||
|
||||
static Future<void> configureAll() async {
|
||||
@@ -187,6 +277,7 @@ class NotificationCenter {
|
||||
minute: minute,
|
||||
payload: 'daily_sentence',
|
||||
);
|
||||
await incrementPushCount('daily_recommend');
|
||||
}
|
||||
|
||||
static Future<void> _configureSigninReminder() async {
|
||||
@@ -204,6 +295,7 @@ class NotificationCenter {
|
||||
minute: minute,
|
||||
payload: 'signin_reminder',
|
||||
);
|
||||
await incrementPushCount('signin');
|
||||
}
|
||||
|
||||
static Future<void> _configureSolarTerm() async {
|
||||
@@ -244,6 +336,7 @@ class NotificationCenter {
|
||||
minute: minute,
|
||||
payload: 'daily_fortune',
|
||||
);
|
||||
await incrementPushCount('fortune');
|
||||
}
|
||||
|
||||
static Future<void> _configureStudyProgress() async {
|
||||
@@ -257,6 +350,7 @@ class NotificationCenter {
|
||||
hour: 20,
|
||||
payload: 'study_progress',
|
||||
);
|
||||
await incrementPushCount('study_progress');
|
||||
}
|
||||
|
||||
// ── 节气数据 ──
|
||||
|
||||
@@ -13,7 +13,7 @@ import 'package:battery_plus/battery_plus.dart';
|
||||
import '../../storage/kv_storage.dart';
|
||||
import '../../utils/logger.dart';
|
||||
import 'notification_service.dart';
|
||||
import '../../../features/mine/user_center/services/user_center_service.dart';
|
||||
import '../../../features/user_center/services/user_center_service.dart';
|
||||
|
||||
class ReadlaterReminderService {
|
||||
ReadlaterReminderService._();
|
||||
|
||||
Reference in New Issue
Block a user