diff --git a/lib/main.dart b/lib/main.dart index fab8546..0582d60 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,7 @@ import 'utils/theme/app_theme.dart'; import 'utils/app_router.dart'; import 'utils/user_prefs.dart'; import 'utils/sync/auto_backup_service.dart'; +import 'utils/sync/server_sync_service.dart'; import 'utils/usage_stats_service.dart'; import 'providers/app_provider.dart'; import 'package:flutter/widget_previews.dart'; @@ -18,7 +19,7 @@ void main() async { await UserPrefs.init(); final appProvider = AppProvider(); runApp(MyApp(appProvider: appProvider)); - unawaited(appProvider.initDatabase().then((_) => appProvider.initMainTabIndex())); + unawaited(_validateSyncOnStartup().then((_) => appProvider.initDatabase().then((_) => appProvider.initMainTabIndex()))); unawaited(_initAutoBackup()); unawaited(_initUsageStats()); } @@ -42,6 +43,34 @@ Future _initUsageStats() async { } } +/// 启动时校验同步激活码:有效则继续,过期/失效则下载数据并关闭同步 +Future _validateSyncOnStartup() async { + try { + final prefs = UserPrefs(); + if (!prefs.syncEnabled || prefs.syncServerUrl.isEmpty || prefs.syncActivationCode.isEmpty) { + return; // 未开启同步,跳过 + } + + debugPrint('[Startup] 校验同步激活码...'); + final result = await ServerSyncService.instance.checkActivation(); + + if (result != null && result['valid'] == true) { + // 激活码有效,更新有效期信息 + await prefs.setSyncExpiresAt(result['expires_at'] ?? ''); + await prefs.setSyncIsPermanent(result['is_permanent'] == true); + debugPrint('[Startup] 激活码有效,继续同步模式'); + } else { + // 激活码无效/过期,下载服务端数据并关闭同步 + debugPrint('[Startup] 激活码失效: ${result?['error'] ?? '未知'},关闭同步并下载数据'); + await ServerSyncService.instance.downloadToLocal(); + await prefs.setSyncEnabled(false); + debugPrint('[Startup] 已切换到本地模式'); + } + } catch (e) { + debugPrint('[Startup] 激活码校验异常: $e'); + } +} + class MyApp extends StatefulWidget { final AppProvider appProvider; const MyApp({super.key, required this.appProvider}); diff --git a/lib/pages/note/note_form_page.dart b/lib/pages/note/note_form_page.dart index 581d1f3..f13deab 100644 --- a/lib/pages/note/note_form_page.dart +++ b/lib/pages/note/note_form_page.dart @@ -549,14 +549,19 @@ class _NoteFormPageState extends State { } /// 显示添加标签对话框 - void _showAddTagDialog() { + Future _showAddTagDialog() async { final controller = TextEditingController(); - // 获取所有已有标签(从所有笔记中收集) + // 从 tags 表获取已有标签(sync 模式下走服务端 API) final provider = context.read(); - final allTags = _getAllExistingTags(provider); + final tagRows = await provider.getTags('note_tag'); + final allTags = tagRows.map((t) => t['name'] as String).toSet(); + // 也从当前笔记内容中收集 + for (final note in provider.notes) { + allTags.addAll(note.tags); + } // 过滤掉已添加的标签 - final availableTags = allTags.where((tag) => !_tags.contains(tag)).toList(); + final availableTags = allTags.where((tag) => !_tags.contains(tag)).toList()..sort(); showDialog( context: context, @@ -713,14 +718,6 @@ class _NoteFormPageState extends State { } /// 获取所有已有标签(从所有笔记中收集) - List _getAllExistingTags(AppProvider provider) { - final allTags = {}; - for (final note in provider.notes) { - allTags.addAll(note.tags); - } - return allTags.toList()..sort(); - } - /// 添加标签 void _addTag(String tag) { final trimmed = tag.trim(); diff --git a/lib/pages/sync/server_sync_page.dart b/lib/pages/sync/server_sync_page.dart index 1a95c0a..81180ea 100644 --- a/lib/pages/sync/server_sync_page.dart +++ b/lib/pages/sync/server_sync_page.dart @@ -48,7 +48,10 @@ class _ServerSyncPageState extends State { _isActivated = url.isNotEmpty && code.isNotEmpty; _syncEnabled = _isActivated && _prefs.syncEnabled; _updateExpiresText(); - if (_isActivated) _startStatusPolling(); + if (_isActivated) { + _startStatusPolling(); + _checkStatus(); // 立即向服务端验证 + } } void _updateExpiresText() { diff --git a/lib/providers/app_provider.dart b/lib/providers/app_provider.dart index d2159c3..2d470b7 100644 --- a/lib/providers/app_provider.dart +++ b/lib/providers/app_provider.dart @@ -582,19 +582,34 @@ class AppProvider extends ChangeNotifier { // ========== 标签管理方法 ========== Future>> getTags(String type) async { + if (_useRemote) { + return await ServerDataService.instance.getTags(type.isEmpty ? null : type); + } return await _tagDao.getTagsByType(type); } Future addTag(String name, String type) async { final id = await _tagDao.addTag(name, type); + if (_useRemote) { + await ServerDataService.instance.saveTag(name, type); + } await _reloadByTagType(type); return id; } Future renameTag(String tagId, String newName, String type) async { + final tag = await _tagDao.getTagById(tagId); + final oldName = tag?['name'] as String?; final result = await _tagDao.renameTag(tagId, newName); if (result) { - await _reloadByTagType(type); + if (_useRemote) { + if (oldName != null) { + await ServerDataService.instance.deleteTagByName(oldName, type); + } + await ServerDataService.instance.saveTag(newName, type); + await _pushAffectedByType(type); // 先推到服务端 + } + await _reloadByTagType(type); // 再从服务端拉最新 } return result; } @@ -602,15 +617,38 @@ class AppProvider extends ChangeNotifier { Future deleteTag(String tagId, String type, {String? replacementName}) async { await _tagDao.deleteTag(tagId, replacementName: replacementName); - await _reloadByTagType(type); + if (_useRemote) { + await ServerDataService.instance.deleteTag(tagId); + if (replacementName != null) { + await ServerDataService.instance.saveTag(replacementName, type); + } + await _pushAffectedByType(type); // 先推到服务端 + } + await _reloadByTagType(type); // 再从服务端拉最新 } /// 仅删除标签本身,不级联影响已有条目 Future deleteTagOnly(String tagId, String type) async { await _tagDao.deleteTagOnly(tagId); + if (_useRemote) { + await ServerDataService.instance.deleteTag(tagId); + } await _reloadByTagType(type); } + /// 把某类型的所有条目推送到服务端(标签级联后调用) + Future _pushAffectedByType(String type) async { + final server = ServerDataService.instance; + switch (type) { + case 'movie_genre': + for (final m in _movies) { await server.saveMovie(m); } + case 'book_genre': + for (final b in _books) { await server.saveBook(b); } + case 'note_tag': + for (final n in _notes) { await server.saveNote(n); } + } + } + /// 从影视/书籍/笔记数据中解析标签,同步到 tags 表 Future syncTagsFromData() async { final db = await DatabaseHelper.instance.database; diff --git a/lib/utils/sync/server_data_service.dart b/lib/utils/sync/server_data_service.dart index e9b7ae7..19ef300 100644 --- a/lib/utils/sync/server_data_service.dart +++ b/lib/utils/sync/server_data_service.dart @@ -257,6 +257,11 @@ class ServerDataService { return data != null; } + Future deleteTagByName(String name, String type) async { + final data = await _post('/api/data/tag/delete_by_name', {'name': name, 'type': type}); + return data != null; + } + // ─── 图片 ──────────────────────────────────────────────────── /// 是否激活(AppProvider 也会用这个检查)