From faa31e7d08986e3d19ac9831809b3b72269ed063 Mon Sep 17 00:00:00 2001 From: lauirf <592184299@qq.com> Date: Mon, 13 Jan 2025 00:07:06 +0800 Subject: [PATCH 1/6] add plugin drag feature --- lib/pages/init_page.dart | 3 +- .../plugin_editor/plugin_editor_page.dart | 2 +- lib/pages/plugin_editor/plugin_view_page.dart | 249 +++++++----------- lib/plugins/plugins_controller.dart | 146 ++++++---- 4 files changed, 198 insertions(+), 202 deletions(-) diff --git a/lib/pages/init_page.dart b/lib/pages/init_page.dart index 9847f66a..cdbe982c 100644 --- a/lib/pages/init_page.dart +++ b/lib/pages/init_page.dart @@ -83,7 +83,7 @@ class _InitPageState extends State { Future _pluginInit() async { String statementsText = ''; try { - await pluginsController.loadPlugins(); + await pluginsController.loadAllPlugins(); statementsText = await rootBundle.loadString("assets/statements/statements.txt"); _pluginUpdate(); @@ -113,7 +113,6 @@ class _InitPageState extends State { onPressed: () async { try { await pluginsController.copyPluginsToExternalDirectory(); - await pluginsController.loadPlugins(); } catch (_) {} KazumiDialog.dismiss(); Modular.to.navigate('/tab/popular/'); diff --git a/lib/pages/plugin_editor/plugin_editor_page.dart b/lib/pages/plugin_editor/plugin_editor_page.dart index 1eef9ae5..83c82fdf 100644 --- a/lib/pages/plugin_editor/plugin_editor_page.dart +++ b/lib/pages/plugin_editor/plugin_editor_page.dart @@ -201,7 +201,7 @@ class _PluginEditorPageState extends State { plugin.usePost = usePost; plugin.useLegacyParser = useLegacyParser; plugin.referer = refererController.text; - await pluginsController.tryInstallPlugin(plugin); + pluginsController.updatePlugin(plugin); Navigator.of(context).pop(); }, ), diff --git a/lib/pages/plugin_editor/plugin_view_page.dart b/lib/pages/plugin_editor/plugin_view_page.dart index bbaa8ea8..dec1c822 100644 --- a/lib/pages/plugin_editor/plugin_view_page.dart +++ b/lib/pages/plugin_editor/plugin_view_page.dart @@ -3,11 +3,13 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_modular/flutter_modular.dart'; +import 'package:kazumi/utils/logger.dart'; import 'package:kazumi/utils/utils.dart'; import 'package:kazumi/bean/dialog/dialog_helper.dart'; import 'package:kazumi/plugins/plugins.dart'; import 'package:kazumi/plugins/plugins_controller.dart'; import 'package:kazumi/bean/appbar/sys_app_bar.dart'; +import 'package:logger/logger.dart'; class PluginViewPage extends StatefulWidget { const PluginViewPage({super.key}); @@ -25,9 +27,6 @@ class _PluginViewPageState extends State { // 已选中的规则名称集合 final Set selectedNames = {}; - // 排序方式状态:false=按安装时间排序,true=按名称排序 - bool sortByName = false; - Future _handleUpdate() async { KazumiDialog.showLoading(msg: '更新中'); int count = await pluginsController.tryUpdateAllPlugin(); @@ -105,7 +104,7 @@ class _PluginViewPageState extends State { onPressed: () async { final String msg = textController.text; try { - await pluginsController.tryInstallPlugin(Plugin.fromJson( + pluginsController.updatePlugin(Plugin.fromJson( json.decode(Utils.kazumiBase64ToJson(msg)))); KazumiDialog.showToast(message: '导入成功'); } catch (e) { @@ -186,17 +185,7 @@ class _PluginViewPageState extends State { ), TextButton( onPressed: () { - // 从大到小排序,这样删除时不会影响前面的索引 - final sortedNames = selectedNames.toList() - ..sort((a, b) => b.compareTo(a)); - for (final name in sortedNames) { - final plugin = pluginsController.pluginList - .firstWhere((p) => p.name == name); - pluginsController - .deletePluginJsonFile(plugin); - pluginsController.pluginList - .removeWhere((p) => p.name == name); - } + pluginsController.removePlugins(selectedNames); setState(() { isMultiSelectMode = false; selectedNames.clear(); @@ -212,15 +201,6 @@ class _PluginViewPageState extends State { icon: const Icon(Icons.delete), ), ] else ...[ - IconButton( - onPressed: () { - setState(() { - sortByName = !sortByName; - }); - }, - tooltip: sortByName ? '按名称排序' : '按安装时间排序', - icon: Icon( - sortByName ? Icons.sort_by_alpha : Icons.access_time)), IconButton( onPressed: () { _handleUpdate(); @@ -242,30 +222,25 @@ class _PluginViewPageState extends State { child: Text('啊咧(⊙.⊙) 没有可用规则的说'), ) : Builder(builder: (context) { - // 创建列表副本用于排序 - var sortedList = List.from(pluginsController.pluginList); - // 排序规则: - // 1. 按名称排序:忽略大小写的字母顺序 - // 2. 按时间排序:安装时间降序(最新的在前面) - if (sortByName) { - sortedList.sort((a, b) => - a.name.toLowerCase().compareTo(b.name.toLowerCase())); - } else { - sortedList.sort((a, b) => pluginsController - .installTimeTracker - .getInstallTime(b.name) - .compareTo(pluginsController.installTimeTracker - .getInstallTime(a.name))); - } - - return ListView.builder( - itemCount: sortedList.length, + return ReorderableListView.builder( + proxyDecorator: (child, index, animation) { + return Material( + elevation: 0, + color: Colors.transparent, + child: child, + ); + }, + onReorder: (int oldIndex, int newIndex) { + pluginsController.onReorder(oldIndex, newIndex); + }, + itemCount: pluginsController.pluginList.length, itemBuilder: (context, index) { - var plugin = sortedList[index]; + var plugin = pluginsController.pluginList[index]; bool canUpdate = pluginsController.pluginUpdateStatus(plugin) == 'updatable'; return Card( + key: ValueKey(index), margin: const EdgeInsets.fromLTRB(8, 0, 8, 8), child: ListTile( shape: RoundedRectangleBorder( @@ -381,116 +356,96 @@ class _PluginViewPageState extends State { }); }, ) - : PopupMenuButton( - onSelected: (String result) async { - if (result == 'Update') { - var state = pluginsController - .pluginUpdateStatus(plugin); - if (state == "nonexistent") { - KazumiDialog.showToast( - message: '规则仓库中没有当前规则'); - } else if (state == "latest") { - KazumiDialog.showToast( - message: '规则已是最新'); - } else if (state == "updatable") { - KazumiDialog.showLoading(msg: '更新中'); - int res = await pluginsController - .tryUpdatePlugin(plugin); - KazumiDialog.dismiss(); - if (res == 0) { - KazumiDialog.showToast( - message: '更新成功'); - } else if (res == 1) { - KazumiDialog.showToast( - message: - 'kazumi版本过低, 此规则不兼容当前版本'); - } else if (res == 2) { - KazumiDialog.showToast( - message: '更新规则失败'); - } - } - } else if (result == 'Delete') { - setState(() { - pluginsController - .deletePluginJsonFile(plugin); - pluginsController.pluginList - .removeWhere( - (p) => p.name == plugin.name); - }); - } else if (result == 'Edit') { - Modular.to.pushNamed( - '/settings/plugin/editor', - arguments: plugin); - } else if (result == 'Share') { - KazumiDialog.show(builder: (context) { - return AlertDialog( - title: const Text('规则链接'), - content: SelectableText( - Utils.jsonToKazumiBase64( - json.encode(pluginsController - .pluginList[index] - .toJson())), - style: const TextStyle( - fontWeight: FontWeight.bold), - textAlign: TextAlign.center, - ), - actions: [ - TextButton( - onPressed: () => - KazumiDialog.dismiss(), - child: Text( - '取消', - style: TextStyle( - color: Theme.of(context) - .colorScheme - .outline), - ), - ), - TextButton( - onPressed: () { - Clipboard.setData(ClipboardData( - text: Utils.jsonToKazumiBase64( - json.encode( - pluginsController - .pluginList[ - index] - .toJson())))); - KazumiDialog.dismiss(); - }, - child: const Text('复制到剪贴板'), - ), - ], - ); - }); - } - }, - itemBuilder: (BuildContext context) => - >[ - const PopupMenuItem( - value: 'Update', - child: Text('更新'), - ), - const PopupMenuItem( - value: 'Edit', - child: Text('编辑'), - ), - const PopupMenuItem( - value: 'Share', - child: Text('分享'), - ), - const PopupMenuItem( - value: 'Delete', - child: Text('删除'), - ), - ], - ), + : popupMenuButton(index) ), ); - }, + } ); }); }), ), ); } + + Widget popupMenuButton(int index){ + final plugin = pluginsController.pluginList[index]; + return PopupMenuButton( + onSelected: (String result) async { + if (result == 'Update') { + var state = pluginsController.pluginUpdateStatus(plugin); + if (state == "nonexistent") { + KazumiDialog.showToast(message: '规则仓库中没有当前规则'); + } else if (state == "latest") { + KazumiDialog.showToast(message: '规则已是最新'); + } else if (state == "updatable") { + KazumiDialog.showLoading(msg: '更新中'); + int res = await pluginsController.tryUpdatePlugin(plugin); + KazumiDialog.dismiss(); + if (res == 0) { + KazumiDialog.showToast(message: '更新成功'); + } else if (res == 1) { + KazumiDialog.showToast(message: 'kazumi版本过低, 此规则不兼容当前版本'); + } else if (res == 2) { + KazumiDialog.showToast(message: '更新规则失败'); + } + } + } else if (result == 'Delete') { + setState(() { + pluginsController.removePlugin(plugin); + }); + } else if (result == 'Edit') { + Modular.to.pushNamed('/settings/plugin/editor', arguments: plugin); + } else if (result == 'Share') { + KazumiDialog.show(builder: (context) { + return AlertDialog( + title: const Text('规则链接'), + content: SelectableText( + Utils.jsonToKazumiBase64( + json.encode(pluginsController.pluginList[index].toJson())), + style: const TextStyle(fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + actions: [ + TextButton( + onPressed: () => KazumiDialog.dismiss(), + child: Text( + '取消', + style: + TextStyle(color: Theme.of(context).colorScheme.outline), + ), + ), + TextButton( + onPressed: () { + Clipboard.setData(ClipboardData( + text: Utils.jsonToKazumiBase64(json.encode( + pluginsController.pluginList[index].toJson())))); + KazumiDialog.dismiss(); + }, + child: const Text('复制到剪贴板'), + ), + ], + ); + }); + } + }, + itemBuilder: (BuildContext context) => >[ + const PopupMenuItem( + value: 'Update', + child: Text('更新'), + ), + const PopupMenuItem( + value: 'Edit', + child: Text('编辑'), + ), + const PopupMenuItem( + value: 'Share', + child: Text('分享'), + ), + const PopupMenuItem( + value: 'Delete', + child: Text('删除'), + ), + ], + ); + } } diff --git a/lib/plugins/plugins_controller.dart b/lib/plugins/plugins_controller.dart index 0c2c4935..4b35949b 100644 --- a/lib/plugins/plugins_controller.dart +++ b/lib/plugins/plugins_controller.dart @@ -30,13 +30,46 @@ abstract class _PluginsController with Store { // 规则安装时间追踪器 final installTimeTracker = PluginInstallTimeTracker(); - Future loadPlugins() async { - pluginList.clear(); + String pluginsFileName = "plugins.json"; + Future loadAllPlugins() async { + pluginList.clear(); final directory = await getApplicationSupportDirectory(); final pluginDirectory = Directory('${directory.path}/plugins'); KazumiLogger().log(Level.info, '插件目录 ${directory.path}/plugins'); + if (await pluginDirectory.exists()) { + final pluginsFile = File('${pluginDirectory.path}/$pluginsFileName'); + if (await pluginsFile.exists()) { + final jsonString = await pluginsFile.readAsString(); + pluginList = ObservableList.of(getPluginListFromJson(jsonString)); + KazumiLogger().log(Level.info, '当前插件数量 ${pluginList.length}'); + } else {} + if (pluginList.isEmpty) { + var jsonFiles = await loadPlugins(); + for (var filePath in jsonFiles) { + await File(filePath).delete(recursive: true); + } + if (pluginList.isNotEmpty) { + savePlugins(); + } + } + } else { + KazumiLogger().log(Level.warning, '插件目录不存在'); + } + } + + Future> loadPlugins() async { + final directory = await getApplicationSupportDirectory(); + final pluginDirectory = Directory('${directory.path}/plugins'); + if (await pluginDirectory.exists()) { + final pluginsFile = File('${pluginDirectory.path}/$pluginsFileName'); + if (await pluginsFile.exists()) { + return []; + } + } + pluginList.clear(); + KazumiLogger().log(Level.info, '插件目录 ${directory.path}/plugins'); if (await pluginDirectory.exists()) { final jsonFiles = pluginDirectory .listSync() @@ -55,10 +88,11 @@ abstract class _PluginsController with Store { plugin.name, stat.modified.millisecondsSinceEpoch); pluginList.add(plugin); } - KazumiLogger().log(Level.info, '当前插件数量 ${pluginList.length}'); + return jsonFiles; } else { KazumiLogger().log(Level.warning, '插件目录不存在'); + return []; } } @@ -78,66 +112,70 @@ abstract class _PluginsController with Store { for (var filePath in jsonFiles) { final jsonString = await rootBundle.loadString(filePath); - // panic - final fileName = filePath.split('/').last; - final file = File('${pluginDirectory.path}/$fileName'); - await file.writeAsString(jsonString); + final plugin = Plugin.fromJson(jsonDecode(jsonString)); + pluginList.add(plugin); } - + await savePlugins(); KazumiLogger().log( Level.info, '已将 ${jsonFiles.length} 个插件文件拷贝到 ${pluginDirectory.path}'); } - Future savePluginToJsonFile(Plugin plugin) async { - final directory = await getApplicationSupportDirectory(); - final pluginDirectory = Directory('${directory.path}/plugins'); - - if (!await pluginDirectory.exists()) { - await pluginDirectory.create(recursive: true); - } - - final fileName = '${plugin.name}.json'; - final existingFile = File('${pluginDirectory.path}/$fileName'); - if (await existingFile.exists()) { - await existingFile.delete(); + List pluginListToJson() { + final List json = []; + for (var plugin in pluginList) { + json.add(plugin.toJson()); } - - final newFile = File('${pluginDirectory.path}/$fileName'); - final jsonData = jsonEncode(plugin.toJson()); - await newFile.writeAsString(jsonData); - - KazumiLogger().log(Level.info, '已创建插件文件 $fileName'); + return json; } - Future deletePluginJsonFile(Plugin plugin) async { - final directory = await getApplicationSupportDirectory(); - final pluginDirectory = Directory('${directory.path}/plugins'); - - if (!await pluginDirectory.exists()) { - KazumiLogger().log(Level.warning, '插件目录不存在,无法删除文件'); - return; + List getPluginListFromJson(String jsonString) { + List json = jsonDecode(jsonString); + List plugins = []; + for (var j in json) { + plugins.add(Plugin.fromJson(j)); } + return plugins; + } - final fileName = '${plugin.name}.json'; - final files = pluginDirectory.listSync(); + Future removePlugin(Plugin plugin) async { + pluginList.removeWhere((p) => p.name == plugin.name); + await savePlugins(); + } - // workaround for android/linux case insensitive - File? targetFile; - for (var file in files) { - if (file is File && - path.basename(file.path).toLowerCase() == fileName.toLowerCase()) { - targetFile = file; + // update or add plugin + void updatePlugin(Plugin plugin) { + bool flag = false; + for (int i = 0; i < pluginList.length; ++i) { + if (pluginList[i].name == plugin.name) { + pluginList[i] = plugin; + flag = true; break; } } + if (!flag) { + pluginList.add(plugin); + } + savePlugins(); + } - if (targetFile != null) { - await targetFile.delete(); - KazumiLogger() - .log(Level.info, '已删除插件文件 ${path.basename(targetFile.path)}'); - } else { - KazumiLogger().log(Level.warning, '插件文件 $fileName 不存在'); + void onReorder(int oldIndex, int newIndex) { + if (oldIndex < newIndex) { + newIndex -= 1; } + final plugin = pluginList.removeAt(oldIndex); + pluginList.insert(newIndex, plugin); + savePlugins(); + } + + Future savePlugins() async { + final jsonData = jsonEncode(pluginListToJson()); + final directory = await getApplicationSupportDirectory(); + final pluginDirectory = Directory('${directory.path}/plugins'); + final pluginsFile = File('${pluginDirectory.path}/$pluginsFileName'); + await pluginsFile.writeAsString(jsonData); + KazumiLogger().log(Level.info, '已更新插件文件 $pluginsFileName'); + + pluginList = ObservableList.of(pluginList); // 强制替换触发更新 } Future queryPluginHTTPList() async { @@ -187,8 +225,8 @@ abstract class _PluginsController with Store { if (int.parse(pluginHTTPItem.api) > Api.apiLevel) { return 1; } - await savePluginToJsonFile(pluginHTTPItem); - await loadPlugins(); + updatePlugin(pluginHTTPItem); + await savePlugins(); return 0; } return 2; @@ -206,8 +244,12 @@ abstract class _PluginsController with Store { return count; } - Future tryInstallPlugin(Plugin plugin) async { - await savePluginToJsonFile(plugin); - await loadPlugins(); + void removePlugins(Set pluginNames) { + for (int i = pluginList.length - 1; i >= 0; --i) { + var name = pluginList[i].name; + if (pluginNames.contains(name)) { + pluginList.removeAt(i); + } + } } } From b70d8ea1fab5a7f434f6a91e5e14755853915ef3 Mon Sep 17 00:00:00 2001 From: lauirf <592184299@qq.com> Date: Mon, 13 Jan 2025 00:18:15 +0800 Subject: [PATCH 2/6] delete unnecessary code --- lib/plugins/plugins_controller.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/plugins/plugins_controller.dart b/lib/plugins/plugins_controller.dart index 4b35949b..6b7f4f06 100644 --- a/lib/plugins/plugins_controller.dart +++ b/lib/plugins/plugins_controller.dart @@ -226,7 +226,6 @@ abstract class _PluginsController with Store { return 1; } updatePlugin(pluginHTTPItem); - await savePlugins(); return 0; } return 2; From a11576dbf3cc0f3dfa2ff2b9e52171d8bd4e8905 Mon Sep 17 00:00:00 2001 From: lauirf <592184299@qq.com> Date: Mon, 13 Jan 2025 00:26:42 +0800 Subject: [PATCH 3/6] optimize logic --- lib/plugins/plugins_controller.dart | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/plugins/plugins_controller.dart b/lib/plugins/plugins_controller.dart index 6b7f4f06..4561fd52 100644 --- a/lib/plugins/plugins_controller.dart +++ b/lib/plugins/plugins_controller.dart @@ -43,8 +43,7 @@ abstract class _PluginsController with Store { final jsonString = await pluginsFile.readAsString(); pluginList = ObservableList.of(getPluginListFromJson(jsonString)); KazumiLogger().log(Level.info, '当前插件数量 ${pluginList.length}'); - } else {} - if (pluginList.isEmpty) { + } else { var jsonFiles = await loadPlugins(); for (var filePath in jsonFiles) { await File(filePath).delete(recursive: true); @@ -61,12 +60,6 @@ abstract class _PluginsController with Store { Future> loadPlugins() async { final directory = await getApplicationSupportDirectory(); final pluginDirectory = Directory('${directory.path}/plugins'); - if (await pluginDirectory.exists()) { - final pluginsFile = File('${pluginDirectory.path}/$pluginsFileName'); - if (await pluginsFile.exists()) { - return []; - } - } pluginList.clear(); KazumiLogger().log(Level.info, '插件目录 ${directory.path}/plugins'); From ff61d1419882332c56a21b9bf1cc7f70734e82b3 Mon Sep 17 00:00:00 2001 From: lauirf <592184299@qq.com> Date: Mon, 13 Jan 2025 12:40:08 +0800 Subject: [PATCH 4/6] enhance plugin drag feature --- lib/pages/init_page.dart | 2 +- lib/pages/plugin_editor/plugin_view_page.dart | 50 ++++++++----- lib/plugins/plugins_controller.dart | 75 ++++++++----------- 3 files changed, 64 insertions(+), 63 deletions(-) diff --git a/lib/pages/init_page.dart b/lib/pages/init_page.dart index cdbe982c..7a5c5692 100644 --- a/lib/pages/init_page.dart +++ b/lib/pages/init_page.dart @@ -83,7 +83,7 @@ class _InitPageState extends State { Future _pluginInit() async { String statementsText = ''; try { - await pluginsController.loadAllPlugins(); + await pluginsController.init(); statementsText = await rootBundle.loadString("assets/statements/statements.txt"); _pluginUpdate(); diff --git a/lib/pages/plugin_editor/plugin_view_page.dart b/lib/pages/plugin_editor/plugin_view_page.dart index dec1c822..8db5fd7f 100644 --- a/lib/pages/plugin_editor/plugin_view_page.dart +++ b/lib/pages/plugin_editor/plugin_view_page.dart @@ -223,6 +223,7 @@ class _PluginViewPageState extends State { ) : Builder(builder: (context) { return ReorderableListView.builder( + buildDefaultDragHandles: false, proxyDecorator: (child, index, animation) { return Material( elevation: 0, @@ -340,26 +341,8 @@ class _PluginViewPageState extends State { ], ], ), - trailing: isMultiSelectMode - ? Checkbox( - value: selectedNames.contains(plugin.name), - onChanged: (bool? value) { - setState(() { - if (value == true) { - selectedNames.add(plugin.name); - } else { - selectedNames.remove(plugin.name); - if (selectedNames.isEmpty) { - isMultiSelectMode = false; - } - } - }); - }, - ) - : popupMenuButton(index) - ), - ); - } + trailing: pluginCardTrailing(index))); + } ); }); }), @@ -367,6 +350,33 @@ class _PluginViewPageState extends State { ); } + Widget pluginCardTrailing(int index) { + final plugin = pluginsController.pluginList[index]; + return Row(mainAxisSize: MainAxisSize.min, children: [ + isMultiSelectMode + ? Checkbox( + value: selectedNames.contains(plugin.name), + onChanged: (bool? value) { + setState(() { + if (value == true) { + selectedNames.add(plugin.name); + } else { + selectedNames.remove(plugin.name); + if (selectedNames.isEmpty) { + isMultiSelectMode = false; + } + } + }); + }, + ) + : popupMenuButton(index), + ReorderableDragStartListener( + index: index, + child: const Icon(Icons.drag_handle), // 单独的拖拽按钮 + ) + ]); + } + Widget popupMenuButton(int index){ final plugin = pluginsController.pluginList[index]; return PopupMenuButton( diff --git a/lib/plugins/plugins_controller.dart b/lib/plugins/plugins_controller.dart index 4561fd52..15c72bd2 100644 --- a/lib/plugins/plugins_controller.dart +++ b/lib/plugins/plugins_controller.dart @@ -31,70 +31,62 @@ abstract class _PluginsController with Store { final installTimeTracker = PluginInstallTimeTracker(); String pluginsFileName = "plugins.json"; + + Directory? pluginDirectory; + // Initializes the plugin directory and loads all plugins + Future init() async { + final directory = await getApplicationSupportDirectory(); + pluginDirectory = Directory('${directory.path}/plugins'); + await loadAllPlugins(); + } + + // Loads all plugins from the directory, populates the plugin list, and saves to plugins.json if needed Future loadAllPlugins() async { pluginList.clear(); - final directory = await getApplicationSupportDirectory(); - final pluginDirectory = Directory('${directory.path}/plugins'); - KazumiLogger().log(Level.info, '插件目录 ${directory.path}/plugins'); - if (await pluginDirectory.exists()) { - final pluginsFile = File('${pluginDirectory.path}/$pluginsFileName'); + KazumiLogger().log(Level.info, '插件目录 ${pluginDirectory!.path}'); + if (await pluginDirectory!.exists()) { + final pluginsFile = File('${pluginDirectory!.path}/$pluginsFileName'); if (await pluginsFile.exists()) { final jsonString = await pluginsFile.readAsString(); pluginList = ObservableList.of(getPluginListFromJson(jsonString)); KazumiLogger().log(Level.info, '当前插件数量 ${pluginList.length}'); } else { - var jsonFiles = await loadPlugins(); + // No plugins.json + var jsonFiles = await getPluginFiles(); for (var filePath in jsonFiles) { - await File(filePath).delete(recursive: true); - } - if (pluginList.isNotEmpty) { - savePlugins(); + final file = File(filePath); + final jsonString = await file.readAsString(); + final data = jsonDecode(jsonString); + final plugin = Plugin.fromJson(data); + pluginList.add(plugin); + await file.delete(recursive: true); } + savePlugins(); } } else { KazumiLogger().log(Level.warning, '插件目录不存在'); } } - Future> loadPlugins() async { - final directory = await getApplicationSupportDirectory(); - final pluginDirectory = Directory('${directory.path}/plugins'); - - pluginList.clear(); - KazumiLogger().log(Level.info, '插件目录 ${directory.path}/plugins'); - if (await pluginDirectory.exists()) { - final jsonFiles = pluginDirectory + // Retrieves a list of JSON plugin file paths from the plugin directory + Future> getPluginFiles() async { + if (await pluginDirectory!.exists()) { + final jsonFiles = pluginDirectory! .listSync() .where((file) => file.path.endsWith('.json') && file is File) .map((file) => file.path) .toList(); - - for (var filePath in jsonFiles) { - final file = File(filePath); - final jsonString = await file.readAsString(); - final data = jsonDecode(jsonString); - final plugin = Plugin.fromJson(data); - // 使用文件修改时间作为安装时间 - final stat = await file.stat(); - installTimeTracker.setInstallTime( - plugin.name, stat.modified.millisecondsSinceEpoch); - pluginList.add(plugin); - } - KazumiLogger().log(Level.info, '当前插件数量 ${pluginList.length}'); return jsonFiles; } else { - KazumiLogger().log(Level.warning, '插件目录不存在'); return []; } } + // Copies plugin JSON files from the assets to the plugin directory Future copyPluginsToExternalDirectory() async { - final directory = await getApplicationSupportDirectory(); - final pluginDirectory = Directory('${directory.path}/plugins'); - - if (!await pluginDirectory.exists()) { - await pluginDirectory.create(recursive: true); + if (!await pluginDirectory!.exists()) { + await pluginDirectory!.create(recursive: true); } final manifestContent = await rootBundle.loadString('AssetManifest.json'); @@ -110,7 +102,7 @@ abstract class _PluginsController with Store { } await savePlugins(); KazumiLogger().log( - Level.info, '已将 ${jsonFiles.length} 个插件文件拷贝到 ${pluginDirectory.path}'); + Level.info, '已将 ${jsonFiles.length} 个插件文件拷贝到 ${pluginDirectory!.path}'); } List pluginListToJson() { @@ -121,6 +113,7 @@ abstract class _PluginsController with Store { return json; } + // Converts a JSON string into a list of Plugin objects. List getPluginListFromJson(String jsonString) { List json = jsonDecode(jsonString); List plugins = []; @@ -135,12 +128,12 @@ abstract class _PluginsController with Store { await savePlugins(); } - // update or add plugin + // Update or add plugin void updatePlugin(Plugin plugin) { bool flag = false; for (int i = 0; i < pluginList.length; ++i) { if (pluginList[i].name == plugin.name) { - pluginList[i] = plugin; + pluginList.replaceRange(i, i + 1, [plugin]); flag = true; break; } @@ -167,8 +160,6 @@ abstract class _PluginsController with Store { final pluginsFile = File('${pluginDirectory.path}/$pluginsFileName'); await pluginsFile.writeAsString(jsonData); KazumiLogger().log(Level.info, '已更新插件文件 $pluginsFileName'); - - pluginList = ObservableList.of(pluginList); // 强制替换触发更新 } Future queryPluginHTTPList() async { From 11bbad65b6c59e6eb4ba00fb8064f5a739355149 Mon Sep 17 00:00:00 2001 From: lauirf <592184299@qq.com> Date: Mon, 13 Jan 2025 13:58:39 +0800 Subject: [PATCH 5/6] add comment --- lib/pages/plugin_editor/plugin_view_page.dart | 3 ++- lib/plugins/plugins_controller.dart | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/pages/plugin_editor/plugin_view_page.dart b/lib/pages/plugin_editor/plugin_view_page.dart index 8db5fd7f..7ae0f999 100644 --- a/lib/pages/plugin_editor/plugin_view_page.dart +++ b/lib/pages/plugin_editor/plugin_view_page.dart @@ -244,6 +244,7 @@ class _PluginViewPageState extends State { key: ValueKey(index), margin: const EdgeInsets.fromLTRB(8, 0, 8, 8), child: ListTile( + trailing: pluginCardTrailing(index), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), onLongPress: () { @@ -341,7 +342,7 @@ class _PluginViewPageState extends State { ], ], ), - trailing: pluginCardTrailing(index))); + )); } ); }); diff --git a/lib/plugins/plugins_controller.dart b/lib/plugins/plugins_controller.dart index 15c72bd2..f9ea4baa 100644 --- a/lib/plugins/plugins_controller.dart +++ b/lib/plugins/plugins_controller.dart @@ -15,6 +15,10 @@ import 'package:kazumi/request/api.dart'; part 'plugins_controller.g.dart'; + +// 从 1.5.1 版本开始,规则文件储存在单一的 plugins.json 文件中。 +// 之前的版本中,规则以分离文件形式存储,版本更新后将这些分离文件合并为单一的 plugins.json 文件。 + class PluginsController = _PluginsController with _$PluginsController; abstract class _PluginsController with Store { From 057b6fd63b67191beea402381fb1db9cb83f8aeb Mon Sep 17 00:00:00 2001 From: lauirf <592184299@qq.com> Date: Mon, 13 Jan 2025 14:03:58 +0800 Subject: [PATCH 6/6] use addAll --- lib/plugins/plugins_controller.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/plugins_controller.dart b/lib/plugins/plugins_controller.dart index f9ea4baa..c53b0de4 100644 --- a/lib/plugins/plugins_controller.dart +++ b/lib/plugins/plugins_controller.dart @@ -53,7 +53,7 @@ abstract class _PluginsController with Store { final pluginsFile = File('${pluginDirectory!.path}/$pluginsFileName'); if (await pluginsFile.exists()) { final jsonString = await pluginsFile.readAsString(); - pluginList = ObservableList.of(getPluginListFromJson(jsonString)); + pluginList.addAll(getPluginListFromJson(jsonString)); KazumiLogger().log(Level.info, '当前插件数量 ${pluginList.length}'); } else { // No plugins.json