Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add plugin drag feature #593

Merged
merged 6 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions lib/pages/init_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class _InitPageState extends State<InitPage> {
Future<void> _pluginInit() async {
String statementsText = '';
try {
await pluginsController.loadPlugins();
await pluginsController.init();
statementsText =
await rootBundle.loadString("assets/statements/statements.txt");
_pluginUpdate();
Expand Down Expand Up @@ -113,7 +113,6 @@ class _InitPageState extends State<InitPage> {
onPressed: () async {
try {
await pluginsController.copyPluginsToExternalDirectory();
await pluginsController.loadPlugins();
} catch (_) {}
KazumiDialog.dismiss();
Modular.to.navigate('/tab/popular/');
Expand Down
2 changes: 1 addition & 1 deletion lib/pages/plugin_editor/plugin_editor_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ class _PluginEditorPageState extends State<PluginEditorPage> {
plugin.usePost = usePost;
plugin.useLegacyParser = useLegacyParser;
plugin.referer = refererController.text;
await pluginsController.tryInstallPlugin(plugin);
pluginsController.updatePlugin(plugin);
Navigator.of(context).pop();
},
),
Expand Down
296 changes: 131 additions & 165 deletions lib/pages/plugin_editor/plugin_view_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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});
Expand All @@ -25,9 +27,6 @@ class _PluginViewPageState extends State<PluginViewPage> {
// 已选中的规则名称集合
final Set<String> selectedNames = {};

// 排序方式状态:false=按安装时间排序,true=按名称排序
bool sortByName = false;

Future<void> _handleUpdate() async {
KazumiDialog.showLoading(msg: '更新中');
int count = await pluginsController.tryUpdateAllPlugin();
Expand Down Expand Up @@ -105,7 +104,7 @@ class _PluginViewPageState extends State<PluginViewPage> {
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) {
Expand Down Expand Up @@ -186,17 +185,7 @@ class _PluginViewPageState extends State<PluginViewPage> {
),
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();
Expand All @@ -212,15 +201,6 @@ class _PluginViewPageState extends State<PluginViewPage> {
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();
Expand All @@ -242,32 +222,29 @@ class _PluginViewPageState extends State<PluginViewPage> {
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(
buildDefaultDragHandles: false,
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(
trailing: pluginCardTrailing(index),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
onLongPress: () {
Expand Down Expand Up @@ -365,132 +342,121 @@ class _PluginViewPageState extends State<PluginViewPage> {
],
],
),
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<String>(
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) =>
<PopupMenuEntry<String>>[
const PopupMenuItem<String>(
value: 'Update',
child: Text('更新'),
),
const PopupMenuItem<String>(
value: 'Edit',
child: Text('编辑'),
),
const PopupMenuItem<String>(
value: 'Share',
child: Text('分享'),
),
const PopupMenuItem<String>(
value: 'Delete',
child: Text('删除'),
),
],
),
),
);
},
));
}
);
});
}),
),
);
}

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<String>(
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) => <PopupMenuEntry<String>>[
const PopupMenuItem<String>(
value: 'Update',
child: Text('更新'),
),
const PopupMenuItem<String>(
value: 'Edit',
child: Text('编辑'),
),
const PopupMenuItem<String>(
value: 'Share',
child: Text('分享'),
),
const PopupMenuItem<String>(
value: 'Delete',
child: Text('删除'),
),
],
);
}
}
Loading
Loading