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 createState() => _WifiConfigTabState(); } class _WifiConfigTabState extends State { final List _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 _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 _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, ); _log_(S.logWifiConfigCompleted); 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 _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 _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( valueListenable: S.langNotifier, builder: (context, isEn, _) { return ValueListenableBuilder( valueListenable: pairedLock, builder: (context, lock, _) { return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ ValueListenableBuilder( valueListenable: videoTalkReady, builder: (context, ready, _) { return ValueListenableBuilder( 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( 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), ], ), ); }, ); }, ); } }