- 配置 edge-to-edge 沉浸式状态栏与刘海屏适配 - 新增百度地图权限、AK 配置及定位权限说明 - 接入 MethodChannel 实现返回键退后台热启动优化 - 配置自定义签名并更新构建逻辑 - 完善 token 刷新与鉴权失效统一处理 - 新增地图页与截图页路由 - 修复返回键拦截导致的 PopScope 失效问题
257 lines
7.4 KiB
Dart
257 lines
7.4 KiB
Dart
/// 设备相关领域实体(家属端对接 client 接口)。
|
||
///
|
||
/// 以不可变类表达,避免 UI / 仓库直接依赖后台 DTO 结构。
|
||
/// 因未运行 build_runner(避免改动依赖与生成产物),此处手写等价实现:
|
||
/// 所有字段 final、提供 const 构造与 copyWith,并手动实现 JSON 转换。
|
||
|
||
/// 设备简要信息(「我的设备」列表)。
|
||
class DeviceBrief {
|
||
const DeviceBrief({
|
||
required this.serialno,
|
||
this.snName,
|
||
this.snModel,
|
||
this.snMobile,
|
||
this.status,
|
||
this.activateTime,
|
||
});
|
||
|
||
final String serialno;
|
||
final String? snName;
|
||
final String? snModel;
|
||
final String? snMobile;
|
||
final int? status;
|
||
final String? activateTime;
|
||
|
||
factory DeviceBrief.fromJson(Map<String, dynamic> json) => DeviceBrief(
|
||
serialno: json['serialno'] as String,
|
||
snName: json['snName'] as String?,
|
||
snModel: json['snModel'] as String?,
|
||
snMobile: json['snMobile'] as String?,
|
||
status: _toInt(json['status']),
|
||
activateTime: json['activateTime']?.toString(),
|
||
);
|
||
}
|
||
|
||
/// 设备基本信息(系统信息面板)。
|
||
class DeviceSystemInfo {
|
||
const DeviceSystemInfo({
|
||
this.serialno,
|
||
this.deviceName,
|
||
this.deviceBrand,
|
||
this.deviceModel,
|
||
this.androidVersion,
|
||
this.releaseVersion,
|
||
this.screenResolution,
|
||
this.osType,
|
||
this.osVersion,
|
||
this.firmwareVersion,
|
||
});
|
||
|
||
final String? serialno;
|
||
final String? deviceName;
|
||
final String? deviceBrand;
|
||
final String? deviceModel;
|
||
final String? androidVersion;
|
||
final String? releaseVersion;
|
||
final String? screenResolution;
|
||
final String? osType;
|
||
final String? osVersion;
|
||
final String? firmwareVersion;
|
||
|
||
factory DeviceSystemInfo.fromJson(Map<String, dynamic> json) => DeviceSystemInfo(
|
||
serialno: json['serialno'] as String?,
|
||
deviceName: json['deviceName'] as String?,
|
||
deviceBrand: json['deviceBrand'] as String?,
|
||
deviceModel: json['deviceModel'] as String?,
|
||
androidVersion: json['androidVersion'] as String?,
|
||
releaseVersion: json['releaseVersion'] as String?,
|
||
screenResolution: json['screenResolution'] as String?,
|
||
osType: json['osType'] as String?,
|
||
osVersion: json['osVersion'] as String?,
|
||
firmwareVersion: json['firmwareVersion'] as String?,
|
||
);
|
||
}
|
||
|
||
/// 设备定位信息。
|
||
class DeviceLocation {
|
||
const DeviceLocation({
|
||
this.sn,
|
||
this.country,
|
||
this.province,
|
||
this.city,
|
||
this.district,
|
||
this.street,
|
||
this.address,
|
||
this.locationDescribe,
|
||
this.longitude,
|
||
this.latitude,
|
||
this.lastSuccessfulTime,
|
||
});
|
||
|
||
final String? sn;
|
||
final String? country;
|
||
final String? province;
|
||
final String? city;
|
||
final String? district;
|
||
final String? street;
|
||
final String? address;
|
||
final String? locationDescribe;
|
||
final String? longitude;
|
||
final String? latitude;
|
||
final String? lastSuccessfulTime;
|
||
|
||
factory DeviceLocation.fromJson(Map<String, dynamic> json) => DeviceLocation(
|
||
sn: json['sn'] as String?,
|
||
country: json['country'] as String?,
|
||
province: json['province'] as String?,
|
||
city: json['city'] as String?,
|
||
district: json['district'] as String?,
|
||
street: json['street'] as String?,
|
||
address: json['address'] as String?,
|
||
locationDescribe: json['locationDescribe'] as String?,
|
||
longitude: _toDoubleString(json['longitude']),
|
||
latitude: _toDoubleString(json['latitude']),
|
||
lastSuccessfulTime: json['lastSuccessfulTime']?.toString(),
|
||
);
|
||
|
||
/// 拼接展示用地址:省+市+区+街道+详细地址。
|
||
String get displayAddress {
|
||
final parts = [
|
||
province,
|
||
city,
|
||
district,
|
||
street,
|
||
address,
|
||
].where((e) => e != null && e.isNotEmpty);
|
||
return parts.join('');
|
||
}
|
||
}
|
||
|
||
/// 设备已安装应用信息。
|
||
class DeviceApkInfo {
|
||
const DeviceApkInfo({
|
||
required this.packageName,
|
||
this.appName,
|
||
this.versionName,
|
||
this.versionCode,
|
||
this.installTime,
|
||
this.lastUpdateTime,
|
||
this.apkSize,
|
||
this.systemApp = false,
|
||
this.iconUrl,
|
||
});
|
||
|
||
final String packageName;
|
||
final String? appName;
|
||
final String? versionName;
|
||
final int? versionCode;
|
||
final String? installTime;
|
||
final String? lastUpdateTime;
|
||
final int? apkSize;
|
||
final bool systemApp;
|
||
final String? iconUrl;
|
||
|
||
factory DeviceApkInfo.fromJson(Map<String, dynamic> json) => DeviceApkInfo(
|
||
packageName: json['packageName'] as String,
|
||
appName: json['appName'] as String?,
|
||
versionName: json['versionName'] as String?,
|
||
versionCode: _toInt(json['versionCode']),
|
||
installTime: json['installTime']?.toString(),
|
||
lastUpdateTime: json['lastUpdateTime']?.toString(),
|
||
apkSize: _toInt(json['apkSize']),
|
||
systemApp: json['systemApp'] as bool? ?? false,
|
||
iconUrl: json['iconUrl'] as String?,
|
||
);
|
||
}
|
||
|
||
/// 设备截图实体(与后台 SnScreenshot 对齐)。
|
||
class SnScreenshot {
|
||
const SnScreenshot({
|
||
this.id,
|
||
this.sn,
|
||
this.fileName,
|
||
this.filePath,
|
||
this.fileSize,
|
||
this.fileMd5,
|
||
this.fileSha1,
|
||
this.fileSha256,
|
||
this.uploadTime,
|
||
});
|
||
|
||
final int? id;
|
||
final String? sn;
|
||
final String? fileName;
|
||
final String? filePath;
|
||
final int? fileSize;
|
||
final String? fileMd5;
|
||
final String? fileSha1;
|
||
final String? fileSha256;
|
||
final String? uploadTime;
|
||
|
||
factory SnScreenshot.fromJson(Map<String, dynamic> json) => SnScreenshot(
|
||
id: _toInt(json['id']),
|
||
sn: json['sn'] as String?,
|
||
fileName: json['fileName'] as String?,
|
||
filePath: json['filePath'] as String?,
|
||
fileSize: _toInt(json['fileSize']),
|
||
fileMd5: json['fileMd5'] as String?,
|
||
fileSha1: json['fileSha1'] as String?,
|
||
fileSha256: json['fileSha256'] as String?,
|
||
uploadTime: json['uploadTime']?.toString(),
|
||
);
|
||
}
|
||
|
||
/// 设备截图视图对象(与后台 ScreenshotVO 对齐)。
|
||
class ScreenshotVO {
|
||
const ScreenshotVO({
|
||
this.id,
|
||
this.sn,
|
||
this.fileName,
|
||
this.fileSize,
|
||
this.url,
|
||
this.uploadTime,
|
||
});
|
||
|
||
final int? id;
|
||
final String? sn;
|
||
final String? fileName;
|
||
final int? fileSize;
|
||
final String? url;
|
||
final String? uploadTime;
|
||
|
||
factory ScreenshotVO.fromJson(Map<String, dynamic> json) => ScreenshotVO(
|
||
id: _toInt(json['id']),
|
||
sn: json['sn'] as String?,
|
||
fileName: json['fileName'] as String?,
|
||
fileSize: _toInt(json['fileSize']),
|
||
url: json['url'] as String?,
|
||
uploadTime: json['uploadTime']?.toString(),
|
||
);
|
||
}
|
||
|
||
/// 容错整型解析:后台可能以字符串(如 "123")或数字返回整型字段,
|
||
/// 直接用 `as int?` 在字符串场景下会抛出
|
||
/// `type 'String' is not a subtype of type 'int'`,故统一在此转换。
|
||
int? _toInt(dynamic value) {
|
||
if (value == null) return null;
|
||
if (value is int) return value;
|
||
if (value is num) return value.toInt();
|
||
if (value is String) return int.tryParse(value);
|
||
return null;
|
||
}
|
||
|
||
/// 容错浮点解析(用于经纬度等带小数的字段),返回字符串形式便于上层 `double.tryParse`。
|
||
///
|
||
/// 经纬度必须保留小数精度:
|
||
/// - 后台若以数字(如 `116.404`)返回,直接截断成整数会偏移数公里;
|
||
/// - 后台若以字符串(如 `"39.915"`)返回,`int.tryParse` 会解析失败变成 null。
|
||
String? _toDoubleString(dynamic value) {
|
||
if (value == null) return null;
|
||
if (value is num) return value.toString();
|
||
if (value is String) {
|
||
final parsed = double.tryParse(value);
|
||
return parsed?.toString();
|
||
}
|
||
return null;
|
||
}
|