flutter_sdk/example/lib/tabs/wifi_config_tab.dart
2026-08-20 09:25:05 +08:00

280 lines
10 KiB
Dart
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
);
_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<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),
],
),
);
},
);
},
);
}
}