182 lines
6.2 KiB
Dart
Executable File
182 lines
6.2 KiB
Dart
Executable File
import 'package:flutter/material.dart';
|
||
import 'package:flutter_sdk/starcloud_flutter_sdk.dart';
|
||
import 'package:starcloud/entity/star_cloud_lock_list.dart' as scl;
|
||
import '../l10n.dart';
|
||
import '../global.dart';
|
||
import '../widgets/common_widgets.dart';
|
||
|
||
class DeviceScanTab extends StatefulWidget {
|
||
const DeviceScanTab({super.key});
|
||
|
||
@override
|
||
State<DeviceScanTab> createState() => _DeviceScanTabState();
|
||
}
|
||
|
||
class _DeviceScanTabState extends State<DeviceScanTab> {
|
||
bool _scanning = false;
|
||
bool _pairing = false;
|
||
List<dynamic> _devices = [];
|
||
final List<String> _log = [];
|
||
|
||
void _log_(String msg) {
|
||
final ts = DateTime.now().toIso8601String().substring(11, 19);
|
||
setState(() => _log.add('[$ts] $msg'));
|
||
}
|
||
|
||
Future<void> _scan() async {
|
||
if (!sdkCloudAccountReady.value) {
|
||
_log_(S.isEn
|
||
? 'Set an SDK cloud account before scanning and pairing a lock.'
|
||
: '请先在“登录”页创建或设置 SDK 星云账户,再扫描绑定锁。');
|
||
return;
|
||
}
|
||
setState(() {
|
||
_scanning = true;
|
||
_devices = [];
|
||
});
|
||
_log_(S.isEn ? 'Scan start (10s)...' : '开始扫描(10 秒超时)...');
|
||
try {
|
||
await StarCloudClient.instance.device.scanDeviceList(
|
||
timeout: 10,
|
||
onScan: (results) {
|
||
setState(() => _devices = results);
|
||
});
|
||
_log_(S.isEn
|
||
? 'Done. Found ${_devices.length}'
|
||
: '扫描完成,发现 ${_devices.length} 个设备');
|
||
} catch (e) {
|
||
_log_('${S.isEn ? "Failed" : "失败"}: $e');
|
||
} finally {
|
||
setState(() => _scanning = false);
|
||
}
|
||
}
|
||
|
||
Future<void> _pair(int idx) async {
|
||
if (!sdkCloudAccountReady.value) {
|
||
_log_(
|
||
S.isEn ? 'Please set an SDK cloud account first.' : '请先设置 SDK 星云账户。');
|
||
return;
|
||
}
|
||
setState(() => _pairing = true);
|
||
_log_(S.isEn ? 'Pairing...' : '正在配对...');
|
||
try {
|
||
final lock = await StarCloudClient.instance.device
|
||
.pairDevice(scanResult: _devices[idx]);
|
||
// 更新 ValueNotifier 的值
|
||
pairedLockId.value = lock.lockId;
|
||
pairedLock.value = lock;
|
||
|
||
_log_('${S.isEn ? "Success" : "成功"}! ID: ${lock.lockId}');
|
||
} catch (e) {
|
||
_log_('${S.isEn ? "Failed" : "失败"}: $e');
|
||
} finally {
|
||
setState(() => _pairing = false);
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return ValueListenableBuilder<bool>(
|
||
valueListenable: S.langNotifier,
|
||
builder: (context, isEn, _) {
|
||
return Column(
|
||
children: [
|
||
Padding(
|
||
padding: const EdgeInsets.all(12),
|
||
child: Row(
|
||
children: [
|
||
Expanded(
|
||
child: ElevatedButton.icon(
|
||
onPressed: _scanning ? null : _scan,
|
||
icon: _scanning
|
||
? const SizedBox(
|
||
width: 16,
|
||
height: 16,
|
||
child: CircularProgressIndicator(strokeWidth: 2))
|
||
: const Icon(Icons.bluetooth_searching),
|
||
label: Text(_scanning ? S.btnScanning : S.btnScan),
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
ElevatedButton(
|
||
onPressed: _scanning
|
||
? () => StarCloudClient.instance.device
|
||
.stopScan()
|
||
.then((_) => setState(() => _scanning = false))
|
||
: null,
|
||
child: Text(S.btnStop)),
|
||
],
|
||
),
|
||
),
|
||
// 使用 ValueListenableBuilder 监听配对状态
|
||
ValueListenableBuilder<int?>(
|
||
valueListenable: pairedLockId,
|
||
builder: (context, id, _) {
|
||
if (id == null) return const SizedBox.shrink();
|
||
return Container(
|
||
padding:
|
||
const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||
alignment: Alignment.centerLeft,
|
||
child: Chip(
|
||
label: Text('${S.pairedStatus}: #$id'),
|
||
backgroundColor: Colors.green.shade50,
|
||
onDeleted: () {
|
||
pairedLockId.value = null;
|
||
pairedLock.value = null;
|
||
},
|
||
),
|
||
);
|
||
},
|
||
),
|
||
Expanded(
|
||
child: ValueListenableBuilder<scl.StarCloudLock?>(
|
||
valueListenable: pairedLock,
|
||
builder: (context, paired, _) {
|
||
if (_devices.isEmpty) {
|
||
return Center(
|
||
child: Text(
|
||
S.scanHint,
|
||
style: const TextStyle(color: Colors.grey),
|
||
),
|
||
);
|
||
}
|
||
return ListView.separated(
|
||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||
itemCount: _devices.length,
|
||
separatorBuilder: (_, __) => const Divider(height: 1),
|
||
itemBuilder: (context, i) {
|
||
final device = _devices[i] as StarCloudScanResult;
|
||
final isPaired = _isPairedDevice(device, paired);
|
||
return ListTile(
|
||
title: Text(device.advName.isNotEmpty
|
||
? device.advName
|
||
: (S.isEn ? 'Unknown' : '未知设备')),
|
||
subtitle: Text('MAC: ${device.id}'),
|
||
trailing: ElevatedButton(
|
||
onPressed:
|
||
_pairing || isPaired ? null : () => _pair(i),
|
||
child: Text(isPaired ? S.pairedStatus : S.btnPair),
|
||
),
|
||
);
|
||
},
|
||
);
|
||
},
|
||
),
|
||
),
|
||
LogArea(log: _log),
|
||
],
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
bool _isPairedDevice(
|
||
StarCloudScanResult device,
|
||
scl.StarCloudLock? paired,
|
||
) {
|
||
if (paired == null) return false;
|
||
return paired.mac == device.id ||
|
||
paired.bluetooth.bluetoothDeviceName == device.advName;
|
||
}
|
||
}
|