chore: 迁移依赖、移除sqlite3_flutter_libs并新增功能
1. 替换hive_flutter为hive_ce_flutter依赖 2. 从各平台插件列表移除sqlite3_flutter_libs 3. 重构API请求体格式,优化历史记录去重逻辑 4. 新增CTC笔记相关功能:桌面小部件、模板模型、本地存储 5. 新增表单收集服务和后台管理接口 6. 优化缓存配置、多语言文案和UI细节 7. 重构首页状态监听组件
This commit is contained in:
@@ -1,18 +1,18 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — Dio HTTP 缓存配置
|
||||
/// 创建时间: 2026-05-27
|
||||
/// 更新时间: 2026-05-30
|
||||
/// 更新时间: 2026-06-15
|
||||
/// 作用: 配置 dio_cache_interceptor 缓存策略
|
||||
/// GET 请求默认缓存5分钟,特定接口可自定义
|
||||
/// 排除需要实时数据的接口(登录、签到等)
|
||||
/// 双层缓存: L1内存(快速) + L2 Hive持久化(重启不丢失)
|
||||
/// 上次更新: 实现DualCacheStore双层缓存,MemCacheStore(L1)+HiveCacheStore(L2)
|
||||
/// 上次更新: 升级dio_cache_interceptor 4.x,hitCacheOnErrorExcept→hitCacheOnNetworkFailure,Nullable<Duration>→Duration?,CacheResponse添加statusCode
|
||||
/// ============================================================
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:hive_ce_flutter/hive_flutter.dart';
|
||||
|
||||
import '../utils/logger.dart';
|
||||
import '../storage/hive_safe_access.dart';
|
||||
@@ -80,6 +80,7 @@ class HiveCacheStore extends CacheStore {
|
||||
'requestDate': resp.requestDate.toIso8601String(),
|
||||
'responseDate': resp.responseDate.toIso8601String(),
|
||||
'url': resp.url,
|
||||
'statusCode': resp.statusCode,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -121,6 +122,7 @@ class HiveCacheStore extends CacheStore {
|
||||
requestDate: DateTime.parse(map['requestDate'] as String),
|
||||
responseDate: DateTime.parse(map['responseDate'] as String),
|
||||
url: map['url'] as String,
|
||||
statusCode: map['statusCode'] as int? ?? 200,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -409,7 +411,7 @@ class CacheConfig {
|
||||
static CacheOptions buildOptions() {
|
||||
return CacheOptions(
|
||||
store: getStore(),
|
||||
hitCacheOnErrorExcept: [401, 403],
|
||||
hitCacheOnNetworkFailure: true,
|
||||
policy: CachePolicy.forceCache,
|
||||
maxStale: const Duration(minutes: 5),
|
||||
);
|
||||
@@ -444,12 +446,12 @@ class CacheConfig {
|
||||
|
||||
final customDuration = getCustomDuration(path);
|
||||
if (customDuration != null) {
|
||||
return baseOptions.copyWith(maxStale: Nullable<Duration>(customDuration));
|
||||
return baseOptions.copyWith(maxStale: customDuration);
|
||||
}
|
||||
|
||||
return baseOptions.copyWith(
|
||||
policy: CachePolicy.refresh,
|
||||
maxStale: const Nullable<Duration>(Duration(minutes: 5)),
|
||||
maxStale: const Duration(minutes: 5),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:hive_ce_flutter/hive_flutter.dart';
|
||||
|
||||
import '../../storage/database/app_database.dart';
|
||||
import '../../storage/kv_storage.dart';
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:hive_ce_flutter/hive_flutter.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import '../../storage/kv_storage.dart';
|
||||
|
||||
@@ -10,7 +10,7 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:hive_ce_flutter/hive_flutter.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — 日历同步服务
|
||||
/// 创建时间: 2026-05-29
|
||||
/// 更新时间: 2026-06-06
|
||||
/// 更新时间: 2026-06-15
|
||||
/// 作用: 跨平台日历事件同步(Android/iOS/HarmonyOS/macOS/Windows)
|
||||
/// 上次更新: 鸿蒙端MethodChannel添加超时保护+MissingPluginException捕获+平台判断早期返回
|
||||
/// 上次更新: 迁移至 device_calendar_plus,适配新 API(DeviceCalendar 单例、CalendarPermissionStatus、listCalendars/createEvent/deleteEvent 新签名)
|
||||
/// ============================================================
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:device_calendar/device_calendar.dart';
|
||||
import 'package:device_calendar_plus/device_calendar_plus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
|
||||
import 'package:xianyan/core/utils/logger.dart';
|
||||
import 'package:xianyan/core/utils/platform/platform_utils.dart' as pu;
|
||||
@@ -43,7 +42,7 @@ class CalendarService {
|
||||
CalendarService._();
|
||||
static final CalendarService instance = CalendarService._();
|
||||
|
||||
final DeviceCalendarPlugin _plugin = DeviceCalendarPlugin();
|
||||
final DeviceCalendar _plugin = DeviceCalendar.instance;
|
||||
String? _calendarId;
|
||||
bool _isAvailable = false;
|
||||
|
||||
@@ -79,11 +78,12 @@ class CalendarService {
|
||||
Log.w('CalendarService: 当前平台不支持日历');
|
||||
return false;
|
||||
}
|
||||
final hasPermissions = await _plugin.hasPermissions();
|
||||
if (hasPermissions.isSuccess && !hasPermissions.data!) {
|
||||
final status = await _plugin.hasPermissions();
|
||||
if (status != CalendarPermissionStatus.granted) {
|
||||
final result = await _plugin.requestPermissions();
|
||||
if (!result.isSuccess || !result.data!) {
|
||||
Log.w('CalendarService: 日历权限被拒绝');
|
||||
if (result != CalendarPermissionStatus.granted &&
|
||||
result != CalendarPermissionStatus.writeOnly) {
|
||||
Log.w('CalendarService: 日历权限被拒绝 ($result)');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -122,23 +122,18 @@ class CalendarService {
|
||||
|
||||
/// 确保闲言专属日历存在,不存在则创建
|
||||
Future<void> _ensureCalendar() async {
|
||||
final calendars = await _plugin.retrieveCalendars();
|
||||
if (calendars.isSuccess && calendars.data != null) {
|
||||
final existing = calendars.data!.where(
|
||||
(c) => c.name == '闲言' || c.name == 'Xianyan',
|
||||
final calendars = await _plugin.listCalendars();
|
||||
final existing = calendars.where(
|
||||
(c) => c.name == '闲言' || c.name == 'Xianyan',
|
||||
);
|
||||
if (existing.isNotEmpty) {
|
||||
_calendarId = existing.first.id;
|
||||
} else {
|
||||
final calendarId = await _plugin.createCalendar(
|
||||
name: '闲言',
|
||||
colorHex: '#007AFF',
|
||||
);
|
||||
if (existing.isNotEmpty) {
|
||||
_calendarId = existing.first.id;
|
||||
} else {
|
||||
final result = await _plugin.createCalendar(
|
||||
'闲言',
|
||||
calendarColor: const Color(0xFF007AFF),
|
||||
localAccountName: '闲言APP',
|
||||
);
|
||||
if (result.isSuccess) {
|
||||
_calendarId = result.data;
|
||||
}
|
||||
}
|
||||
_calendarId = calendarId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +143,7 @@ class CalendarService {
|
||||
|
||||
/// 添加日历事件
|
||||
/// 鸿蒙端:通过_addEventOhos()桥接,通道未实现时返回false
|
||||
/// 注意:device_calendar_plus 暂不支持 Reminder,reminderMinutesBefore 仅鸿蒙端生效
|
||||
Future<bool> addEvent(CalendarEvent event) async {
|
||||
// 鸿蒙端:直接走鸿蒙通道
|
||||
if (_isOhos()) {
|
||||
@@ -160,31 +156,26 @@ class CalendarService {
|
||||
}
|
||||
|
||||
try {
|
||||
final local = tz.local;
|
||||
final calendarEvent = Event(
|
||||
_calendarId,
|
||||
// device_calendar_plus 使用 createEvent 方法,无需手动构造 Event 对象
|
||||
await _plugin.createEvent(
|
||||
calendarId: _calendarId!,
|
||||
title: event.title,
|
||||
startDate: event.start,
|
||||
endDate: event.end,
|
||||
description: event.description,
|
||||
start: tz.TZDateTime.from(event.start, local),
|
||||
end: tz.TZDateTime.from(event.end, local),
|
||||
location: event.location,
|
||||
);
|
||||
|
||||
// device_calendar_plus 暂不支持 Reminder,记录提示
|
||||
if (event.reminderMinutesBefore != null) {
|
||||
calendarEvent.reminders = [
|
||||
Reminder(minutes: event.reminderMinutesBefore!),
|
||||
];
|
||||
Log.w(
|
||||
'CalendarService: device_calendar_plus 暂不支持 Reminder,'
|
||||
'reminderMinutesBefore=${event.reminderMinutesBefore} 已忽略',
|
||||
);
|
||||
}
|
||||
|
||||
final result = await _plugin.createOrUpdateEvent(calendarEvent);
|
||||
if (result?.isSuccess == true) {
|
||||
Log.i('CalendarService: 事件已添加 - ${event.title}');
|
||||
return true;
|
||||
}
|
||||
Log.e(
|
||||
'CalendarService: 添加事件失败 - ${result?.errors.map((e) => e.errorMessage)}',
|
||||
);
|
||||
return false;
|
||||
Log.i('CalendarService: 事件已添加 - ${event.title}');
|
||||
return true;
|
||||
} catch (e) {
|
||||
Log.e('CalendarService: 添加事件异常', e);
|
||||
return false;
|
||||
@@ -216,10 +207,9 @@ class CalendarService {
|
||||
|
||||
/// 删除日历事件
|
||||
Future<bool> deleteEvent(String eventId) async {
|
||||
if (_calendarId == null) return false;
|
||||
try {
|
||||
final result = await _plugin.deleteEvent(_calendarId!, eventId);
|
||||
return result.isSuccess;
|
||||
await _plugin.deleteEvent(eventId: eventId);
|
||||
return true;
|
||||
} catch (e) {
|
||||
Log.e('CalendarService: 删除事件异常', e);
|
||||
return false;
|
||||
|
||||
@@ -11,7 +11,7 @@ import 'dart:isolate';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:hive_ce_flutter/hive_flutter.dart';
|
||||
|
||||
import '../../utils/logger.dart';
|
||||
import '../../utils/platform/platform_utils.dart' as pu;
|
||||
|
||||
@@ -11,7 +11,7 @@ import 'dart:convert';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:hive_ce_flutter/hive_flutter.dart';
|
||||
|
||||
import '../../network/api_client.dart';
|
||||
import '../../storage/kv_storage.dart';
|
||||
|
||||
82
lib/core/services/form/form_collect_service.dart
Normal file
82
lib/core/services/form/form_collect_service.dart
Normal file
@@ -0,0 +1,82 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — 表单收集服务
|
||||
/// 创建时间: 2026-06-15
|
||||
/// 更新时间: 2026-06-15
|
||||
/// 作用: 调用 /api/form_collect/submit 提交表单信息(邮箱等)
|
||||
/// 上次更新: v6.73.0 初始版本
|
||||
/// ============================================================
|
||||
|
||||
import '../../network/api_client.dart';
|
||||
import '../../utils/logger.dart';
|
||||
|
||||
/// 表单来源标识
|
||||
enum FormCollectSource {
|
||||
/// app.html Google Play内测
|
||||
appGpBeta('app_gp_beta'),
|
||||
|
||||
/// 注册页面订阅闲言邮箱
|
||||
registerSubscribe('register_subscribe'),
|
||||
|
||||
/// Beta页面问卷填写Gmail
|
||||
betaQuestionnaire('beta_questionnaire');
|
||||
|
||||
const FormCollectSource(this.value);
|
||||
final String value;
|
||||
}
|
||||
|
||||
/// 表单收集服务
|
||||
class FormCollectService {
|
||||
FormCollectService._();
|
||||
static final FormCollectService instance = FormCollectService._();
|
||||
|
||||
static const String _submitPath = '/api/form_collect/submit';
|
||||
|
||||
/// 提交表单
|
||||
///
|
||||
/// [email] 邮箱地址(必填)
|
||||
/// [source] 来源标识(必填)
|
||||
/// [uid] 用户ID(可选)
|
||||
/// [extraJson] 扩展字段JSON字符串(可选)
|
||||
/// [deviceId] 设备ID(可选)
|
||||
///
|
||||
/// 返回 true 提交成功,false 提交失败
|
||||
Future<bool> submit({
|
||||
required String email,
|
||||
required FormCollectSource source,
|
||||
String? uid,
|
||||
String? extraJson,
|
||||
String? deviceId,
|
||||
}) async {
|
||||
try {
|
||||
final data = <String, dynamic>{
|
||||
'email': email,
|
||||
'source': source.value,
|
||||
};
|
||||
if (uid != null && uid.isNotEmpty) data['uid'] = uid;
|
||||
if (extraJson != null && extraJson.isNotEmpty) {
|
||||
data['extra_json'] = extraJson;
|
||||
}
|
||||
if (deviceId != null && deviceId.isNotEmpty) {
|
||||
data['device_id'] = deviceId;
|
||||
}
|
||||
|
||||
final response = await ApiClient.instance.post<Map<String, dynamic>>(
|
||||
_submitPath,
|
||||
data: data,
|
||||
);
|
||||
|
||||
final result = response.data;
|
||||
final code = result?['code'];
|
||||
if (code == 1) {
|
||||
Log.d('FormCollect: 提交成功 source=${source.value} email=$email');
|
||||
return true;
|
||||
}
|
||||
|
||||
Log.w('FormCollect: 提交失败 code=$code msg=${result?['msg']}');
|
||||
return false;
|
||||
} catch (e) {
|
||||
Log.e('FormCollect: 提交异常 $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:hive_ce_flutter/hive_flutter.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:xianyan/core/utils/logger.dart';
|
||||
import 'database_connection/native.dart'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — Drift原生数据库连接 (Android/iOS/macOS/Windows/Linux/OpenHarmony)
|
||||
/// 创建时间: 2026-04-25
|
||||
/// 更新时间: 2026-05-17
|
||||
/// 更新时间: 2026-06-15
|
||||
/// 作用: 原生平台数据库连接,OpenHarmony 使用 sqflite_ohos 桥接
|
||||
/// 上次更新: 使用pu.isOhos替代Platform.operatingSystem检测,更可靠
|
||||
/// 上次更新: 移除sqlite3_flutter_libs依赖,迁移至sqlite3包
|
||||
/// ============================================================
|
||||
|
||||
import 'dart:io';
|
||||
@@ -12,7 +12,6 @@ import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
|
||||
import 'package:xianyan/core/utils/logger.dart';
|
||||
import 'package:xianyan/core/utils/platform/platform_utils.dart' as pu;
|
||||
|
||||
@@ -28,8 +27,6 @@ QueryExecutor openConnection() {
|
||||
final dbFolder = await getApplicationDocumentsDirectory();
|
||||
final file = File(p.join(dbFolder.path, 'xianyan.db'));
|
||||
|
||||
await applyWorkaroundToOpenSqlite3OnOldAndroidVersions();
|
||||
|
||||
return NativeDatabase.createInBackground(file);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:hive_ce_flutter/hive_flutter.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:xianyan/core/utils/logger.dart';
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:hive_ce_flutter/hive_flutter.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../utils/logger.dart';
|
||||
|
||||
71
lib/core/utils/data/bounded_collection_manager.dart
Normal file
71
lib/core/utils/data/bounded_collection_manager.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — 有界集合管理器
|
||||
/// 创建时间: 2026-06-15
|
||||
/// 更新时间: 2026-06-15
|
||||
/// 作用: 提供固定大小的集合管理,自动清理超出限制的元素
|
||||
/// 上次更新: 初始创建,从HomeNotifier中提取重复的有界集合逻辑
|
||||
/// ============================================================
|
||||
|
||||
/// 有界集合管理器
|
||||
///
|
||||
/// 当集合元素数量达到 [maxSize] 时,再次添加会先清空整个集合再添加新元素。
|
||||
/// 这种策略适用于去重场景:当集合过大时,旧数据已无参考价值,直接重置。
|
||||
///
|
||||
/// 典型用法:
|
||||
/// ```dart
|
||||
/// final seenIds = BoundedCollectionManager<String>(maxSize: 5000);
|
||||
/// seenIds.add('id_1');
|
||||
/// seenIds.addAll(['id_2', 'id_3']);
|
||||
/// if (seenIds.contains('id_1')) { ... }
|
||||
/// ```
|
||||
class BoundedCollectionManager<T> {
|
||||
/// 集合最大容量
|
||||
final int maxSize;
|
||||
|
||||
/// 内部存储
|
||||
final Set<T> _collection = {};
|
||||
|
||||
BoundedCollectionManager({required this.maxSize});
|
||||
|
||||
// ── 添加操作 ──
|
||||
|
||||
/// 添加单个元素,如果集合已满则清空后添加
|
||||
void add(T item) {
|
||||
if (_collection.length >= maxSize) {
|
||||
_collection.clear();
|
||||
}
|
||||
_collection.add(item);
|
||||
}
|
||||
|
||||
/// 批量添加元素
|
||||
void addAll(Iterable<T> items) {
|
||||
for (final item in items) {
|
||||
add(item);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 查询操作 ──
|
||||
|
||||
/// 检查是否包含元素
|
||||
bool contains(T item) => _collection.contains(item);
|
||||
|
||||
/// 获取集合大小
|
||||
int get length => _collection.length;
|
||||
|
||||
/// 集合是否为空
|
||||
bool get isEmpty => _collection.isEmpty;
|
||||
|
||||
/// 集合是否非空
|
||||
bool get isNotEmpty => _collection.isNotEmpty;
|
||||
|
||||
/// 获取不可修改的集合视图
|
||||
Set<T> get unmodifiable => Set.unmodifiable(_collection);
|
||||
|
||||
/// 转换为 List(用于API传参)
|
||||
List<T> toList() => _collection.toList();
|
||||
|
||||
// ── 修改操作 ──
|
||||
|
||||
/// 清空集合
|
||||
void clear() => _collection.clear();
|
||||
}
|
||||
Reference in New Issue
Block a user