flutter_sdk/example/lib/tabs/wifi_config_tab.dart

301 lines
11 KiB
Dart
Raw Normal View History

2026-08-20 09:25:05 +08:00
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_sdk/starcloud_flutter_sdk.dart';
import '../l10n.dart';
import '../global.dart';
import '../services/video_talk_initializer.dart';
import '../widgets/common_widgets.dart';
import 'package:starcloud/entity/star_cloud_lock_list.dart' as scl;
class WifiConfigTab extends StatefulWidget {
const WifiConfigTab({super.key});
@override
State<WifiConfigTab> createState() => _WifiConfigTabState();
}
class _WifiConfigTabState extends State<WifiConfigTab> {
final List<String> _log = [];
final _ssidCtrl = TextEditingController();
final _pwdCtrl = TextEditingController();
bool _loading = false;
@override
void dispose() {
_ssidCtrl.dispose();
_pwdCtrl.dispose();
super.dispose();
}
void _log_(String msg) {
final ts = DateTime.now().toIso8601String().substring(11, 19);
if (mounted) setState(() => _log.add('[$ts] $msg'));
debugPrint('WiFi_DEBUG: $msg');
}
Future<void> _initVideoTalk() async {
try {
_log_(S.isEn ? 'Initializing video talk...' : '正在初始化视频对讲...');
await initializeVideoTalk();
_log_(S.isEn
? 'Video talk is ready. App PeerID: ${appPeerId.value}'
: '视频对讲已初始化。App PeerID: ${appPeerId.value}');
} catch (e) {
_log_('${S.logFailed}: $e');
}
}
Future<void> _config() async {
final lock = pairedLock.value;
if (lock == null) {
_log_('ERROR: No device paired.');
return;
}
setState(() => _loading = true);
final String lockServiceToken = lockServiceAccessToken.value?.trim() ?? '';
if (signedInAccount.value == null || lockServiceToken.isEmpty) {
_log_('请先在“登录”页使用锁主账号/管理员账号登录。');
setState(() => _loading = false);
return;
}
_log_('>>> START CONFIG | HTTP + BLE 0x30F4/0x30F5');
try {
final ctx = await StarCloudLockContext.fromLock(lock);
final String myId = appPeerId.value ?? '';
if (myId.isEmpty) {
_log_(S.logConfigCancelledNeedTalkInitialization);
return;
}
_log_(S.logUsingLoginSessionForGatewayConfig);
final reply = await StarCloudClient.instance.wifi.configureWifiByContext(
ctx,
ssid: _ssidCtrl.text.trim(),
password: _pwdCtrl.text.trim(),
lockServiceAccessToken: lockServiceToken,
deviceName: lock.bluetooth.bluetoothDeviceName,
mac: lock.mac,
);
2026-10-19 16:17:25 +08:00
wifiConfiguredLockId.value = lock.lockId;
2026-08-20 09:25:05 +08:00
_log_(S.logWifiConfigCompleted);
2026-10-19 17:11:58 +08:00
try {
final remoteUnlockReply = await StarCloudClient.instance.lockSetting
.setRemoteUnlockByContext(ctx, enabled: true);
if (remoteUnlockReply.status == 0) {
lock.lockFeature['remoteUnlock'] = 1;
lock.lockSetting.remoteUnlock = 1;
pairedLock.value = null;
pairedLock.value = lock;
_log_(S.isEn
? 'Remote unlock enabled by default.'
: '远程开锁已默认开启。');
} else {
final status = remoteUnlockReply.status;
_log_('Remote unlock default enable failed: '
'status=$status.');
}
} catch (e) {
_log_('Remote unlock default enable failed: $e');
}
2026-08-20 09:25:05 +08:00
try {
final gatewayStatus =
await StarCloudClient.instance.wifi.getGatewayStatus(
lockId: lock.bluetooth.bluetoothDeviceName,
userId: ctx.userId,
deviceName: lock.bluetooth.bluetoothDeviceName,
mac: lock.mac,
);
_log_(
'GATEWAY STATUS: 0x${gatewayStatus.status.toRadixString(16).toUpperCase()}');
} catch (e) {
_log_('GATEWAY STATUS QUERY FAILED: $e');
}
final blePeerId = reply.peerId?.trim() ?? '';
if (blePeerId.isNotEmpty) {
_log_('Lock PeerID from BLE: $blePeerId');
lock.peerId = blePeerId;
pairedLock.value = null;
pairedLock.value = lock;
unawaited(_waitForLockOnline(lock.lockId));
} else {
await _refreshLockPeerId(lock);
}
} catch (e) {
_log_('${S.logFailed}: $e');
} finally {
if (mounted) setState(() => _loading = false);
}
}
/// 配网成功后自动回查云端锁详情,回填锁版 peerId,并等待锁上线
Future<void> _refreshLockPeerId(scl.StarCloudLock lock) async {
try {
final fresh =
await StarCloudClient.instance.device.getDevice(lockId: lock.lockId);
pairedLock.value = fresh;
final p = (fresh.peerId ?? '').trim();
_log_(p.isNotEmpty
? 'Lock PeerID after config: $p'
: 'Lock not reported PeerID yet (may take a while).');
// 等待锁上线
unawaited(_waitForLockOnline(lock.lockId));
} catch (e) {
_log_('Refresh lock detail failed: $e (retry in Talk tab)');
}
}
/// 配网后轮询等待锁上线(最长等待 120 秒)
Future<void> _waitForLockOnline(int lockId) async {
const maxSeconds = 120;
const interval = Duration(seconds: 5);
int waited = 0;
_log_(S.isEn
? 'Waiting for lock to come online (this may take 30-60s)...'
: '等待锁上线(可能需要 30-60 秒)...');
while (waited < maxSeconds) {
await Future.delayed(interval);
waited += 5;
if (!mounted) return;
try {
final fresh =
await StarCloudClient.instance.device.getDevice(lockId: lockId);
final bool online = fresh.isOnline ?? false;
final String peerId = (fresh.peerId ?? '').trim();
if (online && peerId.isNotEmpty) {
pairedLock.value = fresh;
_log_(S.isEn
? 'SUCCESS: Lock is ONLINE! peerId=$peerId. You can start monitoring now.'
: '锁已上线!peerId=$peerId。现在可以开始监控了。');
return;
}
_log_(
'[${waited}s] ${S.isEn ? "still offline" : "仍未上线"} (peerId=${peerId.isNotEmpty ? peerId : "none"})');
} catch (e) {
_log_('Poll online status failed: $e');
}
}
_log_(S.isEn
? 'WARNING: Lock did not come online within 120s. Check WiFi signal and try restarting the lock.'
: '警告:120 秒内锁未上线。请检查 WiFi 信号,或尝试重启锁。');
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: S.langNotifier,
builder: (context, isEn, _) {
return ValueListenableBuilder<scl.StarCloudLock?>(
valueListenable: pairedLock,
builder: (context, lock, _) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ValueListenableBuilder<bool>(
valueListenable: videoTalkReady,
builder: (context, ready, _) {
return ValueListenableBuilder<bool>(
valueListenable: videoTalkInitializing,
builder: (context, initializing, _) {
return ElevatedButton.icon(
onPressed:
ready || initializing ? null : _initVideoTalk,
icon: initializing
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: const Icon(Icons.videocam),
label: Text(ready
? '${S.btnInitTalk} (${S.initialized})'
: S.btnInitTalk),
);
},
);
},
),
const SizedBox(height: 16),
Text(
lock != null
? '${S.pairedStatus}: ${lock.lockName.isNotEmpty ? lock.lockName : "ID:${lock.lockId}"}'
: S.notPaired,
style: TextStyle(
color: lock != null ? Colors.green : Colors.red,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 16),
ValueListenableBuilder<String?>(
valueListenable: signedInAccount,
builder: (context, account, _) => Card(
elevation: 0,
color: account == null
? Colors.red.shade50
: Colors.green.shade50,
child: ListTile(
leading: Icon(account == null
? Icons.error_outline
: Icons.verified_user_outlined),
title: Text(account == null
? '未登录'
: '${S.currentLoggedInAccountPrefix}$account'),
subtitle: Text(account == null
? '请先在“登录”页登录锁主账号/管理员账号。'
: S.wifiUsesAuthorizedSession),
),
),
),
const SizedBox(height: 12),
TextField(
controller: _ssidCtrl,
decoration: InputDecoration(
labelText: isEn ? 'WiFi SSID' : 'WIFI名称',
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.wifi))),
const SizedBox(height: 12),
TextField(
controller: _pwdCtrl,
obscureText: true,
decoration: InputDecoration(
labelText: isEn ? 'WiFi Password' : 'WIFI密码',
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.lock_outline))),
const SizedBox(height: 20),
ElevatedButton.icon(
onPressed: _loading || lock == null ? null : _config,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
minimumSize: const Size.fromHeight(50)),
icon: _loading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
color: Colors.white, strokeWidth: 2))
: const Icon(Icons.send),
label: Text(_loading ? S.btnProcessing : S.btnStartConfig),
),
const SizedBox(height: 20),
LogArea(log: _log),
],
),
);
},
);
},
);
}
}