Add WiZ provisioning wizard

This commit is contained in:
Artem Kokos
2026-05-16 17:24:28 +07:00
parent 0a635115d4
commit 866a074c03
19 changed files with 2668 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
class WizProvisioningDevice {
final String bssid;
final String? ipAddress;
const WizProvisioningDevice({required this.bssid, this.ipAddress});
}

View File

@@ -0,0 +1,134 @@
enum WizProvisioningPermissionStatus { granted, requestable, settingsRequired }
class WizProvisioningEnvironment {
final String platform;
final int? androidApiLevel;
final bool smartPairingSupported;
final bool wifiSettingsSupported;
final bool appSettingsSupported;
final WizProvisioningPermissionStatus permissionStatus;
final bool locationServicesEnabled;
final bool connectedToWifi;
final String? ssid;
final String? bssid;
final int? frequencyMhz;
const WizProvisioningEnvironment({
required this.platform,
required this.androidApiLevel,
required this.smartPairingSupported,
required this.wifiSettingsSupported,
required this.appSettingsSupported,
required this.permissionStatus,
required this.locationServicesEnabled,
required this.connectedToWifi,
required this.ssid,
required this.bssid,
required this.frequencyMhz,
});
factory WizProvisioningEnvironment.unsupported() =>
const WizProvisioningEnvironment(
platform: 'unknown',
androidApiLevel: null,
smartPairingSupported: false,
wifiSettingsSupported: false,
appSettingsSupported: false,
permissionStatus: WizProvisioningPermissionStatus.granted,
locationServicesEnabled: true,
connectedToWifi: false,
ssid: null,
bssid: null,
frequencyMhz: null,
);
factory WizProvisioningEnvironment.fromMap(Map<String, dynamic> raw) {
return WizProvisioningEnvironment(
platform: raw['platform'] as String? ?? 'unknown',
androidApiLevel: (raw['androidApiLevel'] as num?)?.toInt(),
smartPairingSupported: raw['smartPairingSupported'] == true,
wifiSettingsSupported: raw['wifiSettingsSupported'] == true,
appSettingsSupported: raw['appSettingsSupported'] == true,
permissionStatus: _permissionStatusFromPlatformValue(
raw['permissionStatus'] as String?,
),
locationServicesEnabled: raw['locationServicesEnabled'] != false,
connectedToWifi: raw['connectedToWifi'] == true,
ssid: _normalizeText(raw['ssid']),
bssid: _normalizeText(raw['bssid']),
frequencyMhz: (raw['frequencyMhz'] as num?)?.toInt(),
);
}
bool get permissionsGranted =>
permissionStatus == WizProvisioningPermissionStatus.granted;
bool get permissionRequestable =>
permissionStatus == WizProvisioningPermissionStatus.requestable;
bool get requiresAppSettings =>
permissionStatus == WizProvisioningPermissionStatus.settingsRequired;
bool get isAndroid => platform == 'android';
bool get isOn24Ghz =>
frequencyMhz != null && frequencyMhz! >= 2400 && frequencyMhz! < 2500;
bool get isLikelyOn5Ghz =>
frequencyMhz != null && frequencyMhz! >= 4900 && frequencyMhz! < 6000;
WizProvisioningEnvironment copyWith({
String? platform,
int? androidApiLevel,
bool? smartPairingSupported,
bool? wifiSettingsSupported,
bool? appSettingsSupported,
WizProvisioningPermissionStatus? permissionStatus,
bool? locationServicesEnabled,
bool? connectedToWifi,
String? ssid,
String? bssid,
int? frequencyMhz,
bool clearWifiInfo = false,
}) {
return WizProvisioningEnvironment(
platform: platform ?? this.platform,
androidApiLevel: androidApiLevel ?? this.androidApiLevel,
smartPairingSupported:
smartPairingSupported ?? this.smartPairingSupported,
wifiSettingsSupported:
wifiSettingsSupported ?? this.wifiSettingsSupported,
appSettingsSupported: appSettingsSupported ?? this.appSettingsSupported,
permissionStatus: permissionStatus ?? this.permissionStatus,
locationServicesEnabled:
locationServicesEnabled ?? this.locationServicesEnabled,
connectedToWifi: connectedToWifi ?? this.connectedToWifi,
ssid: clearWifiInfo ? null : (ssid ?? this.ssid),
bssid: clearWifiInfo ? null : (bssid ?? this.bssid),
frequencyMhz: clearWifiInfo ? null : (frequencyMhz ?? this.frequencyMhz),
);
}
static WizProvisioningPermissionStatus _permissionStatusFromPlatformValue(
String? value,
) {
switch (value) {
case 'granted':
return WizProvisioningPermissionStatus.granted;
case 'settings_required':
return WizProvisioningPermissionStatus.settingsRequired;
case 'requestable':
default:
return WizProvisioningPermissionStatus.requestable;
}
}
static String? _normalizeText(Object? raw) {
final text = raw as String?;
if (text == null) {
return null;
}
final trimmed = text.trim();
return trimmed.isEmpty ? null : trimmed;
}
}

