feat: VehicleSorting

This commit is contained in:
2025-06-01 16:36:10 +08:00
parent 4679d559d9
commit 488ad2a485
11 changed files with 530 additions and 279 deletions

View File

@ -0,0 +1,337 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hive_ce/hive.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
class VehicleSortingDialogUi extends HookConsumerWidget {
final ValueNotifier<String> iniStringData;
const VehicleSortingDialogUi({
super.key,
required this.iniStringData,
});
static const List<String> vehicleLineRegExpList = ["vehicle_Name.*"];
@override
Widget build(BuildContext context, WidgetRef ref) {
final leftVehiclesList = useState<List<MapEntry<String, String>>?>(null);
final rightVehiclesList = useState<List<MapEntry<String, String>>>([]);
final leftSearchKey = useState<String>("");
final leftSearchController = useTextEditingController();
useEffect(() {
_loadVehiclesList(leftVehiclesList, rightVehiclesList);
return () {
_saveSortedVehicles(rightVehiclesList.value);
};
}, const []);
if (leftVehiclesList.value == null) {
return const Center(child: ProgressRing());
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: .03),
borderRadius: BorderRadius.circular(4.0),
),
child: Text(
"将左侧载具拖动到右侧列表中,这将会为载具名称增加 001、002 .. 等前缀,方便您在游戏内 UI 快速定位载具。在右侧列表上下拖动可以调整载具的顺序。",
),
),
const SizedBox(height: 12),
Expanded(
child: Row(
children: [
// 左侧载具列表
Expanded(
flex: 2,
child: Card(
padding: EdgeInsets.only(
left: 8.0,
top: 8.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.all(4.0),
child: Text(
"载具",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
Padding(
padding: const EdgeInsets.only(top: 12, bottom: 12),
child: Row(
children: [
Expanded(
child: TextFormBox(
controller: leftSearchController,
placeholder: "搜索载具",
onChanged: (value) {
leftSearchKey.value = value;
},
),
),
SizedBox(width: 6),
// clear button
Button(
child: Padding(
padding: const EdgeInsets.only(top: 4, bottom: 4),
child: const Icon(FluentIcons.clear),
),
onPressed: () {
leftSearchKey.value = "";
leftSearchController.clear();
},
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: leftVehiclesList.value!.length,
padding: EdgeInsets.only(right: 8),
itemBuilder: (context, index) {
final vehicle = leftVehiclesList.value![index];
if (leftSearchKey.value.isNotEmpty) {
// 如果搜索关键字不为空,则过滤列表
// key value 匹配
if (!vehicle.key.toLowerCase().contains(leftSearchKey.value.toLowerCase()) &&
!vehicle.value.toLowerCase().contains(leftSearchKey.value.toLowerCase())) {
return const SizedBox.shrink();
}
}
return Draggable<MapEntry<String, String>>(
data: vehicle,
feedback: _buildVehicleItem(context, vehicle, (MediaQuery.of(context).size.width / 3)),
childWhenDragging: _buildVehicleItem(context, vehicle, null, opacity: 0.5),
child: _buildVehicleItem(context, vehicle, null),
onDragCompleted: () {
// 当拖动完成后,从左侧列表移除
final updatedList = [...leftVehiclesList.value!];
updatedList.removeAt(index);
leftVehiclesList.value = updatedList;
},
);
},
),
),
],
),
),
),
const SizedBox(width: 12),
// 右侧载具列表已排序a
Expanded(
flex: 3,
child: Card(
padding: EdgeInsets.only(
left: 8.0,
top: 8.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.all(4.0),
child: Text(
"已排序载具",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
Expanded(
child: DragTarget<MapEntry<String, String>>(
onAcceptWithDetails: (detail) {
// 接受从左侧拖过来的数据
final updatedList = [...rightVehiclesList.value];
updatedList.add(detail.data);
rightVehiclesList.value = updatedList;
_applyChanges(rightVehiclesList.value);
},
builder: (context, candidateData, rejectedData) {
return ReorderableListView.builder(
buildDefaultDragHandles: false,
padding: EdgeInsets.only(right: 8.0),
onReorder: (oldIndex, newIndex) {
final updatedList = [...rightVehiclesList.value];
if (oldIndex < newIndex) {
newIndex -= 1;
}
final item = updatedList.removeAt(oldIndex);
updatedList.insert(newIndex, item);
rightVehiclesList.value = updatedList;
},
onReorderEnd: (_) {
_applyChanges(rightVehiclesList.value);
},
itemCount: rightVehiclesList.value.length,
itemBuilder: (context, index) {
final vehicle = rightVehiclesList.value[index];
// 创建带有前缀的显示值
final prefixedValue = _getPrefixedValue(index, vehicle.value);
return Container(
key: ValueKey(vehicle.key + index.toString()),
margin: const EdgeInsets.symmetric(vertical: 2.0),
child: Row(
children: [
IconButton(
icon: const Icon(FluentIcons.delete),
onPressed: () {
// 从右侧移除,添加回左侧
final updatedRightList = [...rightVehiclesList.value];
final removed = updatedRightList.removeAt(index);
rightVehiclesList.value = updatedRightList;
final updatedLeftList = [...leftVehiclesList.value!];
updatedLeftList.add(removed);
leftVehiclesList.value = updatedLeftList;
_applyChanges(rightVehiclesList.value);
},
),
Expanded(
child: ReorderableDragStartListener(
index: index,
child: _buildVehicleItem(
context,
MapEntry(vehicle.key, prefixedValue),
null,
isRightList: true,
),
),
),
],
),
);
},
);
},
),
),
],
),
),
),
],
),
),
],
);
}
Widget _buildVehicleItem(BuildContext context, MapEntry<String, String> vehicle, double? width,
{double opacity = 1.0, bool isRightList = false}) {
return Container(
width: width,
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 4.0),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(4.0),
),
child: Opacity(
opacity: opacity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
vehicle.value,
style: const TextStyle(fontSize: 14),
overflow: TextOverflow.ellipsis,
),
Text(
vehicle.key,
style: TextStyle(
fontSize: 12,
color: Colors.white.withValues(
alpha: .4,
),
),
overflow: TextOverflow.ellipsis,
),
],
),
),
);
}
String _getPrefixedValue(int index, String originalValue) {
// 生成前缀 (001, 002, etc.)
final prefix = (index + 1).toString().padLeft(3, '0');
return "$prefix - $originalValue";
}
void _applyChanges(List<MapEntry<String, String>> sortedVehicles) async {
final lines = iniStringData.value.split('\n');
final updatedLines = <String>[];
for (final line in lines) {
bool matched = false;
final lineKey = line.split('=')[0].trim();
for (var i = 0; i < sortedVehicles.length; i++) {
final vehicle = sortedVehicles[i];
if (lineKey == vehicle.key) {
// 使用新的前缀值替换
final prefixedValue = _getPrefixedValue(i, vehicle.value);
updatedLines.add("$lineKey=$prefixedValue");
matched = true;
break;
}
}
if (!matched) {
updatedLines.add(line);
}
}
iniStringData.value = updatedLines.join('\n');
dPrint("[VehicleSortingDialogUi] Applied changes to ${sortedVehicles.length} vehicles");
}
Future<void> _saveSortedVehicles(List<MapEntry<String, String>> sortedVehicles) async {
final appBox = await Hive.openBox("app_conf");
appBox.put("sorted_vehicles", sortedVehicles.map((e) => e.key).toList());
dPrint("[VehicleSortingDialogUi] Saved sorted vehicles: ${sortedVehicles.length}");
}
void _loadVehiclesList(
ValueNotifier<List<MapEntry<String, String>>?> vehiclesList,
ValueNotifier<List<MapEntry<String, String>>> rightVehiclesList,
) async {
final vehicleMap = <String, String>{};
final lines = iniStringData.value.split('\n');
for (final regExp in vehicleLineRegExpList) {
final pattern = RegExp(regExp);
for (final line in lines) {
if (pattern.hasMatch(line)) {
final parts = line.split('=');
if (parts.length == 2) {
final key = parts[0].trim();
final value = parts[1].trim();
vehicleMap[key] = value;
}
}
}
}
vehiclesList.value = vehicleMap.entries.toList();
dPrint("[VehicleSortingDialogUi] Loaded vehicles: ${vehiclesList.value?.length ?? 0}");
// Load sorted vehicles from app_conf
final appBox = await Hive.openBox("app_conf");
final sortedVehicles = appBox.get("sorted_vehicles", defaultValue: <String>[]) as List<String>;
if (sortedVehicles.isNotEmpty) {
// 只保留有效载具
rightVehiclesList.value = sortedVehicles
.where((key) => vehicleMap.containsKey(key))
.map((key) => MapEntry(key, vehicleMap[key]!))
.toList();
dPrint("[VehicleSortingDialogUi] Loaded sorted vehicles: ${rightVehiclesList.value.length}");
}
}
}