feat(remote): 修复键盘输入映射并重构连接流程
- Android端注入按键时显式设置SOURCE_KEYBOARD,修复IME丢弃事件导致输入框无法输入文字的问题 - Flutter端新增Flutter逻辑键到Android keyCode的转换映射,避免功能键误触发 - 重构RemoteController,分离信令连接与远程控制逻辑,支持设备列表选择与配对码兑换 - AuthRepository接口新增getBindings、getOnlineDevices、redeemPairingCode方法 - DioAuthRepository实现401自动刷新token重试机制 - ConnectionSessionState扩展设备列表、在线状态、配对码等状态字段
This commit is contained in:
@@ -6,6 +6,7 @@ import '../../../app/constants/app_constants.dart';
|
||||
import '../../../core/network/api_exception.dart';
|
||||
import '../../../core/storage/token_storage.dart';
|
||||
import '../domain/auth_repository.dart';
|
||||
import '../../connection/domain/binding_device.dart';
|
||||
|
||||
/// 基于 Dio 的 [AuthRepository] 实现。
|
||||
class DioAuthRepository implements AuthRepository {
|
||||
@@ -101,13 +102,41 @@ class DioAuthRepository implements AuthRepository {
|
||||
Future<Map<String, dynamic>> verify() => _get('/api/client/verify');
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> bindings() => _get('/api/client/bindings');
|
||||
Future<List<BindingDevice>> getBindings() async {
|
||||
final data = await _get('/api/client/bindings');
|
||||
final list = (data['bindings'] as List?) ?? [];
|
||||
return [
|
||||
for (final e in list)
|
||||
if (e is Map)
|
||||
BindingDevice.fromJson(e.cast<String, dynamic>()),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<BindingDevice>> getOnlineDevices() async {
|
||||
final data = await _get('/api/client/devices/online');
|
||||
final list = (data['devices'] as List?) ?? [];
|
||||
return [
|
||||
for (final e in list)
|
||||
if (e is Map)
|
||||
BindingDevice.fromJson(e.cast<String, dynamic>()),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> redeemPairingCode(String code) async {
|
||||
await _post(
|
||||
'/api/client/pairing/redeem',
|
||||
body: {'code': code},
|
||||
auth: true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>?> turnCredentials() async {
|
||||
try {
|
||||
return await _get('/api/client/turn-credentials');
|
||||
} on ApiException {
|
||||
} on Object {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -121,24 +150,61 @@ class DioAuthRepository implements AuthRepository {
|
||||
Future<Map<String, dynamic>> _post(
|
||||
String path, {
|
||||
required Map<String, dynamic> body,
|
||||
bool auth = false,
|
||||
}) async {
|
||||
final res = await _dio.post<dynamic>(
|
||||
path,
|
||||
data: jsonEncode(body),
|
||||
return _handle(
|
||||
await _send(
|
||||
() => _dio.post<dynamic>(
|
||||
path,
|
||||
data: jsonEncode(body),
|
||||
options: Options(
|
||||
headers: {
|
||||
if (auth && _accessToken != null)
|
||||
'Authorization': 'Bearer $_accessToken',
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
return _handle(res);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> _get(String path) async {
|
||||
final res = await _dio.get<dynamic>(
|
||||
path,
|
||||
options: Options(
|
||||
headers: {
|
||||
if (_accessToken != null) 'Authorization': 'Bearer $_accessToken',
|
||||
},
|
||||
return _handle(
|
||||
await _send(
|
||||
() => _dio.get<dynamic>(
|
||||
path,
|
||||
options: Options(
|
||||
headers: {
|
||||
if (_accessToken != null)
|
||||
'Authorization': 'Bearer $_accessToken',
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
return _handle(res);
|
||||
}
|
||||
|
||||
/// 发送请求,遇 401 时尝试用 refreshToken 续期后重试一次。
|
||||
///
|
||||
/// Dio 默认对非 2xx 直接抛出 [DioException](bad response),不会走到
|
||||
/// [_handle],因此在异常层拦截 401 并触发刷新重试,避免已登录用户因
|
||||
/// accessToken 过期而直接失败。
|
||||
Future<Response<dynamic>> _send(
|
||||
Future<Response<dynamic>> Function() request,
|
||||
) async {
|
||||
try {
|
||||
return await request();
|
||||
} on DioException catch (e) {
|
||||
if (e.response?.statusCode == 401 && await hasRefreshToken()) {
|
||||
try {
|
||||
await refresh();
|
||||
} on Object {
|
||||
rethrow;
|
||||
}
|
||||
return await request();
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> _handle(Response<dynamic> res) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import '../../connection/domain/binding_device.dart';
|
||||
|
||||
/// 账号体系与自助接口抽象(对应服务端 /api/auth/* 与 /api/client/*)。
|
||||
abstract interface class AuthRepository {
|
||||
/// 内存中的 accessToken(掉线即失)。
|
||||
@@ -19,7 +21,14 @@ abstract interface class AuthRepository {
|
||||
|
||||
Future<Map<String, dynamic>> verify();
|
||||
|
||||
Future<Map<String, dynamic>> bindings();
|
||||
/// 获取主控端已绑定的被控设备列表(含名称、鉴权方式等)。
|
||||
Future<List<BindingDevice>> getBindings();
|
||||
|
||||
/// 获取已绑定且当前在线(信令网络活跃)的设备列表。
|
||||
Future<List<BindingDevice>> getOnlineDevices();
|
||||
|
||||
/// 使用被控端展示的一次性配对码建立绑定关系。
|
||||
Future<void> redeemPairingCode(String code);
|
||||
|
||||
/// 拉取 TURN 短期凭证(服务端开启时返回 iceServers);关闭时返回 null。
|
||||
Future<Map<String, dynamic>?> turnCredentials();
|
||||
|
||||
Reference in New Issue
Block a user