M1.1 收尾: - 24项 P0/P1/P2 bug 修复 (Rust 107 tests + Flutter 15 tests) - Flutter App v0.3: cupertino_icons 修复, 单元测试, 调试面板, APK 52.6MB - 示例插件完善: manifest.json + 请求/响应示范 + 7个测试 - API 文档重写 (以 routes.rs 为唯一权威) - MILESTONES.md 更新至 100% M1.2 启动: - P0: 插件管理 API 闭环 (handle_manager_message Custom 分支 + broadcast_plugin_states) - ServiceManager 集成测试 8/8 (tests/m1_2_service_manager.rs) - M1.2 测试计划 (docs/M1.2_TEST_PLAN.md, 18个E2E场景) - 动态插件系统: auto_rollback + version_manager GC + 路径穿越防护 总计: Rust 115/115 测试, Flutter 15/15 测试, 零 warning Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
154 lines
4.1 KiB
Dart
154 lines
4.1 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
class SavedDevice {
|
|
const SavedDevice({
|
|
required this.ip,
|
|
required this.port,
|
|
required this.name,
|
|
required this.lastUsedAt,
|
|
});
|
|
|
|
final String ip;
|
|
final int port;
|
|
final String name;
|
|
final DateTime lastUsedAt;
|
|
|
|
String get address => '$ip:$port';
|
|
|
|
Map<String, dynamic> toJson() {
|
|
return <String, dynamic>{
|
|
'ip': ip,
|
|
'port': port,
|
|
'name': name,
|
|
'lastUsedAt': lastUsedAt.toIso8601String(),
|
|
};
|
|
}
|
|
|
|
factory SavedDevice.fromJson(Map<String, dynamic> json) {
|
|
return SavedDevice(
|
|
ip: _normalizeIp(json['ip']?.toString()),
|
|
port: _normalizePort(json['port']),
|
|
name: _normalizeName(json['name']?.toString()),
|
|
lastUsedAt: DateTime.tryParse(json['lastUsedAt']?.toString() ?? '') ??
|
|
DateTime.fromMillisecondsSinceEpoch(0),
|
|
);
|
|
}
|
|
|
|
static String _normalizeIp(String? value) {
|
|
final normalized = value?.trim() ?? '';
|
|
return normalized.isEmpty ? '127.0.0.1' : normalized;
|
|
}
|
|
|
|
static int _normalizePort(dynamic value) {
|
|
final port = int.tryParse(value?.toString() ?? '');
|
|
if (port == null || port <= 0 || port > 65535) {
|
|
return 5000;
|
|
}
|
|
return port;
|
|
}
|
|
|
|
static String _normalizeName(String? value) {
|
|
final normalized = value?.trim() ?? '';
|
|
return normalized.isEmpty ? 'Showen' : normalized;
|
|
}
|
|
}
|
|
|
|
class DeviceStorageService {
|
|
static const String _devicesKey = 'showen_device_list';
|
|
static const int _maxDevices = 10;
|
|
|
|
SharedPreferences? _preferences;
|
|
|
|
Future<void> saveDevice(String ip, int port, String? name) async {
|
|
final prefs = await _getPreferences();
|
|
final devices = await getDevices();
|
|
final normalizedIp = _normalizeIp(ip);
|
|
final normalizedPort = _normalizePort(port);
|
|
final normalizedName = _normalizeName(name);
|
|
final now = DateTime.now();
|
|
|
|
final nextDevices = <SavedDevice>[
|
|
SavedDevice(
|
|
ip: normalizedIp,
|
|
port: normalizedPort,
|
|
name: normalizedName,
|
|
lastUsedAt: now,
|
|
),
|
|
...devices.where(
|
|
(device) => !(device.ip == normalizedIp && device.port == normalizedPort),
|
|
),
|
|
]
|
|
..sort((a, b) => b.lastUsedAt.compareTo(a.lastUsedAt));
|
|
|
|
await prefs.setString(
|
|
_devicesKey,
|
|
jsonEncode(
|
|
nextDevices
|
|
.take(_maxDevices)
|
|
.map((device) => device.toJson())
|
|
.toList(growable: false),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<List<SavedDevice>> getDevices() async {
|
|
final prefs = await _getPreferences();
|
|
final raw = prefs.getString(_devicesKey);
|
|
if (raw == null || raw.isEmpty) {
|
|
return const <SavedDevice>[];
|
|
}
|
|
|
|
try {
|
|
final decoded = jsonDecode(raw);
|
|
if (decoded is! List) {
|
|
return const <SavedDevice>[];
|
|
}
|
|
|
|
final devices = decoded
|
|
.whereType<Map>()
|
|
.map((item) => SavedDevice.fromJson(Map<String, dynamic>.from(item)))
|
|
.toList(growable: false)
|
|
..sort((a, b) => b.lastUsedAt.compareTo(a.lastUsedAt));
|
|
|
|
return devices.take(_maxDevices).toList(growable: false);
|
|
} on FormatException {
|
|
return const <SavedDevice>[];
|
|
}
|
|
}
|
|
|
|
Future<void> removeDevice(String ip, [int? port]) async {
|
|
final prefs = await _getPreferences();
|
|
final normalizedIp = _normalizeIp(ip);
|
|
final nextDevices = (await getDevices())
|
|
.where(
|
|
(device) =>
|
|
device.ip != normalizedIp ||
|
|
(port != null && device.port != _normalizePort(port)),
|
|
)
|
|
.map((device) => device.toJson())
|
|
.toList(growable: false);
|
|
|
|
await prefs.setString(_devicesKey, jsonEncode(nextDevices));
|
|
}
|
|
|
|
Future<SavedDevice?> getLastDevice() async {
|
|
final devices = await getDevices();
|
|
if (devices.isEmpty) {
|
|
return null;
|
|
}
|
|
return devices.first;
|
|
}
|
|
|
|
Future<SharedPreferences> _getPreferences() async {
|
|
return _preferences ??= await SharedPreferences.getInstance();
|
|
}
|
|
|
|
String _normalizeIp(String value) => SavedDevice._normalizeIp(value);
|
|
|
|
int _normalizePort(dynamic value) => SavedDevice._normalizePort(value);
|
|
|
|
String _normalizeName(String? value) => SavedDevice._normalizeName(value);
|
|
}
|