feat(android): 支持沉浸式状态栏、地图定位与自定义签名
- 配置 edge-to-edge 沉浸式状态栏与刘海屏适配 - 新增百度地图权限、AK 配置及定位权限说明 - 接入 MethodChannel 实现返回键退后台热启动优化 - 配置自定义签名并更新构建逻辑 - 完善 token 刷新与鉴权失效统一处理 - 新增地图页与截图页路由 - 修复返回键拦截导致的 PopScope 失效问题
This commit is contained in:
163
lib/features/device/data/device_api.dart
Normal file
163
lib/features/device/data/device_api.dart
Normal file
@@ -0,0 +1,163 @@
|
||||
import 'package:cupertino_ui/cupertino_ui.dart';
|
||||
import 'package:ttstd_family_care/app/constants/app_constants.dart';
|
||||
import 'package:ttstd_family_care/core/network/api_exception.dart';
|
||||
import 'package:ttstd_family_care/core/network/dio_client.dart';
|
||||
import 'package:ttstd_family_care/features/device/domain/device_models.dart';
|
||||
|
||||
/// 客户端(家属端)设备接口访问层。
|
||||
///
|
||||
/// 后端接口前缀为 `/api/v1/client/sn`(与登录接口所在的 `/api/v1/open` 不同),
|
||||
/// 由于 Dio 在 path 以 `/` 开头时会替换 baseUrl 的完整 path 段(保留 host),
|
||||
/// 这里直接使用绝对路径即可正确命中 client 接口。
|
||||
/// [DioClient.get] 已统一解析为后台返回体中的 `data` 字段;业务失败时抛出
|
||||
/// [ApiException],本层将其降级为「空数据」以适配家属端展示。
|
||||
class DeviceApi {
|
||||
const DeviceApi();
|
||||
|
||||
/// 获取当前用户已绑定的设备列表。
|
||||
Future<List<DeviceBrief>> getMyDevices() async {
|
||||
try {
|
||||
final data = await DioClient.get(
|
||||
'v1/client/sn/my-devices',
|
||||
);
|
||||
return _parseList(data, DeviceBrief.fromJson);
|
||||
} on ApiException {
|
||||
return const [];
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取已绑定设备基本信息(无上报数据时返回 null)。
|
||||
Future<DeviceSystemInfo?> getDeviceInfo(String sn) async {
|
||||
try {
|
||||
final data = await DioClient.get(
|
||||
'v1/client/sn/device-info',
|
||||
queryParameters: {'sn': sn},
|
||||
);
|
||||
return data is Map<String, dynamic> ? DeviceSystemInfo.fromJson(data) : null;
|
||||
} on ApiException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取已绑定设备最新定位信息(无上报数据时返回 null)。
|
||||
Future<DeviceLocation?> getLocation(String sn) async {
|
||||
try {
|
||||
final data = await DioClient.get(
|
||||
'v1/client/sn/location',
|
||||
queryParameters: {'sn': sn},
|
||||
);
|
||||
return data is Map<String, dynamic> ? DeviceLocation.fromJson(data) : null;
|
||||
} on ApiException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取已绑定设备已安装应用列表。
|
||||
Future<List<DeviceApkInfo>> getApks(String sn) async {
|
||||
try {
|
||||
final data = await DioClient.get(
|
||||
'v1/client/sn/apks',
|
||||
queryParameters: {'sn': sn},
|
||||
);
|
||||
return _parseList(data, DeviceApkInfo.fromJson);
|
||||
} on ApiException {
|
||||
return const [];
|
||||
}
|
||||
}
|
||||
|
||||
/// 向设备下发操作指令(重启/关机/截屏/刷新/定位等)。
|
||||
///
|
||||
/// [op] 为后端操作标识,如 `reboot` / `shutdown` / `screenshot` / `refresh` / `locate`,
|
||||
/// 对应接口路径 `/api/v1/client/sn/ops/{op}?sn=xxx`。
|
||||
/// 成功返回 null;失败返回后台错误提示。
|
||||
Future<String?> sendDeviceOp(String sn, String op) async {
|
||||
try {
|
||||
final data = await DioClient.post(
|
||||
'v1/client/sn/ops/$op',
|
||||
queryParameters: {'sn': sn},
|
||||
);
|
||||
debugPrint('[DeviceApi] sendDeviceOp op=$op sn=$sn data=$data');
|
||||
return null;
|
||||
} on ApiException catch (e) {
|
||||
debugPrint('[DeviceApi] sendDeviceOp op=$op sn=$sn failed: ${e.message}');
|
||||
return e.message;
|
||||
}
|
||||
}
|
||||
|
||||
/// 对指定应用执行操作(打开/停止/卸载/清除数据)。
|
||||
///
|
||||
/// [op] 为后端操作标识:`launch` / `stop` / `uninstall` / `clear_data`,
|
||||
/// 对应接口路径 `/api/v1/client/sn/ops/app/{op}?sn=xxx`,请求体携带应用包名。
|
||||
/// 成功返回 null;失败返回后台错误提示。
|
||||
Future<String?> sendAppOp(String sn, String op, String packageName) async {
|
||||
try {
|
||||
final data = await DioClient.post(
|
||||
'v1/client/sn/ops/app/$op',
|
||||
queryParameters: {'sn': sn},
|
||||
data: {'packageName': packageName},
|
||||
);
|
||||
debugPrint('[DeviceApi] sendAppOp op=$op sn=$sn pkg=$packageName data=$data');
|
||||
return null;
|
||||
} on ApiException catch (e) {
|
||||
debugPrint('[DeviceApi] sendAppOp op=$op sn=$sn pkg=$packageName failed: ${e.message}');
|
||||
return e.message;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static List<T> _parseList<T>(
|
||||
dynamic data,
|
||||
T Function(Map<String, dynamic>) fromJson,
|
||||
) {
|
||||
if (data is! List) return const [];
|
||||
return data
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(fromJson)
|
||||
.toList(growable: false);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 获取设备最近截图列表(家属端)。
|
||||
///
|
||||
/// [sn] 为设备序列号(可空,由后台按绑定关系补全)。
|
||||
/// 返回 [ScreenshotVO] 列表,并自动补全图片可访问的完整 URL(拼接 baseUrl)。
|
||||
Future<List<ScreenshotVO>> getRecentScreenshots({String? sn}) async {
|
||||
final query = sn != null && sn.isNotEmpty ? {'sn': sn} : <String, dynamic>{};
|
||||
// DioClient.get 已统一解析出响应体中的 data 字段,
|
||||
// 后台返回结构为 { code, msg, data: [...] },故此处 payload 即为截图数组。
|
||||
final payload = await DioClient.get(
|
||||
'v1/client/sn/upload-screenshot',
|
||||
queryParameters: query,
|
||||
);
|
||||
// 防御性提取:兼容后台直接返回 List,或返回 { data: [...] } 的包裹结构。
|
||||
final List<dynamic> listData;
|
||||
if (payload is List) {
|
||||
listData = payload;
|
||||
} else if (payload is Map && payload['data'] is List) {
|
||||
listData = payload['data'] as List;
|
||||
} else {
|
||||
debugPrint('[DeviceApi] getRecentScreenshots 返回非预期结构: sn=$sn payload=$payload');
|
||||
return const [];
|
||||
}
|
||||
debugPrint('[DeviceApi] getRecentScreenshots sn=$sn count=${listData.length}');
|
||||
final origin = Uri.parse(AppConstants.kBaseUrl).origin;
|
||||
return listData.whereType<Map<String, dynamic>>().map((e) {
|
||||
final vo = ScreenshotVO.fromJson(e);
|
||||
// 后台返回的 url 为相对路径(如 /static/screenshot/xxx),补全为完整地址。
|
||||
final raw = vo.url;
|
||||
final fullUrl = (raw != null && raw.startsWith('/'))
|
||||
? '$origin$raw'
|
||||
: (raw ?? '');
|
||||
return ScreenshotVO(
|
||||
id: vo.id,
|
||||
sn: vo.sn,
|
||||
fileName: vo.fileName,
|
||||
fileSize: vo.fileSize,
|
||||
url: fullUrl,
|
||||
uploadTime: vo.uploadTime,
|
||||
);
|
||||
}).toList(growable: false);
|
||||
}
|
||||
}
|
||||
61
lib/features/device/data/device_providers.dart
Normal file
61
lib/features/device/data/device_providers.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:ttstd_family_care/features/device/data/device_api.dart';
|
||||
import 'package:ttstd_family_care/features/device/data/device_repository_impl.dart';
|
||||
import 'package:ttstd_family_care/features/device/domain/device_models.dart';
|
||||
import 'package:ttstd_family_care/features/device/domain/device_repository.dart';
|
||||
|
||||
/// 设备仓储 Provider。
|
||||
final deviceRepositoryProvider = Provider<DeviceRepository>((ref) {
|
||||
const api = DeviceApi();
|
||||
return DeviceRepositoryImpl(api);
|
||||
});
|
||||
|
||||
/// 当前用户已绑定设备列表。
|
||||
final myDevicesProvider = FutureProvider<List<DeviceBrief>>((ref) async {
|
||||
final repo = ref.watch(deviceRepositoryProvider);
|
||||
return repo.getMyDevices();
|
||||
});
|
||||
|
||||
/// 当前选中的设备序列号(默认取绑定列表首个设备)。
|
||||
///
|
||||
/// 使用普通 Provider(而非 StateProvider)以便随 [myDevicesProvider] 的加载完成
|
||||
/// 自动推导出首个已绑定设备的 SN。若后续需要手动切换设备,可改为 StateProvider。
|
||||
final selectedDeviceSnProvider = Provider<String?>((ref) {
|
||||
final devices = ref.watch(myDevicesProvider);
|
||||
return devices.maybeWhen(
|
||||
data: (list) => list.isEmpty ? null : list.first.serialno,
|
||||
orElse: () => null,
|
||||
);
|
||||
});
|
||||
|
||||
/// 按 SN 获取设备基本信息。
|
||||
final deviceInfoProvider =
|
||||
FutureProvider.family<DeviceSystemInfo?, String>((ref, sn) async {
|
||||
if (sn.isEmpty) return null;
|
||||
final repo = ref.watch(deviceRepositoryProvider);
|
||||
return repo.getDeviceInfo(sn);
|
||||
});
|
||||
|
||||
/// 按 SN 获取设备最新定位信息。
|
||||
final deviceLocationProvider =
|
||||
FutureProvider.family<DeviceLocation?, String>((ref, sn) async {
|
||||
if (sn.isEmpty) return null;
|
||||
final repo = ref.watch(deviceRepositoryProvider);
|
||||
return repo.getLocation(sn);
|
||||
});
|
||||
|
||||
/// 按 SN 获取设备已安装应用列表。
|
||||
final deviceApksProvider =
|
||||
FutureProvider.family<List<DeviceApkInfo>, String>((ref, sn) async {
|
||||
if (sn.isEmpty) return const [];
|
||||
final repo = ref.watch(deviceRepositoryProvider);
|
||||
return repo.getApks(sn);
|
||||
});
|
||||
|
||||
/// 按 SN 获取设备最近截图列表。
|
||||
final recentScreenshotsProvider =
|
||||
FutureProvider.family<List<ScreenshotVO>, String>((ref, sn) async {
|
||||
if (sn.isEmpty) return const [];
|
||||
final repo = ref.watch(deviceRepositoryProvider);
|
||||
return repo.getRecentScreenshots(sn);
|
||||
});
|
||||
35
lib/features/device/data/device_repository_impl.dart
Normal file
35
lib/features/device/data/device_repository_impl.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
import 'package:ttstd_family_care/features/device/data/device_api.dart';
|
||||
import 'package:ttstd_family_care/features/device/domain/device_models.dart';
|
||||
import 'package:ttstd_family_care/features/device/domain/device_repository.dart';
|
||||
|
||||
/// [DeviceRepository] 的默认实现,委托给 [DeviceApi]。
|
||||
class DeviceRepositoryImpl implements DeviceRepository {
|
||||
const DeviceRepositoryImpl(this._api);
|
||||
|
||||
final DeviceApi _api;
|
||||
|
||||
|
||||
@override
|
||||
Future<List<DeviceBrief>> getMyDevices() => _api.getMyDevices();
|
||||
|
||||
@override
|
||||
Future<DeviceSystemInfo?> getDeviceInfo(String sn) => _api.getDeviceInfo(sn);
|
||||
|
||||
@override
|
||||
Future<DeviceLocation?> getLocation(String sn) => _api.getLocation(sn);
|
||||
|
||||
@override
|
||||
Future<List<DeviceApkInfo>> getApks(String sn) => _api.getApks(sn);
|
||||
|
||||
@override
|
||||
Future<List<ScreenshotVO>> getRecentScreenshots(String sn) =>
|
||||
_api.getRecentScreenshots(sn: sn);
|
||||
|
||||
@override
|
||||
Future<String?> sendDeviceOp(String sn, String op) =>
|
||||
_api.sendDeviceOp(sn, op);
|
||||
|
||||
@override
|
||||
Future<String?> sendAppOp(String sn, String op, String packageName) =>
|
||||
_api.sendAppOp(sn, op, packageName);
|
||||
}
|
||||
256
lib/features/device/domain/device_models.dart
Normal file
256
lib/features/device/domain/device_models.dart
Normal file
@@ -0,0 +1,256 @@
|
||||
/// 设备相关领域实体(家属端对接 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;
|
||||
}
|
||||
31
lib/features/device/domain/device_repository.dart
Normal file
31
lib/features/device/domain/device_repository.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:ttstd_family_care/features/device/domain/device_models.dart';
|
||||
|
||||
/// 设备仓储,封装设备信息/定位/应用列表/绑定设备列表的获取。
|
||||
abstract class DeviceRepository {
|
||||
/// 当前用户已绑定的设备列表。
|
||||
Future<List<DeviceBrief>> getMyDevices();
|
||||
|
||||
/// 已绑定设备基本信息(无上报数据时返回 null)。
|
||||
Future<DeviceSystemInfo?> getDeviceInfo(String sn);
|
||||
|
||||
/// 已绑定设备最新定位信息(无上报数据时返回 null)。
|
||||
Future<DeviceLocation?> getLocation(String sn);
|
||||
|
||||
/// 已绑定设备已安装应用列表。
|
||||
Future<List<DeviceApkInfo>> getApks(String sn);
|
||||
|
||||
/// 已绑定设备最近截图列表。
|
||||
Future<List<ScreenshotVO>> getRecentScreenshots(String sn);
|
||||
|
||||
/// 向设备下发操作指令(重启/关机/截屏/刷新/定位等)。
|
||||
///
|
||||
/// [op] 为后端操作标识,如 `reboot` / `shutdown` / `screenshot` / `refresh` / `locate`。
|
||||
/// 成功返回 null;失败返回错误提示。
|
||||
Future<String?> sendDeviceOp(String sn, String op);
|
||||
|
||||
/// 对指定应用执行操作(打开/停止/卸载/清除数据)。
|
||||
///
|
||||
/// [op] 为后端操作标识:`launch` / `stop` / `uninstall` / `clear_data`。
|
||||
/// 成功返回 null;失败返回错误提示。
|
||||
Future<String?> sendAppOp(String sn, String op, String packageName);
|
||||
}
|
||||
Reference in New Issue
Block a user