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:
Developer
2026-06-15 10:04:52 +08:00
parent af14ed4121
commit ad00967c68
90 changed files with 4728 additions and 1028 deletions

View File

@@ -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.xhitCacheOnErrorExcept→hitCacheOnNetworkFailureNullable<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),
);
}

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -1,19 +1,18 @@
/// ============================================================
/// 闲言APP — 日历同步服务
/// 创建时间: 2026-05-29
/// 更新时间: 2026-06-06
/// 更新时间: 2026-06-15
/// 作用: 跨平台日历事件同步Android/iOS/HarmonyOS/macOS/Windows
/// 上次更新: 鸿蒙端MethodChannel添加超时保护+MissingPluginException捕获+平台判断早期返回
/// 上次更新: 迁移至 device_calendar_plus适配新 APIDeviceCalendar 单例、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 暂不支持 ReminderreminderMinutesBefore 仅鸿蒙端生效
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;

View File

@@ -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;

View File

@@ -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';

View 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;
}
}
}

View File

@@ -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'

View File

@@ -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);
});
}

View 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';

View File

@@ -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';

View 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();
}