View File

@@ -0,0 +1,23 @@
enum WizProvisioningFailureKind {
noActiveHome,
unsupportedPlatform,
missingPermissions,
locationServicesDisabled,
wifiUnavailable,
invalidSsid,
provisioningTimedOut,
provisioningFailed,
rescanFailed,
}
class WizProvisioningFailure {
final WizProvisioningFailureKind kind;
final String message;
final String? details;
const WizProvisioningFailure({
required this.kind,
required this.message,
this.details,
});
}

View File

@@ -0,0 +1,114 @@
import 'wiz_provisioning_device.dart';
import 'wiz_provisioning_environment.dart';
import 'wiz_provisioning_failure.dart';
enum WizProvisioningStatus {
initial,
loadingEnvironment,
attentionRequired,
ready,
provisioning,
rescanning,
success,
failure,
unsupported,
}
class WizRescanSummary {
final int found;
final int added;
final int updated;
final int removedOffline;
final int pendingRemoval;
final int online;
const WizRescanSummary({
required this.found,
required this.added,
required this.updated,
required this.removedOffline,
required this.pendingRemoval,
required this.online,
});
factory WizRescanSummary.fromMap(Map<String, dynamic> raw) {
return WizRescanSummary(
found: (raw['found'] as num?)?.toInt() ?? 0,
added: (raw['added'] as num?)?.toInt() ?? 0,
updated: (raw['updated'] as num?)?.toInt() ?? 0,
removedOffline: (raw['removed_offline'] as num?)?.toInt() ?? 0,
pendingRemoval: (raw['pending_removal'] as num?)?.toInt() ?? 0,
online: (raw['online'] as num?)?.toInt() ?? 0,
);
}
}
class WizProvisioningState {
final WizProvisioningStatus status;
final WizProvisioningEnvironment environment;
final String? activeHomeName;
final WizProvisioningFailure? failure;
final WizRescanSummary? rescanSummary;
final List<WizProvisioningDevice> provisionedDevices;
final List<String> timeline;
final String? notice;
const WizProvisioningState({
required this.status,
required this.environment,
required this.activeHomeName,
required this.failure,
required this.rescanSummary,
required this.provisionedDevices,
required this.timeline,
required this.notice,
});
factory WizProvisioningState.initial() => WizProvisioningState(
status: WizProvisioningStatus.initial,
environment: WizProvisioningEnvironment.unsupported(),
activeHomeName: null,
failure: null,
rescanSummary: null,
provisionedDevices: const [],
timeline: const [],
notice: null,
);
bool get isBusy =>
status == WizProvisioningStatus.loadingEnvironment ||
status == WizProvisioningStatus.provisioning ||
status == WizProvisioningStatus.rescanning;
bool get canStart =>
status == WizProvisioningStatus.ready ||
status == WizProvisioningStatus.failure ||
status == WizProvisioningStatus.attentionRequired;
WizProvisioningState copyWith({
WizProvisioningStatus? status,
WizProvisioningEnvironment? environment,
String? activeHomeName,
WizProvisioningFailure? failure,
bool clearFailure = false,
WizRescanSummary? rescanSummary,
bool clearRescanSummary = false,
List<WizProvisioningDevice>? provisionedDevices,
List<String>? timeline,
String? notice,
bool clearNotice = false,
}) {
return WizProvisioningState(
status: status ?? this.status,
environment: environment ?? this.environment,
activeHomeName: activeHomeName ?? this.activeHomeName,
failure: clearFailure ? null : (failure ?? this.failure),
rescanSummary: clearRescanSummary
? null
: (rescanSummary ?? this.rescanSummary),
provisionedDevices: provisionedDevices ?? this.provisionedDevices,
timeline: timeline ?? this.timeline,
notice: clearNotice ? null : (notice ?? this.notice),
);
}
}

View File

@@ -0,0 +1,15 @@
class WizProvisioningTiming {
final Duration provisioningTimeout;
final Duration settleAfterFirstResponse;
final Duration initialRescanDelay;
final Duration retryRescanDelay;
final int maxRescanAttempts;
const WizProvisioningTiming({
this.provisioningTimeout = const Duration(seconds: 45),
this.settleAfterFirstResponse = const Duration(seconds: 3),
this.initialRescanDelay = const Duration(seconds: 3),
this.retryRescanDelay = const Duration(seconds: 4),
this.maxRescanAttempts = 3,
});
}