refactor(settings): migrate wifi_settings God VM to Riverpod
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:settings/src/core/providers/wifi_repository_provider.dart';
|
||||
import 'package:settings/src/features/wifi_settings/domain/entities/wifi_network_entity.dart';
|
||||
|
||||
part 'saved_wifi_networks_provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<List<WifiNetworkEntity>> savedWifiNetworks(
|
||||
Ref ref,
|
||||
String deviceIdentificator,
|
||||
) async {
|
||||
return ref
|
||||
.read(wifiRepositoryProvider)
|
||||
.getWifiNetworks(deviceIdentificator: deviceIdentificator);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'saved_wifi_networks_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(savedWifiNetworks)
|
||||
const savedWifiNetworksProvider = SavedWifiNetworksFamily._();
|
||||
|
||||
final class SavedWifiNetworksProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<List<WifiNetworkEntity>>,
|
||||
List<WifiNetworkEntity>,
|
||||
FutureOr<List<WifiNetworkEntity>>
|
||||
>
|
||||
with
|
||||
$FutureModifier<List<WifiNetworkEntity>>,
|
||||
$FutureProvider<List<WifiNetworkEntity>> {
|
||||
const SavedWifiNetworksProvider._({
|
||||
required SavedWifiNetworksFamily super.from,
|
||||
required String super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'savedWifiNetworksProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$savedWifiNetworksHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'savedWifiNetworksProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<List<WifiNetworkEntity>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<List<WifiNetworkEntity>> create(Ref ref) {
|
||||
final argument = this.argument as String;
|
||||
return savedWifiNetworks(ref, argument);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is SavedWifiNetworksProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$savedWifiNetworksHash() => r'421ac8fad874ad3bd90efdf6ea4a3c43a478cb88';
|
||||
|
||||
final class SavedWifiNetworksFamily extends $Family
|
||||
with $FunctionalFamilyOverride<FutureOr<List<WifiNetworkEntity>>, String> {
|
||||
const SavedWifiNetworksFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'savedWifiNetworksProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
SavedWifiNetworksProvider call(String deviceIdentificator) =>
|
||||
SavedWifiNetworksProvider._(argument: deviceIdentificator, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'savedWifiNetworksProvider';
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
|
||||
part 'web_socket_service_provider.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
WebSocketService webSocketService(Ref ref) => GetIt.I<WebSocketService>();
|
||||
@@ -0,0 +1,56 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'web_socket_service_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(webSocketService)
|
||||
const webSocketServiceProvider = WebSocketServiceProvider._();
|
||||
|
||||
final class WebSocketServiceProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
WebSocketService,
|
||||
WebSocketService,
|
||||
WebSocketService
|
||||
>
|
||||
with $Provider<WebSocketService> {
|
||||
const WebSocketServiceProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'webSocketServiceProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$webSocketServiceHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<WebSocketService> $createElement($ProviderPointer pointer) =>
|
||||
$ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
WebSocketService create(Ref ref) {
|
||||
return webSocketService(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(WebSocketService value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<WebSocketService>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$webSocketServiceHash() => r'4a522db698e3aced3c90dc3ea1c2bf9e85f88830';
|
||||
@@ -0,0 +1,35 @@
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'wifi_connect_form_provider.g.dart';
|
||||
|
||||
class WifiConnectFormState {
|
||||
const WifiConnectFormState({
|
||||
this.canSave = false,
|
||||
this.obscurePassword = true,
|
||||
});
|
||||
|
||||
final bool canSave;
|
||||
final bool obscurePassword;
|
||||
|
||||
WifiConnectFormState copyWith({bool? canSave, bool? obscurePassword}) {
|
||||
return WifiConnectFormState(
|
||||
canSave: canSave ?? this.canSave,
|
||||
obscurePassword: obscurePassword ?? this.obscurePassword,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class WifiConnectForm extends _$WifiConnectForm {
|
||||
@override
|
||||
WifiConnectFormState build() => const WifiConnectFormState();
|
||||
|
||||
void setCanSave(bool value) {
|
||||
if (value == state.canSave) return;
|
||||
state = state.copyWith(canSave: value);
|
||||
}
|
||||
|
||||
void togglePasswordVisibility() {
|
||||
state = state.copyWith(obscurePassword: !state.obscurePassword);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'wifi_connect_form_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(WifiConnectForm)
|
||||
const wifiConnectFormProvider = WifiConnectFormProvider._();
|
||||
|
||||
final class WifiConnectFormProvider
|
||||
extends $NotifierProvider<WifiConnectForm, WifiConnectFormState> {
|
||||
const WifiConnectFormProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'wifiConnectFormProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$wifiConnectFormHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
WifiConnectForm create() => WifiConnectForm();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(WifiConnectFormState value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<WifiConnectFormState>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$wifiConnectFormHash() => r'44d3409aa045e7a50174ce54e69a24beaf128566';
|
||||
|
||||
abstract class _$WifiConnectForm extends $Notifier<WifiConnectFormState> {
|
||||
WifiConnectFormState build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<WifiConnectFormState, WifiConnectFormState>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<WifiConnectFormState, WifiConnectFormState>,
|
||||
WifiConnectFormState,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:legacy_device_state/legacy_device_state.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:settings/src/core/providers/wifi_repository_provider.dart';
|
||||
import 'package:settings/src/features/wifi_settings/domain/entities/wifi_network_entity.dart';
|
||||
import 'package:settings/src/features/wifi_settings/presentation/providers/saved_wifi_networks_provider.dart';
|
||||
import 'package:settings/src/features/wifi_settings/presentation/providers/wifi_current_network_provider.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
part 'wifi_controller.g.dart';
|
||||
|
||||
enum WifiControllerAction { connectAndSave, remove, set }
|
||||
|
||||
@riverpod
|
||||
class WifiController extends _$WifiController {
|
||||
WifiControllerAction? _lastAction;
|
||||
|
||||
WifiControllerAction? get lastAction => _lastAction;
|
||||
|
||||
@override
|
||||
FutureOr<void> build() {}
|
||||
|
||||
Future<void> connectAndSave({
|
||||
required String deviceIdentificator,
|
||||
required String ssid,
|
||||
required String bssid,
|
||||
required String password,
|
||||
}) async {
|
||||
_lastAction = WifiControllerAction.connectAndSave;
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
await ref.read(commandsRepositoryProvider).send(
|
||||
request: SendCommandRequestModel(
|
||||
device: deviceIdentificator,
|
||||
command: DeviceCommand.setWifi,
|
||||
data: {'ssid': ssid, 'bssid': bssid, 'password': password},
|
||||
),
|
||||
);
|
||||
await ref.read(wifiRepositoryProvider).createWifiNetwork(
|
||||
id: const Uuid().v4(),
|
||||
deviceIdentificator: deviceIdentificator,
|
||||
ssid: ssid,
|
||||
bssid: bssid,
|
||||
password: password,
|
||||
);
|
||||
ref.invalidate(savedWifiNetworksProvider(deviceIdentificator));
|
||||
final networks = await ref
|
||||
.read(savedWifiNetworksProvider(deviceIdentificator).future);
|
||||
unawaited(
|
||||
ref
|
||||
.read(sfTrackingProvider)
|
||||
.legacySettingsWifiAdded(totalCount: networks.length),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> removeNetwork({
|
||||
required String deviceIdentificator,
|
||||
required String networkId,
|
||||
}) async {
|
||||
_lastAction = WifiControllerAction.remove;
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
await ref
|
||||
.read(wifiRepositoryProvider)
|
||||
.deleteWifiNetwork(networkId: networkId);
|
||||
ref.invalidate(savedWifiNetworksProvider(deviceIdentificator));
|
||||
final networks = await ref
|
||||
.read(savedWifiNetworksProvider(deviceIdentificator).future);
|
||||
unawaited(
|
||||
ref
|
||||
.read(sfTrackingProvider)
|
||||
.legacySettingsWifiRemoved(totalCount: networks.length),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> setNetwork({
|
||||
required String deviceIdentificator,
|
||||
required WifiNetworkEntity network,
|
||||
}) async {
|
||||
_lastAction = WifiControllerAction.set;
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
await ref.read(commandsRepositoryProvider).send(
|
||||
request: SendCommandRequestModel(
|
||||
device: deviceIdentificator,
|
||||
command: DeviceCommand.setWifi,
|
||||
data: {
|
||||
'ssid': network.ssid,
|
||||
'bssid': network.bssid,
|
||||
'password': network.password ?? '',
|
||||
},
|
||||
),
|
||||
);
|
||||
unawaited(
|
||||
ref
|
||||
.read(wifiCurrentNetworkProvider.notifier)
|
||||
.requestCurrent(deviceIdentificator),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'wifi_controller.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(WifiController)
|
||||
const wifiControllerProvider = WifiControllerProvider._();
|
||||
|
||||
final class WifiControllerProvider
|
||||
extends $AsyncNotifierProvider<WifiController, void> {
|
||||
const WifiControllerProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'wifiControllerProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$wifiControllerHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
WifiController create() => WifiController();
|
||||
}
|
||||
|
||||
String _$wifiControllerHash() => r'498786d32573636cb57284969a25f64936f91a6e';
|
||||
|
||||
abstract class _$WifiController extends $AsyncNotifier<void> {
|
||||
FutureOr<void> build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
build();
|
||||
final ref = this.ref as $Ref<AsyncValue<void>, void>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<AsyncValue<void>, void>,
|
||||
AsyncValue<void>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:legacy_device_state/legacy_device_state.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:settings/src/features/wifi_settings/domain/entities/wifi_network_entity.dart';
|
||||
import 'package:settings/src/features/wifi_settings/presentation/providers/web_socket_service_provider.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
|
||||
part 'wifi_current_network_provider.g.dart';
|
||||
|
||||
class WifiCurrentNetworkState {
|
||||
const WifiCurrentNetworkState({this.network, this.isLoading = false});
|
||||
final WifiNetworkEntity? network;
|
||||
final bool isLoading;
|
||||
|
||||
WifiCurrentNetworkState copyWith({
|
||||
WifiNetworkEntity? network,
|
||||
bool? isLoading,
|
||||
bool clearNetwork = false,
|
||||
}) {
|
||||
return WifiCurrentNetworkState(
|
||||
network: clearNetwork ? null : (network ?? this.network),
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class WifiCurrentNetwork extends _$WifiCurrentNetwork {
|
||||
Timer? _timeout;
|
||||
|
||||
@override
|
||||
WifiCurrentNetworkState build() {
|
||||
final sub = ref
|
||||
.read(webSocketServiceProvider)
|
||||
.events
|
||||
.listen(_onEvent);
|
||||
ref.onDispose(() {
|
||||
sub.cancel();
|
||||
_timeout?.cancel();
|
||||
});
|
||||
return const WifiCurrentNetworkState();
|
||||
}
|
||||
|
||||
void _onEvent(WebSocketEvent event) {
|
||||
if (event is! WifiEvent) return;
|
||||
_timeout?.cancel();
|
||||
state = state.copyWith(
|
||||
network: WifiNetworkEntity(
|
||||
id: '',
|
||||
ssid: event.ssid,
|
||||
bssid: event.bssid,
|
||||
password: event.password,
|
||||
),
|
||||
isLoading: false,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> requestCurrent(String deviceIdentificator) async {
|
||||
state = state.copyWith(isLoading: true);
|
||||
try {
|
||||
await ref.read(commandsRepositoryProvider).send(
|
||||
request: SendCommandRequestModel(
|
||||
device: deviceIdentificator,
|
||||
command: DeviceCommand.wifiCurrent,
|
||||
),
|
||||
);
|
||||
} catch (_) {
|
||||
state = state.copyWith(isLoading: false);
|
||||
return;
|
||||
}
|
||||
_timeout?.cancel();
|
||||
_timeout = Timer(const Duration(seconds: 15), () {
|
||||
if (state.isLoading) state = state.copyWith(isLoading: false);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'wifi_current_network_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(WifiCurrentNetwork)
|
||||
const wifiCurrentNetworkProvider = WifiCurrentNetworkProvider._();
|
||||
|
||||
final class WifiCurrentNetworkProvider
|
||||
extends $NotifierProvider<WifiCurrentNetwork, WifiCurrentNetworkState> {
|
||||
const WifiCurrentNetworkProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'wifiCurrentNetworkProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$wifiCurrentNetworkHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
WifiCurrentNetwork create() => WifiCurrentNetwork();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(WifiCurrentNetworkState value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<WifiCurrentNetworkState>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$wifiCurrentNetworkHash() =>
|
||||
r'308b925398753ccb641bd602f4efa61f8b5bc58f';
|
||||
|
||||
abstract class _$WifiCurrentNetwork extends $Notifier<WifiCurrentNetworkState> {
|
||||
WifiCurrentNetworkState build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref =
|
||||
this.ref as $Ref<WifiCurrentNetworkState, WifiCurrentNetworkState>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<WifiCurrentNetworkState, WifiCurrentNetworkState>,
|
||||
WifiCurrentNetworkState,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:legacy_device_state/legacy_device_state.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:settings/src/features/wifi_settings/domain/entities/scanned_wifi_network.dart';
|
||||
import 'package:settings/src/features/wifi_settings/presentation/providers/web_socket_service_provider.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
|
||||
part 'wifi_scan_provider.g.dart';
|
||||
|
||||
class WifiScanTimeoutException implements Exception {
|
||||
const WifiScanTimeoutException();
|
||||
}
|
||||
|
||||
class WifiScanState {
|
||||
const WifiScanState({
|
||||
this.networks = const [],
|
||||
this.isScanning = false,
|
||||
this.secondsRemaining = 0,
|
||||
});
|
||||
|
||||
final List<ScannedWifiNetwork> networks;
|
||||
final bool isScanning;
|
||||
final int secondsRemaining;
|
||||
|
||||
WifiScanState copyWith({
|
||||
List<ScannedWifiNetwork>? networks,
|
||||
bool? isScanning,
|
||||
int? secondsRemaining,
|
||||
}) {
|
||||
return WifiScanState(
|
||||
networks: networks ?? this.networks,
|
||||
isScanning: isScanning ?? this.isScanning,
|
||||
secondsRemaining: secondsRemaining ?? this.secondsRemaining,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class WifiScan extends _$WifiScan {
|
||||
static const _scanDurationSeconds = 30;
|
||||
|
||||
Timer? _countdown;
|
||||
Completer<void>? _completer;
|
||||
|
||||
@override
|
||||
WifiScanState build() {
|
||||
final sub = ref
|
||||
.read(webSocketServiceProvider)
|
||||
.events
|
||||
.listen(_onEvent);
|
||||
ref.onDispose(() {
|
||||
sub.cancel();
|
||||
_countdown?.cancel();
|
||||
if (_completer != null && !_completer!.isCompleted) {
|
||||
_completer!.complete();
|
||||
}
|
||||
});
|
||||
return const WifiScanState();
|
||||
}
|
||||
|
||||
void _onEvent(WebSocketEvent event) {
|
||||
if (event is! WifiSearchEvent) return;
|
||||
_countdown?.cancel();
|
||||
final networks = event.wifis
|
||||
.map(
|
||||
(wifi) => ScannedWifiNetwork(
|
||||
ssid: wifi['ssid'] as String? ?? '',
|
||||
bssid: wifi['bssid'] as String? ?? '',
|
||||
),
|
||||
)
|
||||
.where((network) => network.ssid.isNotEmpty)
|
||||
.toList();
|
||||
state = state.copyWith(
|
||||
networks: networks,
|
||||
isScanning: false,
|
||||
secondsRemaining: 0,
|
||||
);
|
||||
if (_completer != null && !_completer!.isCompleted) {
|
||||
_completer!.complete();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> scan(String deviceIdentificator) async {
|
||||
state = state.copyWith(
|
||||
isScanning: true,
|
||||
secondsRemaining: _scanDurationSeconds,
|
||||
);
|
||||
|
||||
await ref.read(commandsRepositoryProvider).send(
|
||||
request: SendCommandRequestModel(
|
||||
device: deviceIdentificator,
|
||||
command: DeviceCommand.wifiSearch,
|
||||
),
|
||||
);
|
||||
|
||||
_completer = Completer<void>();
|
||||
_countdown?.cancel();
|
||||
_countdown = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||
final remaining = state.secondsRemaining - 1;
|
||||
if (remaining <= 0) {
|
||||
_countdown?.cancel();
|
||||
if (state.isScanning) {
|
||||
state = state.copyWith(isScanning: false, secondsRemaining: 0);
|
||||
if (!_completer!.isCompleted) {
|
||||
_completer!.completeError(const WifiScanTimeoutException());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state = state.copyWith(secondsRemaining: remaining);
|
||||
}
|
||||
});
|
||||
|
||||
return _completer!.future;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'wifi_scan_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(WifiScan)
|
||||
const wifiScanProvider = WifiScanProvider._();
|
||||
|
||||
final class WifiScanProvider
|
||||
extends $NotifierProvider<WifiScan, WifiScanState> {
|
||||
const WifiScanProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'wifiScanProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$wifiScanHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
WifiScan create() => WifiScan();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(WifiScanState value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<WifiScanState>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$wifiScanHash() => r'8a0d4bbb2c7514505c6868691b32720846f0470a';
|
||||
|
||||
abstract class _$WifiScan extends $Notifier<WifiScanState> {
|
||||
WifiScanState build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<WifiScanState, WifiScanState>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<WifiScanState, WifiScanState>,
|
||||
WifiScanState,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
@@ -1,356 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:legacy_device_state/legacy_device_state.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../../../../core/domain/repositories/wifi_repository.dart';
|
||||
import '../../../../core/providers/wifi_repository_provider.dart';
|
||||
import '../../domain/entities/scanned_wifi_network.dart';
|
||||
import '../../domain/entities/wifi_network_entity.dart';
|
||||
import 'wifi_settings_view_state.dart';
|
||||
|
||||
final wifiSettingsViewModelProvider =
|
||||
NotifierProvider.autoDispose<WifiSettingsViewModel, WifiSettingsViewState>(
|
||||
WifiSettingsViewModel.new,
|
||||
);
|
||||
|
||||
class WifiSettingsViewModel extends Notifier<WifiSettingsViewState> {
|
||||
late final WifiRepository _repository;
|
||||
late final CommandsRepository _commands;
|
||||
late final SfTrackingRepository _tracking;
|
||||
late final WebSocketService _webSocket;
|
||||
StreamSubscription<WebSocketEvent>? _webSocketSubscription;
|
||||
Timer? _scanCountdown;
|
||||
Timer? _currentNetworkTimeout;
|
||||
|
||||
static const _scanDurationSeconds = 30;
|
||||
|
||||
@override
|
||||
WifiSettingsViewState build() {
|
||||
_repository = ref.read(wifiRepositoryProvider);
|
||||
_commands = ref.read(commandsRepositoryProvider);
|
||||
_tracking = ref.read(sfTrackingProvider);
|
||||
_webSocket = GetIt.I<WebSocketService>();
|
||||
|
||||
_webSocketSubscription = _webSocket.events.listen(_onWebSocketEvent);
|
||||
ref.onDispose(() {
|
||||
_webSocketSubscription?.cancel();
|
||||
_scanCountdown?.cancel();
|
||||
_currentNetworkTimeout?.cancel();
|
||||
});
|
||||
|
||||
Future.microtask(_load);
|
||||
return const WifiSettingsViewState();
|
||||
}
|
||||
|
||||
String? get _identificator =>
|
||||
ref.read(selectedDeviceProvider).value?.identificator;
|
||||
|
||||
void _onWebSocketEvent(WebSocketEvent event) {
|
||||
switch (event) {
|
||||
case WifiEvent():
|
||||
debugPrint('[WiFi] WS received WIFI_CURRENT: ssid=${event.ssid} bssid=${event.bssid}');
|
||||
_currentNetworkTimeout?.cancel();
|
||||
final network = WifiNetworkEntity(
|
||||
id: '',
|
||||
ssid: event.ssid,
|
||||
bssid: event.bssid,
|
||||
password: event.password,
|
||||
);
|
||||
state = state.copyWith(
|
||||
currentNetwork: network,
|
||||
isConnecting: false,
|
||||
isLoadingCurrentNetwork: false,
|
||||
);
|
||||
case WifiSearchEvent():
|
||||
debugPrint('[WiFi] WS received WIFI_SEARCH: ${event.wifis.length} networks');
|
||||
_scanCountdown?.cancel();
|
||||
final networks = event.wifis
|
||||
.map(
|
||||
(wifi) => ScannedWifiNetwork(
|
||||
ssid: wifi['ssid'] as String? ?? '',
|
||||
bssid: wifi['bssid'] as String? ?? '',
|
||||
),
|
||||
)
|
||||
.where((network) => network.ssid.isNotEmpty)
|
||||
.toList();
|
||||
state = state.copyWith(
|
||||
availableNetworks: networks,
|
||||
isScanning: false,
|
||||
scanSecondsRemaining: 0,
|
||||
);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
final identificator = _identificator;
|
||||
if (identificator == null) {
|
||||
state = state.copyWith(isLoading: false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
debugPrint('[WiFi] loading saved networks for $identificator');
|
||||
final networks = await _repository.getWifiNetworks(
|
||||
deviceIdentificator: identificator,
|
||||
);
|
||||
if (!ref.mounted) return;
|
||||
debugPrint('[WiFi] loaded ${networks.length} saved networks');
|
||||
|
||||
state = state.copyWith(
|
||||
savedNetworks: networks,
|
||||
isLoading: false,
|
||||
isLoadingCurrentNetwork: true,
|
||||
);
|
||||
|
||||
debugPrint('[WiFi] sending WIFI_CURRENT command');
|
||||
unawaited(
|
||||
_commands
|
||||
.send(
|
||||
request: SendCommandRequestModel(
|
||||
device: identificator,
|
||||
command: DeviceCommand.wifiCurrent,
|
||||
),
|
||||
)
|
||||
.catchError((_) {
|
||||
debugPrint('[WiFi] WIFI_CURRENT command failed');
|
||||
if (ref.mounted) {
|
||||
state = state.copyWith(isLoadingCurrentNetwork: false);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
_currentNetworkTimeout?.cancel();
|
||||
_currentNetworkTimeout = Timer(const Duration(seconds: 15), () {
|
||||
if (!ref.mounted) return;
|
||||
if (state.isLoadingCurrentNetwork) {
|
||||
state = state.copyWith(isLoadingCurrentNetwork: false);
|
||||
}
|
||||
});
|
||||
} catch (_) {
|
||||
if (!ref.mounted) return;
|
||||
state = state.copyWith(
|
||||
isLoading: false,
|
||||
error: WifiSettingsError.loadFailed,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> scanNetworks() async {
|
||||
final identificator = _identificator;
|
||||
if (identificator == null) return;
|
||||
|
||||
state = state.copyWith(
|
||||
isScanning: true,
|
||||
scanSecondsRemaining: _scanDurationSeconds,
|
||||
error: null,
|
||||
);
|
||||
|
||||
try {
|
||||
debugPrint('[WiFi] sending WIFI_SEARCH command');
|
||||
await _commands.send(
|
||||
request: SendCommandRequestModel(
|
||||
device: identificator,
|
||||
command: DeviceCommand.wifiSearch,
|
||||
),
|
||||
);
|
||||
debugPrint('[WiFi] WIFI_SEARCH sent, waiting for WS response (${_scanDurationSeconds}s timeout)');
|
||||
|
||||
_scanCountdown?.cancel();
|
||||
|
||||
_scanCountdown = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||
if (!ref.mounted) {
|
||||
_scanCountdown?.cancel();
|
||||
return;
|
||||
}
|
||||
final remaining = state.scanSecondsRemaining - 1;
|
||||
if (remaining <= 0) {
|
||||
_scanCountdown?.cancel();
|
||||
if (state.isScanning) {
|
||||
state = state.copyWith(
|
||||
isScanning: false,
|
||||
scanSecondsRemaining: 0,
|
||||
error: WifiSettingsError.scanFailed,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
state = state.copyWith(scanSecondsRemaining: remaining);
|
||||
}
|
||||
});
|
||||
} catch (_) {
|
||||
if (!ref.mounted) return;
|
||||
_scanCountdown?.cancel();
|
||||
state = state.copyWith(
|
||||
isScanning: false,
|
||||
scanSecondsRemaining: 0,
|
||||
error: WifiSettingsError.scanFailed,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> connectAndSave({
|
||||
required String ssid,
|
||||
required String bssid,
|
||||
required String password,
|
||||
}) async {
|
||||
final identificator = _identificator;
|
||||
if (identificator == null) return;
|
||||
|
||||
state = state.copyWith(isConnecting: true, error: null, success: null);
|
||||
|
||||
try {
|
||||
debugPrint('[WiFi] connecting to $ssid ($bssid)');
|
||||
await _commands.send(
|
||||
request: SendCommandRequestModel(
|
||||
device: identificator,
|
||||
command: DeviceCommand.setWifi,
|
||||
data: {'ssid': ssid, 'bssid': bssid, 'password': password},
|
||||
),
|
||||
);
|
||||
debugPrint('[WiFi] SET_WIFI sent, saving network');
|
||||
|
||||
final id = const Uuid().v4();
|
||||
await _repository.createWifiNetwork(
|
||||
id: id,
|
||||
deviceIdentificator: identificator,
|
||||
ssid: ssid,
|
||||
bssid: bssid,
|
||||
password: password,
|
||||
);
|
||||
if (!ref.mounted) return;
|
||||
|
||||
final networks = await _repository.getWifiNetworks(
|
||||
deviceIdentificator: identificator,
|
||||
);
|
||||
if (!ref.mounted) return;
|
||||
|
||||
debugPrint('[WiFi] network saved, total: ${networks.length}');
|
||||
unawaited(_tracking.legacySettingsWifiAdded(totalCount: networks.length));
|
||||
|
||||
state = state.copyWith(
|
||||
savedNetworks: networks,
|
||||
isConnecting: false,
|
||||
success: WifiSettingsSuccess.networkSaved,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('[WiFi] connectAndSave failed: $e');
|
||||
if (!ref.mounted) return;
|
||||
state = state.copyWith(
|
||||
isConnecting: false,
|
||||
error: WifiSettingsError.saveFailed,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> removeNetwork(String networkId) async {
|
||||
final identificator = _identificator;
|
||||
if (identificator == null) return;
|
||||
|
||||
state = state.copyWith(isSaving: true, error: null, success: null);
|
||||
|
||||
try {
|
||||
debugPrint('[WiFi] deleting network $networkId');
|
||||
await _repository.deleteWifiNetwork(networkId: networkId);
|
||||
if (!ref.mounted) return;
|
||||
|
||||
final networks = await _repository.getWifiNetworks(
|
||||
deviceIdentificator: identificator,
|
||||
);
|
||||
if (!ref.mounted) return;
|
||||
|
||||
debugPrint('[WiFi] network deleted, remaining: ${networks.length}');
|
||||
unawaited(
|
||||
_tracking.legacySettingsWifiRemoved(totalCount: networks.length),
|
||||
);
|
||||
|
||||
state = state.copyWith(
|
||||
savedNetworks: networks,
|
||||
isSaving: false,
|
||||
success: WifiSettingsSuccess.networkDeleted,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('[WiFi] delete failed: $e');
|
||||
if (!ref.mounted) return;
|
||||
state = state.copyWith(
|
||||
isSaving: false,
|
||||
error: WifiSettingsError.deleteFailed,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setNetwork(WifiNetworkEntity network) async {
|
||||
final identificator = _identificator;
|
||||
if (identificator == null) return;
|
||||
|
||||
state = state.copyWith(isConnecting: true, error: null, success: null);
|
||||
|
||||
try {
|
||||
debugPrint('[WiFi] setting saved network: ${network.ssid} (${network.bssid})');
|
||||
await _commands.send(
|
||||
request: SendCommandRequestModel(
|
||||
device: identificator,
|
||||
command: DeviceCommand.setWifi,
|
||||
data: {
|
||||
'ssid': network.ssid,
|
||||
'bssid': network.bssid,
|
||||
'password': network.password ?? '',
|
||||
},
|
||||
),
|
||||
);
|
||||
if (!ref.mounted) return;
|
||||
debugPrint('[WiFi] SET_WIFI sent, requesting WIFI_CURRENT');
|
||||
|
||||
state = state.copyWith(
|
||||
isConnecting: false,
|
||||
isLoadingCurrentNetwork: true,
|
||||
success: WifiSettingsSuccess.networkSet,
|
||||
);
|
||||
|
||||
unawaited(
|
||||
_commands
|
||||
.send(
|
||||
request: SendCommandRequestModel(
|
||||
device: identificator,
|
||||
command: DeviceCommand.wifiCurrent,
|
||||
),
|
||||
)
|
||||
.catchError((_) {
|
||||
debugPrint('[WiFi] WIFI_CURRENT after set failed');
|
||||
if (ref.mounted) {
|
||||
state = state.copyWith(isLoadingCurrentNetwork: false);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
_currentNetworkTimeout?.cancel();
|
||||
_currentNetworkTimeout = Timer(const Duration(seconds: 15), () {
|
||||
if (!ref.mounted) return;
|
||||
if (state.isLoadingCurrentNetwork) {
|
||||
state = state.copyWith(isLoadingCurrentNetwork: false);
|
||||
}
|
||||
});
|
||||
} catch (_) {
|
||||
if (!ref.mounted) return;
|
||||
state = state.copyWith(
|
||||
isConnecting: false,
|
||||
error: WifiSettingsError.setFailed,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void clearError() {
|
||||
if (state.error != null) state = state.copyWith(error: null);
|
||||
}
|
||||
|
||||
void clearSuccess() {
|
||||
if (state.success != null) state = state.copyWith(success: null);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import '../../domain/entities/scanned_wifi_network.dart';
|
||||
import '../../domain/entities/wifi_network_entity.dart';
|
||||
|
||||
part 'wifi_settings_view_state.freezed.dart';
|
||||
|
||||
enum WifiSettingsError { loadFailed, scanFailed, connectFailed, saveFailed, deleteFailed, setFailed }
|
||||
|
||||
enum WifiSettingsSuccess { networkSaved, networkDeleted, connected, networkSet }
|
||||
|
||||
@freezed
|
||||
abstract class WifiSettingsViewState with _$WifiSettingsViewState {
|
||||
const factory WifiSettingsViewState({
|
||||
@Default([]) List<WifiNetworkEntity> savedNetworks,
|
||||
@Default([]) List<ScannedWifiNetwork> availableNetworks,
|
||||
WifiNetworkEntity? currentNetwork,
|
||||
@Default(true) bool isLoading,
|
||||
@Default(false) bool isLoadingCurrentNetwork,
|
||||
@Default(false) bool isScanning,
|
||||
@Default(0) int scanSecondsRemaining,
|
||||
@Default(false) bool isSaving,
|
||||
@Default(false) bool isConnecting,
|
||||
WifiSettingsError? error,
|
||||
WifiSettingsSuccess? success,
|
||||
}) = _WifiSettingsViewState;
|
||||
}
|
||||
@@ -1,337 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'wifi_settings_view_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$WifiSettingsViewState {
|
||||
|
||||
List<WifiNetworkEntity> get savedNetworks; List<ScannedWifiNetwork> get availableNetworks; WifiNetworkEntity? get currentNetwork; bool get isLoading; bool get isLoadingCurrentNetwork; bool get isScanning; int get scanSecondsRemaining; bool get isSaving; bool get isConnecting; WifiSettingsError? get error; WifiSettingsSuccess? get success;
|
||||
/// Create a copy of WifiSettingsViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$WifiSettingsViewStateCopyWith<WifiSettingsViewState> get copyWith => _$WifiSettingsViewStateCopyWithImpl<WifiSettingsViewState>(this as WifiSettingsViewState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is WifiSettingsViewState&&const DeepCollectionEquality().equals(other.savedNetworks, savedNetworks)&&const DeepCollectionEquality().equals(other.availableNetworks, availableNetworks)&&(identical(other.currentNetwork, currentNetwork) || other.currentNetwork == currentNetwork)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingCurrentNetwork, isLoadingCurrentNetwork) || other.isLoadingCurrentNetwork == isLoadingCurrentNetwork)&&(identical(other.isScanning, isScanning) || other.isScanning == isScanning)&&(identical(other.scanSecondsRemaining, scanSecondsRemaining) || other.scanSecondsRemaining == scanSecondsRemaining)&&(identical(other.isSaving, isSaving) || other.isSaving == isSaving)&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.error, error) || other.error == error)&&(identical(other.success, success) || other.success == success));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(savedNetworks),const DeepCollectionEquality().hash(availableNetworks),currentNetwork,isLoading,isLoadingCurrentNetwork,isScanning,scanSecondsRemaining,isSaving,isConnecting,error,success);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'WifiSettingsViewState(savedNetworks: $savedNetworks, availableNetworks: $availableNetworks, currentNetwork: $currentNetwork, isLoading: $isLoading, isLoadingCurrentNetwork: $isLoadingCurrentNetwork, isScanning: $isScanning, scanSecondsRemaining: $scanSecondsRemaining, isSaving: $isSaving, isConnecting: $isConnecting, error: $error, success: $success)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $WifiSettingsViewStateCopyWith<$Res> {
|
||||
factory $WifiSettingsViewStateCopyWith(WifiSettingsViewState value, $Res Function(WifiSettingsViewState) _then) = _$WifiSettingsViewStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
List<WifiNetworkEntity> savedNetworks, List<ScannedWifiNetwork> availableNetworks, WifiNetworkEntity? currentNetwork, bool isLoading, bool isLoadingCurrentNetwork, bool isScanning, int scanSecondsRemaining, bool isSaving, bool isConnecting, WifiSettingsError? error, WifiSettingsSuccess? success
|
||||
});
|
||||
|
||||
|
||||
$WifiNetworkEntityCopyWith<$Res>? get currentNetwork;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$WifiSettingsViewStateCopyWithImpl<$Res>
|
||||
implements $WifiSettingsViewStateCopyWith<$Res> {
|
||||
_$WifiSettingsViewStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final WifiSettingsViewState _self;
|
||||
final $Res Function(WifiSettingsViewState) _then;
|
||||
|
||||
/// Create a copy of WifiSettingsViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? savedNetworks = null,Object? availableNetworks = null,Object? currentNetwork = freezed,Object? isLoading = null,Object? isLoadingCurrentNetwork = null,Object? isScanning = null,Object? scanSecondsRemaining = null,Object? isSaving = null,Object? isConnecting = null,Object? error = freezed,Object? success = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
savedNetworks: null == savedNetworks ? _self.savedNetworks : savedNetworks // ignore: cast_nullable_to_non_nullable
|
||||
as List<WifiNetworkEntity>,availableNetworks: null == availableNetworks ? _self.availableNetworks : availableNetworks // ignore: cast_nullable_to_non_nullable
|
||||
as List<ScannedWifiNetwork>,currentNetwork: freezed == currentNetwork ? _self.currentNetwork : currentNetwork // ignore: cast_nullable_to_non_nullable
|
||||
as WifiNetworkEntity?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isLoadingCurrentNetwork: null == isLoadingCurrentNetwork ? _self.isLoadingCurrentNetwork : isLoadingCurrentNetwork // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isScanning: null == isScanning ? _self.isScanning : isScanning // ignore: cast_nullable_to_non_nullable
|
||||
as bool,scanSecondsRemaining: null == scanSecondsRemaining ? _self.scanSecondsRemaining : scanSecondsRemaining // ignore: cast_nullable_to_non_nullable
|
||||
as int,isSaving: null == isSaving ? _self.isSaving : isSaving // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isConnecting: null == isConnecting ? _self.isConnecting : isConnecting // ignore: cast_nullable_to_non_nullable
|
||||
as bool,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
|
||||
as WifiSettingsError?,success: freezed == success ? _self.success : success // ignore: cast_nullable_to_non_nullable
|
||||
as WifiSettingsSuccess?,
|
||||
));
|
||||
}
|
||||
/// Create a copy of WifiSettingsViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$WifiNetworkEntityCopyWith<$Res>? get currentNetwork {
|
||||
if (_self.currentNetwork == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $WifiNetworkEntityCopyWith<$Res>(_self.currentNetwork!, (value) {
|
||||
return _then(_self.copyWith(currentNetwork: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [WifiSettingsViewState].
|
||||
extension WifiSettingsViewStatePatterns on WifiSettingsViewState {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _WifiSettingsViewState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _WifiSettingsViewState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _WifiSettingsViewState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _WifiSettingsViewState():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _WifiSettingsViewState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _WifiSettingsViewState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<WifiNetworkEntity> savedNetworks, List<ScannedWifiNetwork> availableNetworks, WifiNetworkEntity? currentNetwork, bool isLoading, bool isLoadingCurrentNetwork, bool isScanning, int scanSecondsRemaining, bool isSaving, bool isConnecting, WifiSettingsError? error, WifiSettingsSuccess? success)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _WifiSettingsViewState() when $default != null:
|
||||
return $default(_that.savedNetworks,_that.availableNetworks,_that.currentNetwork,_that.isLoading,_that.isLoadingCurrentNetwork,_that.isScanning,_that.scanSecondsRemaining,_that.isSaving,_that.isConnecting,_that.error,_that.success);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<WifiNetworkEntity> savedNetworks, List<ScannedWifiNetwork> availableNetworks, WifiNetworkEntity? currentNetwork, bool isLoading, bool isLoadingCurrentNetwork, bool isScanning, int scanSecondsRemaining, bool isSaving, bool isConnecting, WifiSettingsError? error, WifiSettingsSuccess? success) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _WifiSettingsViewState():
|
||||
return $default(_that.savedNetworks,_that.availableNetworks,_that.currentNetwork,_that.isLoading,_that.isLoadingCurrentNetwork,_that.isScanning,_that.scanSecondsRemaining,_that.isSaving,_that.isConnecting,_that.error,_that.success);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<WifiNetworkEntity> savedNetworks, List<ScannedWifiNetwork> availableNetworks, WifiNetworkEntity? currentNetwork, bool isLoading, bool isLoadingCurrentNetwork, bool isScanning, int scanSecondsRemaining, bool isSaving, bool isConnecting, WifiSettingsError? error, WifiSettingsSuccess? success)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _WifiSettingsViewState() when $default != null:
|
||||
return $default(_that.savedNetworks,_that.availableNetworks,_that.currentNetwork,_that.isLoading,_that.isLoadingCurrentNetwork,_that.isScanning,_that.scanSecondsRemaining,_that.isSaving,_that.isConnecting,_that.error,_that.success);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _WifiSettingsViewState implements WifiSettingsViewState {
|
||||
const _WifiSettingsViewState({final List<WifiNetworkEntity> savedNetworks = const [], final List<ScannedWifiNetwork> availableNetworks = const [], this.currentNetwork, this.isLoading = true, this.isLoadingCurrentNetwork = false, this.isScanning = false, this.scanSecondsRemaining = 0, this.isSaving = false, this.isConnecting = false, this.error, this.success}): _savedNetworks = savedNetworks,_availableNetworks = availableNetworks;
|
||||
|
||||
|
||||
final List<WifiNetworkEntity> _savedNetworks;
|
||||
@override@JsonKey() List<WifiNetworkEntity> get savedNetworks {
|
||||
if (_savedNetworks is EqualUnmodifiableListView) return _savedNetworks;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_savedNetworks);
|
||||
}
|
||||
|
||||
final List<ScannedWifiNetwork> _availableNetworks;
|
||||
@override@JsonKey() List<ScannedWifiNetwork> get availableNetworks {
|
||||
if (_availableNetworks is EqualUnmodifiableListView) return _availableNetworks;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_availableNetworks);
|
||||
}
|
||||
|
||||
@override final WifiNetworkEntity? currentNetwork;
|
||||
@override@JsonKey() final bool isLoading;
|
||||
@override@JsonKey() final bool isLoadingCurrentNetwork;
|
||||
@override@JsonKey() final bool isScanning;
|
||||
@override@JsonKey() final int scanSecondsRemaining;
|
||||
@override@JsonKey() final bool isSaving;
|
||||
@override@JsonKey() final bool isConnecting;
|
||||
@override final WifiSettingsError? error;
|
||||
@override final WifiSettingsSuccess? success;
|
||||
|
||||
/// Create a copy of WifiSettingsViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$WifiSettingsViewStateCopyWith<_WifiSettingsViewState> get copyWith => __$WifiSettingsViewStateCopyWithImpl<_WifiSettingsViewState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WifiSettingsViewState&&const DeepCollectionEquality().equals(other._savedNetworks, _savedNetworks)&&const DeepCollectionEquality().equals(other._availableNetworks, _availableNetworks)&&(identical(other.currentNetwork, currentNetwork) || other.currentNetwork == currentNetwork)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingCurrentNetwork, isLoadingCurrentNetwork) || other.isLoadingCurrentNetwork == isLoadingCurrentNetwork)&&(identical(other.isScanning, isScanning) || other.isScanning == isScanning)&&(identical(other.scanSecondsRemaining, scanSecondsRemaining) || other.scanSecondsRemaining == scanSecondsRemaining)&&(identical(other.isSaving, isSaving) || other.isSaving == isSaving)&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.error, error) || other.error == error)&&(identical(other.success, success) || other.success == success));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_savedNetworks),const DeepCollectionEquality().hash(_availableNetworks),currentNetwork,isLoading,isLoadingCurrentNetwork,isScanning,scanSecondsRemaining,isSaving,isConnecting,error,success);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'WifiSettingsViewState(savedNetworks: $savedNetworks, availableNetworks: $availableNetworks, currentNetwork: $currentNetwork, isLoading: $isLoading, isLoadingCurrentNetwork: $isLoadingCurrentNetwork, isScanning: $isScanning, scanSecondsRemaining: $scanSecondsRemaining, isSaving: $isSaving, isConnecting: $isConnecting, error: $error, success: $success)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$WifiSettingsViewStateCopyWith<$Res> implements $WifiSettingsViewStateCopyWith<$Res> {
|
||||
factory _$WifiSettingsViewStateCopyWith(_WifiSettingsViewState value, $Res Function(_WifiSettingsViewState) _then) = __$WifiSettingsViewStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
List<WifiNetworkEntity> savedNetworks, List<ScannedWifiNetwork> availableNetworks, WifiNetworkEntity? currentNetwork, bool isLoading, bool isLoadingCurrentNetwork, bool isScanning, int scanSecondsRemaining, bool isSaving, bool isConnecting, WifiSettingsError? error, WifiSettingsSuccess? success
|
||||
});
|
||||
|
||||
|
||||
@override $WifiNetworkEntityCopyWith<$Res>? get currentNetwork;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$WifiSettingsViewStateCopyWithImpl<$Res>
|
||||
implements _$WifiSettingsViewStateCopyWith<$Res> {
|
||||
__$WifiSettingsViewStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _WifiSettingsViewState _self;
|
||||
final $Res Function(_WifiSettingsViewState) _then;
|
||||
|
||||
/// Create a copy of WifiSettingsViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? savedNetworks = null,Object? availableNetworks = null,Object? currentNetwork = freezed,Object? isLoading = null,Object? isLoadingCurrentNetwork = null,Object? isScanning = null,Object? scanSecondsRemaining = null,Object? isSaving = null,Object? isConnecting = null,Object? error = freezed,Object? success = freezed,}) {
|
||||
return _then(_WifiSettingsViewState(
|
||||
savedNetworks: null == savedNetworks ? _self._savedNetworks : savedNetworks // ignore: cast_nullable_to_non_nullable
|
||||
as List<WifiNetworkEntity>,availableNetworks: null == availableNetworks ? _self._availableNetworks : availableNetworks // ignore: cast_nullable_to_non_nullable
|
||||
as List<ScannedWifiNetwork>,currentNetwork: freezed == currentNetwork ? _self.currentNetwork : currentNetwork // ignore: cast_nullable_to_non_nullable
|
||||
as WifiNetworkEntity?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isLoadingCurrentNetwork: null == isLoadingCurrentNetwork ? _self.isLoadingCurrentNetwork : isLoadingCurrentNetwork // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isScanning: null == isScanning ? _self.isScanning : isScanning // ignore: cast_nullable_to_non_nullable
|
||||
as bool,scanSecondsRemaining: null == scanSecondsRemaining ? _self.scanSecondsRemaining : scanSecondsRemaining // ignore: cast_nullable_to_non_nullable
|
||||
as int,isSaving: null == isSaving ? _self.isSaving : isSaving // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isConnecting: null == isConnecting ? _self.isConnecting : isConnecting // ignore: cast_nullable_to_non_nullable
|
||||
as bool,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
|
||||
as WifiSettingsError?,success: freezed == success ? _self.success : success // ignore: cast_nullable_to_non_nullable
|
||||
as WifiSettingsSuccess?,
|
||||
));
|
||||
}
|
||||
|
||||
/// Create a copy of WifiSettingsViewState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$WifiNetworkEntityCopyWith<$Res>? get currentNetwork {
|
||||
if (_self.currentNetwork == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $WifiNetworkEntityCopyWith<$Res>(_self.currentNetwork!, (value) {
|
||||
return _then(_self.copyWith(currentNetwork: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -1,13 +1,14 @@
|
||||
import 'package:legacy_theme/legacy_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:legacy_theme/legacy_theme.dart';
|
||||
import 'package:settings/src/features/wifi_settings/presentation/providers/wifi_connect_form_provider.dart';
|
||||
import 'package:settings/src/features/wifi_settings/presentation/providers/wifi_controller.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
import 'package:utils/utils.dart';
|
||||
|
||||
import '../state/wifi_settings_view_model.dart';
|
||||
|
||||
void showConnectWifiSheet(
|
||||
BuildContext context, {
|
||||
required String deviceIdentificator,
|
||||
String? ssid,
|
||||
String? bssid,
|
||||
}) {
|
||||
@@ -15,15 +16,24 @@ void showConnectWifiSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (_) => _ConnectWifiSheet(ssid: ssid, bssid: bssid),
|
||||
builder: (_) => _ConnectWifiSheet(
|
||||
deviceIdentificator: deviceIdentificator,
|
||||
ssid: ssid,
|
||||
bssid: bssid,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _ConnectWifiSheet extends ConsumerStatefulWidget {
|
||||
final String deviceIdentificator;
|
||||
final String? ssid;
|
||||
final String? bssid;
|
||||
|
||||
const _ConnectWifiSheet({this.ssid, this.bssid});
|
||||
const _ConnectWifiSheet({
|
||||
required this.deviceIdentificator,
|
||||
this.ssid,
|
||||
this.bssid,
|
||||
});
|
||||
|
||||
@override
|
||||
ConsumerState<_ConnectWifiSheet> createState() => _ConnectWifiSheetState();
|
||||
@@ -33,46 +43,55 @@ class _ConnectWifiSheetState extends ConsumerState<_ConnectWifiSheet> {
|
||||
late final TextEditingController _ssidController;
|
||||
late final TextEditingController _bssidController;
|
||||
final _passwordController = TextEditingController();
|
||||
bool _obscurePassword = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_ssidController = TextEditingController(text: widget.ssid ?? '');
|
||||
_bssidController = TextEditingController(text: widget.bssid ?? '');
|
||||
_ssidController.addListener(_refreshCanSave);
|
||||
_bssidController.addListener(_refreshCanSave);
|
||||
_passwordController.addListener(_refreshCanSave);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _refreshCanSave());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_ssidController.removeListener(_refreshCanSave);
|
||||
_bssidController.removeListener(_refreshCanSave);
|
||||
_passwordController.removeListener(_refreshCanSave);
|
||||
_ssidController.dispose();
|
||||
_bssidController.dispose();
|
||||
_passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool get _canSave =>
|
||||
_ssidController.text.trim().isNotEmpty &&
|
||||
_bssidController.text.trim().isNotEmpty &&
|
||||
_passwordController.text.trim().isNotEmpty;
|
||||
void _refreshCanSave() {
|
||||
final canSave = _ssidController.text.trim().isNotEmpty &&
|
||||
_bssidController.text.trim().isNotEmpty &&
|
||||
_passwordController.text.trim().isNotEmpty;
|
||||
ref.read(wifiConnectFormProvider.notifier).setCanSave(canSave);
|
||||
}
|
||||
|
||||
void _submit() {
|
||||
if (!_canSave) return;
|
||||
final formState = ref.read(wifiConnectFormProvider);
|
||||
if (!formState.canSave) return;
|
||||
|
||||
final vm = ref.read(wifiSettingsViewModelProvider.notifier);
|
||||
vm.connectAndSave(
|
||||
ssid: _ssidController.text.trim(),
|
||||
bssid: _bssidController.text.trim(),
|
||||
password: _passwordController.text.trim(),
|
||||
);
|
||||
ref.read(wifiControllerProvider.notifier).connectAndSave(
|
||||
deviceIdentificator: widget.deviceIdentificator,
|
||||
ssid: _ssidController.text.trim(),
|
||||
bssid: _bssidController.text.trim(),
|
||||
password: _passwordController.text.trim(),
|
||||
);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final primaryColor = context.sfColors.legacyPrimary;
|
||||
final isConnecting = ref.watch(
|
||||
wifiSettingsViewModelProvider.select((s) => s.isConnecting),
|
||||
);
|
||||
final formState = ref.watch(wifiConnectFormProvider);
|
||||
final isSubmitting = ref
|
||||
.watch(wifiControllerProvider.select((s) => s.isLoading));
|
||||
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
|
||||
final hasPrefilled = widget.ssid != null;
|
||||
|
||||
@@ -81,7 +100,7 @@ class _ConnectWifiSheetState extends ConsumerState<_ConnectWifiSheet> {
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
@@ -119,12 +138,13 @@ class _ConnectWifiSheetState extends ConsumerState<_ConnectWifiSheet> {
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _canSave && !isConnecting ? _submit : null,
|
||||
child: isConnecting
|
||||
onPressed: formState.canSave && !isSubmitting
|
||||
? _submit
|
||||
: null,
|
||||
child: isSubmitting
|
||||
? SizedBox(
|
||||
width: SizeUtils.getByScreen(small: 20, big: 22),
|
||||
height:
|
||||
SizeUtils.getByScreen(small: 20, big: 22),
|
||||
height: SizeUtils.getByScreen(small: 20, big: 22),
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: primaryColor,
|
||||
@@ -133,7 +153,9 @@ class _ConnectWifiSheetState extends ConsumerState<_ConnectWifiSheet> {
|
||||
: Text(
|
||||
context.translate(I18n.save),
|
||||
style: TextStyle(
|
||||
color: _canSave ? primaryColor : Colors.grey,
|
||||
color: formState.canSave
|
||||
? primaryColor
|
||||
: Colors.grey,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: SizeUtils.getByScreen(
|
||||
small: 16,
|
||||
@@ -152,11 +174,10 @@ class _ConnectWifiSheetState extends ConsumerState<_ConnectWifiSheet> {
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _ssidController,
|
||||
readOnly: hasPrefilled,
|
||||
onChanged: (_) => setState(() {}),
|
||||
decoration: _inputDecoration(
|
||||
hintText: context.translate(I18n.wifiSsidHint),
|
||||
primaryColor: primaryColor,
|
||||
@@ -170,11 +191,10 @@ class _ConnectWifiSheetState extends ConsumerState<_ConnectWifiSheet> {
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _bssidController,
|
||||
readOnly: hasPrefilled,
|
||||
onChanged: (_) => setState(() {}),
|
||||
decoration: _inputDecoration(
|
||||
hintText: context.translate(I18n.wifiBssidHint),
|
||||
primaryColor: primaryColor,
|
||||
@@ -188,20 +208,20 @@ class _ConnectWifiSheetState extends ConsumerState<_ConnectWifiSheet> {
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _passwordController,
|
||||
obscureText: _obscurePassword,
|
||||
onChanged: (_) => setState(() {}),
|
||||
obscureText: formState.obscurePassword,
|
||||
decoration: _inputDecoration(
|
||||
hintText: context.translate(I18n.wifiPasswordHint),
|
||||
primaryColor: primaryColor,
|
||||
).copyWith(
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () =>
|
||||
setState(() => _obscurePassword = !_obscurePassword),
|
||||
onPressed: ref
|
||||
.read(wifiConnectFormProvider.notifier)
|
||||
.togglePasswordVisibility,
|
||||
icon: Icon(
|
||||
_obscurePassword
|
||||
formState.obscurePassword
|
||||
? Icons.visibility_off
|
||||
: Icons.visibility,
|
||||
color: Colors.grey,
|
||||
@@ -226,18 +246,21 @@ class _ConnectWifiSheetState extends ConsumerState<_ConnectWifiSheet> {
|
||||
return InputDecoration(
|
||||
hintText: hintText,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
borderSide: BorderSide(color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
borderSide: BorderSide(color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
borderSide: BorderSide(color: primaryColor, width: 2),
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 14, vertical: 14),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 14,
|
||||
vertical: 14,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +1,145 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:legacy_theme/legacy_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:legacy_device_state/legacy_device_state.dart';
|
||||
import 'package:legacy_theme/legacy_theme.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
import 'package:settings/src/features/wifi_settings/domain/entities/wifi_network_entity.dart';
|
||||
import 'package:settings/src/features/wifi_settings/presentation/providers/saved_wifi_networks_provider.dart';
|
||||
import 'package:settings/src/features/wifi_settings/presentation/providers/wifi_controller.dart';
|
||||
import 'package:settings/src/features/wifi_settings/presentation/providers/wifi_current_network_provider.dart';
|
||||
import 'package:settings/src/features/wifi_settings/presentation/providers/wifi_scan_provider.dart';
|
||||
import 'package:settings/src/features/wifi_settings/presentation/widgets/add_wifi_network_sheet.dart';
|
||||
import 'package:settings/src/features/wifi_settings/presentation/widgets/available_wifi_network_card.dart';
|
||||
import 'package:settings/src/features/wifi_settings/presentation/widgets/wifi_network_card.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
import 'package:utils/utils.dart';
|
||||
|
||||
import 'state/wifi_settings_view_model.dart';
|
||||
import 'state/wifi_settings_view_state.dart';
|
||||
import 'widgets/available_wifi_network_card.dart';
|
||||
import 'widgets/add_wifi_network_sheet.dart';
|
||||
import 'widgets/wifi_network_card.dart';
|
||||
|
||||
class WifiSettingsScreen extends ConsumerWidget {
|
||||
class WifiSettingsScreen extends ConsumerStatefulWidget {
|
||||
final NavigationContract navigationContract;
|
||||
|
||||
const WifiSettingsScreen({super.key, required this.navigationContract});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final state = ref.watch(wifiSettingsViewModelProvider);
|
||||
final vm = ref.read(wifiSettingsViewModelProvider.notifier);
|
||||
ConsumerState<WifiSettingsScreen> createState() => _WifiSettingsScreenState();
|
||||
}
|
||||
|
||||
class _WifiSettingsScreenState extends ConsumerState<WifiSettingsScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final identificator =
|
||||
ref.read(selectedDeviceProvider).value?.identificator;
|
||||
if (identificator == null) return;
|
||||
ref
|
||||
.read(wifiCurrentNetworkProvider.notifier)
|
||||
.requestCurrent(identificator);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final primaryColor = context.sfColors.legacyPrimary;
|
||||
final device = ref.watch(selectedDeviceProvider).value;
|
||||
final identificator = device?.identificator;
|
||||
|
||||
ref.listen(wifiSettingsViewModelProvider.select((s) => s.error), (
|
||||
_,
|
||||
error,
|
||||
) {
|
||||
if (error == null) return;
|
||||
final message = switch (error) {
|
||||
WifiSettingsError.loadFailed => context.translate(I18n.wifiLoadError),
|
||||
WifiSettingsError.scanFailed => context.translate(I18n.wifiScanFailed),
|
||||
WifiSettingsError.connectFailed =>
|
||||
context.translate(I18n.wifiConnectFailed),
|
||||
WifiSettingsError.saveFailed => context.translate(I18n.wifiSaveFailed),
|
||||
WifiSettingsError.deleteFailed =>
|
||||
context.translate(I18n.wifiDeleteFailed),
|
||||
WifiSettingsError.setFailed =>
|
||||
context.translate(I18n.wifiSetFailed),
|
||||
ref.watch(wifiCurrentNetworkProvider);
|
||||
ref.watch(wifiScanProvider);
|
||||
|
||||
ref.listen(wifiControllerProvider, (prev, next) async {
|
||||
if (prev == null || !prev.isLoading || next.isLoading) return;
|
||||
if (next.hasError) {
|
||||
await next.showErrorOn(context);
|
||||
return;
|
||||
}
|
||||
final action =
|
||||
ref.read(wifiControllerProvider.notifier).lastAction;
|
||||
final message = switch (action) {
|
||||
WifiControllerAction.connectAndSave => I18n.wifiNetworkSaved,
|
||||
WifiControllerAction.remove => I18n.wifiNetworkDeleted,
|
||||
WifiControllerAction.set => I18n.wifiNetworkSet,
|
||||
null => I18n.deviceUpdatedSuccess,
|
||||
};
|
||||
showTopSnackbar(context, message: message, type: MessageType.error);
|
||||
vm.clearError();
|
||||
await showSuccessDialog(context, message);
|
||||
});
|
||||
|
||||
ref.listen(wifiSettingsViewModelProvider.select((s) => s.success), (
|
||||
_,
|
||||
success,
|
||||
) {
|
||||
if (success == null) return;
|
||||
final message = switch (success) {
|
||||
WifiSettingsSuccess.networkSaved =>
|
||||
context.translate(I18n.wifiNetworkSaved),
|
||||
WifiSettingsSuccess.networkDeleted =>
|
||||
context.translate(I18n.wifiNetworkDeleted),
|
||||
WifiSettingsSuccess.connected => context.translate(I18n.wifiConnected),
|
||||
WifiSettingsSuccess.networkSet =>
|
||||
context.translate(I18n.wifiNetworkSet),
|
||||
};
|
||||
showTopSnackbar(context, message: message, type: MessageType.success);
|
||||
vm.clearSuccess();
|
||||
});
|
||||
if (identificator == null) {
|
||||
return Scaffold(
|
||||
appBar: _appBar(context, primaryColor, onScan: null),
|
||||
body: const SizedBox.shrink(),
|
||||
);
|
||||
}
|
||||
|
||||
final savedAsync = ref.watch(savedWifiNetworksProvider(identificator));
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
automaticallyImplyLeading: false,
|
||||
leading: IconButton(
|
||||
onPressed: () => navigationContract.goBack(),
|
||||
icon: Icon(
|
||||
Icons.adaptive.arrow_back,
|
||||
color: primaryColor,
|
||||
size: SizeUtils.getByScreen(small: 32, big: 28),
|
||||
appBar: _appBar(
|
||||
context,
|
||||
primaryColor,
|
||||
onScan: () async {
|
||||
if (!await guardDeviceCommand(context, ref)) return;
|
||||
if (!context.mounted) return;
|
||||
try {
|
||||
await ref
|
||||
.read(wifiScanProvider.notifier)
|
||||
.scan(identificator);
|
||||
} on WifiScanTimeoutException {
|
||||
if (!context.mounted) return;
|
||||
await showErrorDialog(context, I18n.wifiScanFailed);
|
||||
} catch (_) {
|
||||
if (!context.mounted) return;
|
||||
await showErrorDialog(context, I18n.wifiScanFailed);
|
||||
}
|
||||
},
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: savedAsync.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (_, __) => Center(
|
||||
child: Text(context.translate(I18n.wifiLoadError)),
|
||||
),
|
||||
data: (savedNetworks) => _Body(
|
||||
deviceIdentificator: identificator,
|
||||
savedNetworks: savedNetworks,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
'WiFi',
|
||||
style: TextStyle(
|
||||
fontSize: SizeUtils.getByScreen(small: 20, big: 19),
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
PreferredSizeWidget _appBar(
|
||||
BuildContext context,
|
||||
Color primaryColor, {
|
||||
required VoidCallback? onScan,
|
||||
}) {
|
||||
return AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
automaticallyImplyLeading: false,
|
||||
leading: IconButton(
|
||||
onPressed: () => widget.navigationContract.goBack(),
|
||||
icon: Icon(
|
||||
Icons.adaptive.arrow_back,
|
||||
color: primaryColor,
|
||||
size: SizeUtils.getByScreen(small: 32, big: 28),
|
||||
),
|
||||
actions: [
|
||||
),
|
||||
title: Text(
|
||||
'WiFi',
|
||||
style: TextStyle(
|
||||
fontSize: SizeUtils.getByScreen(small: 20, big: 19),
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
if (onScan != null)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
right: SizeUtils.getByScreen(small: 16, big: 14),
|
||||
@@ -98,10 +150,7 @@ class WifiSettingsScreen extends ConsumerWidget {
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: IconButton(
|
||||
onPressed: () async {
|
||||
if (!await guardDeviceCommand(context, ref)) return;
|
||||
vm.scanNetworks();
|
||||
},
|
||||
onPressed: onScan,
|
||||
icon: Icon(
|
||||
Icons.wifi_find,
|
||||
color: Colors.white,
|
||||
@@ -110,31 +159,31 @@ class WifiSettingsScreen extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: state.isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _Body(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Body extends ConsumerWidget {
|
||||
const _Body();
|
||||
final String deviceIdentificator;
|
||||
final List<WifiNetworkEntity> savedNetworks;
|
||||
|
||||
const _Body({
|
||||
required this.deviceIdentificator,
|
||||
required this.savedNetworks,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final state = ref.watch(wifiSettingsViewModelProvider);
|
||||
final primaryColor = context.sfColors.legacyPrimary;
|
||||
final current = ref.watch(wifiCurrentNetworkProvider);
|
||||
final scan = ref.watch(wifiScanProvider);
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: SizeUtils.getByScreen(
|
||||
small: EdgeInsets.symmetric(horizontal: 22, vertical: 10),
|
||||
big: EdgeInsets.symmetric(horizontal: 21, vertical: 8),
|
||||
small: const EdgeInsets.symmetric(horizontal: 22, vertical: 10),
|
||||
big: const EdgeInsets.symmetric(horizontal: 21, vertical: 8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -147,7 +196,7 @@ class _Body extends ConsumerWidget {
|
||||
context.translate(I18n.wifiCurrentNetwork),
|
||||
primaryColor,
|
||||
),
|
||||
if (state.isLoadingCurrentNetwork)
|
||||
if (current.isLoading)
|
||||
SizedBox(
|
||||
width: SizeUtils.getByScreen(small: 16, big: 14),
|
||||
height: SizeUtils.getByScreen(small: 16, big: 14),
|
||||
@@ -159,12 +208,12 @@ class _Body extends ConsumerWidget {
|
||||
],
|
||||
),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 8, big: 6)),
|
||||
if (state.currentNetwork != null)
|
||||
if (current.network != null)
|
||||
_CurrentNetworkCard(
|
||||
ssid: state.currentNetwork!.ssid,
|
||||
bssid: state.currentNetwork!.bssid,
|
||||
ssid: current.network!.ssid,
|
||||
bssid: current.network!.bssid,
|
||||
)
|
||||
else if (!state.isLoadingCurrentNetwork)
|
||||
else if (!current.isLoading)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: SizeUtils.getByScreen(small: 12, big: 10),
|
||||
@@ -173,12 +222,14 @@ class _Body extends ConsumerWidget {
|
||||
context.translate(I18n.wifiNoCurrentNetwork),
|
||||
style: TextStyle(
|
||||
fontSize: SizeUtils.getByScreen(small: 14, big: 15),
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withAlpha(178),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (state.availableNetworks.isNotEmpty || state.isScanning) ...[
|
||||
if (scan.networks.isNotEmpty || scan.isScanning) ...[
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 14)),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
@@ -188,19 +239,21 @@ class _Body extends ConsumerWidget {
|
||||
context.translate(I18n.wifiAvailableNetworks),
|
||||
primaryColor,
|
||||
),
|
||||
if (state.isScanning)
|
||||
if (scan.isScanning)
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'${state.scanSecondsRemaining}s',
|
||||
'${scan.secondsRemaining}s',
|
||||
style: TextStyle(
|
||||
fontSize: SizeUtils.getByScreen(small: 13, big: 12),
|
||||
fontSize:
|
||||
SizeUtils.getByScreen(small: 13, big: 12),
|
||||
fontWeight: FontWeight.w600,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
SizedBox(width: SizeUtils.getByScreen(small: 6, big: 5)),
|
||||
SizedBox(
|
||||
width: SizeUtils.getByScreen(small: 6, big: 5)),
|
||||
SizedBox(
|
||||
width: SizeUtils.getByScreen(small: 16, big: 14),
|
||||
height: SizeUtils.getByScreen(small: 16, big: 14),
|
||||
@@ -214,11 +267,12 @@ class _Body extends ConsumerWidget {
|
||||
],
|
||||
),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 8, big: 6)),
|
||||
...state.availableNetworks.map(
|
||||
...scan.networks.map(
|
||||
(network) => AvailableWifiNetworkCard(
|
||||
network: network,
|
||||
onTap: () => showConnectWifiSheet(
|
||||
context,
|
||||
deviceIdentificator: deviceIdentificator,
|
||||
ssid: network.ssid,
|
||||
bssid: network.bssid,
|
||||
),
|
||||
@@ -230,12 +284,12 @@ class _Body extends ConsumerWidget {
|
||||
context,
|
||||
context.translate(
|
||||
I18n.wifiSavedNetworks,
|
||||
args: {'count': state.savedNetworks.length.toString()},
|
||||
args: {'count': savedNetworks.length.toString()},
|
||||
),
|
||||
primaryColor,
|
||||
),
|
||||
SizedBox(height: SizeUtils.getByScreen(small: 8, big: 6)),
|
||||
if (state.savedNetworks.isEmpty)
|
||||
if (savedNetworks.isEmpty)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: SizeUtils.getByScreen(small: 12, big: 10),
|
||||
@@ -244,20 +298,23 @@ class _Body extends ConsumerWidget {
|
||||
context.translate(I18n.noWifiNetworks),
|
||||
style: TextStyle(
|
||||
fontSize: SizeUtils.getByScreen(small: 14, big: 15),
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withAlpha(178),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
...state.savedNetworks.map(
|
||||
...savedNetworks.map(
|
||||
(network) => WifiNetworkCard(
|
||||
network: network,
|
||||
onTap: () async {
|
||||
if (!await guardDeviceCommand(context, ref)) return;
|
||||
ref
|
||||
.read(wifiSettingsViewModelProvider.notifier)
|
||||
.setNetwork(network);
|
||||
ref.read(wifiControllerProvider.notifier).setNetwork(
|
||||
deviceIdentificator: deviceIdentificator,
|
||||
network: network,
|
||||
);
|
||||
},
|
||||
onDelete: () => _confirmDelete(context, ref, network),
|
||||
),
|
||||
@@ -283,15 +340,15 @@ class _Body extends ConsumerWidget {
|
||||
Future<void> _confirmDelete(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
dynamic network,
|
||||
WifiNetworkEntity network,
|
||||
) async {
|
||||
if (!await guardDeviceCommand(context, ref)) return;
|
||||
if (!context.mounted) return;
|
||||
final primaryColor = context.sfColors.legacyPrimary;
|
||||
|
||||
showDialog(
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: Text(context.translate(I18n.removeWifiNetwork)),
|
||||
content: Text(
|
||||
context.translate(
|
||||
@@ -301,7 +358,7 @@ class _Body extends ConsumerWidget {
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
onPressed: () => Navigator.pop(dialogContext),
|
||||
child: Text(
|
||||
context.translate(I18n.cancel),
|
||||
style: TextStyle(color: primaryColor),
|
||||
@@ -309,10 +366,11 @@ class _Body extends ConsumerWidget {
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(wifiSettingsViewModelProvider.notifier)
|
||||
.removeNetwork(network.id);
|
||||
Navigator.pop(context);
|
||||
ref.read(wifiControllerProvider.notifier).removeNetwork(
|
||||
deviceIdentificator: deviceIdentificator,
|
||||
networkId: network.id,
|
||||
);
|
||||
Navigator.pop(dialogContext);
|
||||
},
|
||||
child: Text(
|
||||
context.translate(I18n.delete),
|
||||
@@ -329,10 +387,7 @@ class _CurrentNetworkCard extends StatelessWidget {
|
||||
final String ssid;
|
||||
final String bssid;
|
||||
|
||||
const _CurrentNetworkCard({
|
||||
required this.ssid,
|
||||
required this.bssid,
|
||||
});
|
||||
const _CurrentNetworkCard({required this.ssid, required this.bssid});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -347,7 +402,7 @@ class _CurrentNetworkCard extends StatelessWidget {
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
@@ -369,11 +424,13 @@ class _CurrentNetworkCard extends StatelessWidget {
|
||||
fontSize: SizeUtils.getByScreen(small: 15, big: 16),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 2),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
bssid,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withAlpha(178),
|
||||
fontSize: SizeUtils.getByScreen(small: 13, big: 14),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:legacy_device_state/legacy_device_state.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:settings/src/core/domain/repositories/wifi_repository.dart';
|
||||
import 'package:settings/src/core/providers/wifi_repository_provider.dart';
|
||||
import 'package:settings/src/features/wifi_settings/domain/entities/wifi_network_entity.dart';
|
||||
import 'package:settings/src/features/wifi_settings/presentation/providers/web_socket_service_provider.dart';
|
||||
import 'package:settings/src/features/wifi_settings/presentation/providers/wifi_controller.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
import 'package:sf_shared/testing.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
|
||||
class MockWifiRepository extends Mock implements WifiRepository {}
|
||||
|
||||
class MockCommandsRepository extends Mock implements CommandsRepository {}
|
||||
|
||||
class FakeWebSocketService implements WebSocketService {
|
||||
final _controller = StreamController<WebSocketEvent>.broadcast();
|
||||
@override
|
||||
Stream<WebSocketEvent> get events => _controller.stream;
|
||||
|
||||
void close() => _controller.close();
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
const _network = WifiNetworkEntity(
|
||||
id: 'net-1',
|
||||
ssid: 'home',
|
||||
bssid: '00:11:22:33:44:55',
|
||||
password: 'secret',
|
||||
);
|
||||
|
||||
void main() {
|
||||
setUpAll(() {
|
||||
registerFallbackValue(
|
||||
SendCommandRequestModel(
|
||||
device: 'x',
|
||||
command: DeviceCommand.wifiCurrent,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
ProviderContainer buildContainer({
|
||||
required WifiRepository repo,
|
||||
required CommandsRepository commands,
|
||||
}) {
|
||||
final ws = FakeWebSocketService();
|
||||
return makeContainer(
|
||||
overrides: [
|
||||
wifiRepositoryProvider.overrideWithValue(repo),
|
||||
commandsRepositoryProvider.overrideWithValue(commands),
|
||||
webSocketServiceProvider.overrideWithValue(ws),
|
||||
sfTrackingProvider.overrideWithValue(
|
||||
SfTrackingRepository(clients: const []),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
group('WifiController.connectAndSave', () {
|
||||
test('sends SET_WIFI, persists, and refetches on success', () async {
|
||||
final repo = MockWifiRepository();
|
||||
final commands = MockCommandsRepository();
|
||||
|
||||
when(() => commands.send(request: any(named: 'request')))
|
||||
.thenAnswer((_) async {});
|
||||
when(
|
||||
() => repo.createWifiNetwork(
|
||||
id: any(named: 'id'),
|
||||
deviceIdentificator: any(named: 'deviceIdentificator'),
|
||||
ssid: any(named: 'ssid'),
|
||||
bssid: any(named: 'bssid'),
|
||||
password: any(named: 'password'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
when(
|
||||
() => repo.getWifiNetworks(
|
||||
deviceIdentificator: any(named: 'deviceIdentificator'),
|
||||
),
|
||||
).thenAnswer((_) async => const [_network]);
|
||||
|
||||
final container = buildContainer(repo: repo, commands: commands);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container.read(wifiControllerProvider.notifier).connectAndSave(
|
||||
deviceIdentificator: 'imei-1',
|
||||
ssid: 'home',
|
||||
bssid: '00:11:22:33:44:55',
|
||||
password: 'secret',
|
||||
);
|
||||
|
||||
expect(container.read(wifiControllerProvider), isA<AsyncData<void>>());
|
||||
verify(() => commands.send(request: any(named: 'request'))).called(1);
|
||||
verify(
|
||||
() => repo.createWifiNetwork(
|
||||
id: any(named: 'id'),
|
||||
deviceIdentificator: 'imei-1',
|
||||
ssid: 'home',
|
||||
bssid: '00:11:22:33:44:55',
|
||||
password: 'secret',
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('exposes AsyncError when command fails', () async {
|
||||
final repo = MockWifiRepository();
|
||||
final commands = MockCommandsRepository();
|
||||
|
||||
when(() => commands.send(request: any(named: 'request')))
|
||||
.thenThrow(const ApiException(message: 'boom', isNetworkError: true));
|
||||
|
||||
final container = buildContainer(repo: repo, commands: commands);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container.read(wifiControllerProvider.notifier).connectAndSave(
|
||||
deviceIdentificator: 'imei-1',
|
||||
ssid: 'home',
|
||||
bssid: '00:11:22:33:44:55',
|
||||
password: 'secret',
|
||||
);
|
||||
|
||||
expect(container.read(wifiControllerProvider), isA<AsyncError<void>>());
|
||||
verifyNever(
|
||||
() => repo.createWifiNetwork(
|
||||
id: any(named: 'id'),
|
||||
deviceIdentificator: any(named: 'deviceIdentificator'),
|
||||
ssid: any(named: 'ssid'),
|
||||
bssid: any(named: 'bssid'),
|
||||
password: any(named: 'password'),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('WifiController.removeNetwork', () {
|
||||
test('deletes and transitions to AsyncData', () async {
|
||||
final repo = MockWifiRepository();
|
||||
final commands = MockCommandsRepository();
|
||||
|
||||
when(() => repo.deleteWifiNetwork(networkId: any(named: 'networkId')))
|
||||
.thenAnswer((_) async {});
|
||||
when(
|
||||
() => repo.getWifiNetworks(
|
||||
deviceIdentificator: any(named: 'deviceIdentificator'),
|
||||
),
|
||||
).thenAnswer((_) async => const []);
|
||||
|
||||
final container = buildContainer(repo: repo, commands: commands);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container.read(wifiControllerProvider.notifier).removeNetwork(
|
||||
deviceIdentificator: 'imei-1',
|
||||
networkId: 'net-1',
|
||||
);
|
||||
|
||||
expect(container.read(wifiControllerProvider), isA<AsyncData<void>>());
|
||||
verify(() => repo.deleteWifiNetwork(networkId: 'net-1')).called(1);
|
||||
});
|
||||
|
||||
test('exposes AsyncError when repository fails', () async {
|
||||
final repo = MockWifiRepository();
|
||||
final commands = MockCommandsRepository();
|
||||
|
||||
when(() => repo.deleteWifiNetwork(networkId: any(named: 'networkId')))
|
||||
.thenThrow(const ApiException(message: 'boom', isNetworkError: true));
|
||||
|
||||
final container = buildContainer(repo: repo, commands: commands);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container.read(wifiControllerProvider.notifier).removeNetwork(
|
||||
deviceIdentificator: 'imei-1',
|
||||
networkId: 'net-1',
|
||||
);
|
||||
|
||||
expect(container.read(wifiControllerProvider), isA<AsyncError<void>>());
|
||||
});
|
||||
});
|
||||
|
||||
group('WifiController.setNetwork', () {
|
||||
test('sends SET_WIFI on success', () async {
|
||||
final repo = MockWifiRepository();
|
||||
final commands = MockCommandsRepository();
|
||||
|
||||
when(() => commands.send(request: any(named: 'request')))
|
||||
.thenAnswer((_) async {});
|
||||
|
||||
final container = buildContainer(repo: repo, commands: commands);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container.read(wifiControllerProvider.notifier).setNetwork(
|
||||
deviceIdentificator: 'imei-1',
|
||||
network: _network,
|
||||
);
|
||||
|
||||
expect(container.read(wifiControllerProvider), isA<AsyncData<void>>());
|
||||
final sent = verify(
|
||||
() => commands.send(request: captureAny(named: 'request')),
|
||||
).captured;
|
||||
final setWifi = sent.firstWhere(
|
||||
(r) => (r as SendCommandRequestModel).command == DeviceCommand.setWifi,
|
||||
) as SendCommandRequestModel;
|
||||
expect(setWifi.data, {
|
||||
'ssid': 'home',
|
||||
'bssid': '00:11:22:33:44:55',
|
||||
'password': 'secret',
|
||||
});
|
||||
});
|
||||
|
||||
test('exposes AsyncError when command fails', () async {
|
||||
final repo = MockWifiRepository();
|
||||
final commands = MockCommandsRepository();
|
||||
|
||||
when(() => commands.send(request: any(named: 'request')))
|
||||
.thenThrow(const ApiException(message: 'boom', isNetworkError: true));
|
||||
|
||||
final container = buildContainer(repo: repo, commands: commands);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container.read(wifiControllerProvider.notifier).setNetwork(
|
||||
deviceIdentificator: 'imei-1',
|
||||
network: _network,
|
||||
);
|
||||
|
||||
expect(container.read(wifiControllerProvider), isA<AsyncError<void>>());
|
||||
});
|
||||
});
|
||||
|
||||
group('lastAction tracking', () {
|
||||
test('records connectAndSave', () async {
|
||||
final repo = MockWifiRepository();
|
||||
final commands = MockCommandsRepository();
|
||||
when(() => commands.send(request: any(named: 'request')))
|
||||
.thenAnswer((_) async {});
|
||||
when(
|
||||
() => repo.createWifiNetwork(
|
||||
id: any(named: 'id'),
|
||||
deviceIdentificator: any(named: 'deviceIdentificator'),
|
||||
ssid: any(named: 'ssid'),
|
||||
bssid: any(named: 'bssid'),
|
||||
password: any(named: 'password'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
when(
|
||||
() => repo.getWifiNetworks(
|
||||
deviceIdentificator: any(named: 'deviceIdentificator'),
|
||||
),
|
||||
).thenAnswer((_) async => const []);
|
||||
|
||||
final container = buildContainer(repo: repo, commands: commands);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container.read(wifiControllerProvider.notifier).connectAndSave(
|
||||
deviceIdentificator: 'imei-1',
|
||||
ssid: 'home',
|
||||
bssid: '00:11:22:33:44:55',
|
||||
password: 'secret',
|
||||
);
|
||||
|
||||
expect(
|
||||
container.read(wifiControllerProvider.notifier).lastAction,
|
||||
WifiControllerAction.connectAndSave,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user