From ffff67bb04b8dc428269a4c6ec1b9dcc4c4d27de Mon Sep 17 00:00:00 2001 From: Hank Yu Date: Thu, 8 Jan 2026 13:49:19 +0800 Subject: [PATCH 1/2] refactor(pnp): Extract AutoConfigurationUIModel from JNAP layer - Add AutoConfigurationUIModel and AutoConfigurationMethodUI enum to pnp_ui_models.dart - Update PnpService.autoConfigurationCheck() to return UI model instead of JNAP model - Add _convertToUIModel() and _convertMethod() conversion helpers in PnpService - Remove JNAP AutoConfigurationSettings import from Provider layer files - Update BasePnpNotifier and PnpNotifier to use AutoConfigurationUIModel - Update all mock PnP providers to use UI model - Update router_provider.dart to use UI model field names - Update demo_overrides.dart to use UI model This change enforces architectural layer separation by ensuring Provider layer does not depend on JNAP data models directly. --- lib/demo/providers/demo_overrides.dart | 6 +- .../instant_setup/models/pnp_ui_models.dart | 70 +++++++++++++++++++ .../providers/mock_pnp_providers.dart | 41 ++++++----- .../instant_setup/providers/pnp_provider.dart | 6 +- .../instant_setup/services/pnp_service.dart | 42 ++++++++--- lib/route/router_provider.dart | 22 +++--- 6 files changed, 140 insertions(+), 47 deletions(-) diff --git a/lib/demo/providers/demo_overrides.dart b/lib/demo/providers/demo_overrides.dart index 0ff881cc8..d2de7e0c1 100644 --- a/lib/demo/providers/demo_overrides.dart +++ b/lib/demo/providers/demo_overrides.dart @@ -10,7 +10,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:privacy_gui/core/cloud/providers/geolocation/geolocation_provider.dart'; import 'package:privacy_gui/core/cloud/providers/geolocation/geolocation_state.dart'; -import 'package:privacy_gui/core/jnap/models/auto_configuration_settings.dart'; +import 'package:privacy_gui/page/instant_setup/models/pnp_ui_models.dart'; import 'package:privacy_gui/demo/jnap/demo_router_repository.dart'; import 'package:privacy_gui/core/data/providers/polling_provider.dart'; import 'package:privacy_gui/core/jnap/router_repository.dart'; @@ -157,9 +157,9 @@ class _DemoPollingNotifier extends PollingNotifier { /// Demo PnP notifier - bypasses setup wizard checks class _DemoPnpNotifier extends PnpNotifier { @override - Future autoConfigurationCheck() async { + Future autoConfigurationCheck() async { debugPrint('🔌 Demo: Bypassing auto-configuration check'); - return const AutoConfigurationSettings(isAutoConfigurationSupported: false); + return const AutoConfigurationUIModel(isSupported: false); } @override diff --git a/lib/page/instant_setup/models/pnp_ui_models.dart b/lib/page/instant_setup/models/pnp_ui_models.dart index 387f3888c..abefca86b 100644 --- a/lib/page/instant_setup/models/pnp_ui_models.dart +++ b/lib/page/instant_setup/models/pnp_ui_models.dart @@ -92,6 +92,76 @@ class PnpChildNodeUIModel extends Equatable { List get props => [location, modelNumber]; } +/// The auto-configuration method for the device (UI layer). +/// +/// This enum is used by the UI layer to determine the configuration flow. +/// - [preConfigured]: Standard PnP flow for pre-configured devices. +/// - [autoParent]: Auto-parent flow for devices that connect to an existing network. +enum AutoConfigurationMethodUI { + preConfigured, + autoParent, +} + +/// Represents the auto-configuration settings for UI display. +/// +/// This model abstracts the JNAP AutoConfigurationSettings and provides +/// only the data needed by the UI layer for routing decisions. +class AutoConfigurationUIModel extends Equatable { + /// Indicates if auto-configuration is supported by the device. + final bool? isSupported; + + /// Indicates if the user has acknowledged the auto-configuration. + final bool? userAcknowledged; + + /// The method of auto-configuration. + final AutoConfigurationMethodUI? method; + + const AutoConfigurationUIModel({ + this.isSupported, + this.userAcknowledged, + this.method, + }); + + @override + List get props => [isSupported, userAcknowledged, method]; + + /// Creates a copy of this model with optional new values. + AutoConfigurationUIModel copyWith({ + bool? isSupported, + bool? userAcknowledged, + AutoConfigurationMethodUI? method, + }) { + return AutoConfigurationUIModel( + isSupported: isSupported ?? this.isSupported, + userAcknowledged: userAcknowledged ?? this.userAcknowledged, + method: method ?? this.method, + ); + } + + Map toMap() { + return { + 'isSupported': isSupported, + 'userAcknowledged': userAcknowledged, + 'method': method?.name, + }; + } + + factory AutoConfigurationUIModel.fromMap(Map map) { + return AutoConfigurationUIModel( + isSupported: map['isSupported'] as bool?, + userAcknowledged: map['userAcknowledged'] as bool?, + method: map['method'] != null + ? AutoConfigurationMethodUI.values.byName(map['method'] as String) + : null, + ); + } + + Map toJson() => toMap(); + + factory AutoConfigurationUIModel.fromJson(Map json) => + AutoConfigurationUIModel.fromMap(json); +} + /// Represents the default Wi-Fi settings for both main and guest networks. class PnpDefaultSettingsUIModel extends Equatable { /// The default SSID for the main Wi-Fi network. diff --git a/lib/page/instant_setup/providers/mock_pnp_providers.dart b/lib/page/instant_setup/providers/mock_pnp_providers.dart index 81abba3c7..ec0c1b9c3 100644 --- a/lib/page/instant_setup/providers/mock_pnp_providers.dart +++ b/lib/page/instant_setup/providers/mock_pnp_providers.dart @@ -1,5 +1,4 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacy_gui/core/jnap/models/auto_configuration_settings.dart'; import 'package:privacy_gui/core/jnap/router_repository.dart'; import 'package:privacy_gui/core/utils/logger.dart'; import 'package:privacy_gui/page/instant_setup/model/pnp_step.dart'; @@ -155,13 +154,13 @@ class BaseMockPnpNotifier extends BasePnpNotifier { } @override - Future autoConfigurationCheck() { + Future autoConfigurationCheck() { logger.d('[PnP]: Mock - autoConfigurationCheck called'); // Default to configured, acknowledged - return Future.value(const AutoConfigurationSettings( - isAutoConfigurationSupported: true, - userAcknowledgedAutoConfiguration: true, - autoConfigurationMethod: AutoConfigurationMethod.preConfigured, + return Future.value(const AutoConfigurationUIModel( + isSupported: true, + userAcknowledged: true, + method: AutoConfigurationMethodUI.preConfigured, )); } @@ -278,12 +277,12 @@ final unconfiguredPnpProvider = NotifierProvider( class UnconfiguredMockPnpNotifier extends BaseMockPnpNotifier { @override - Future autoConfigurationCheck() { + Future autoConfigurationCheck() { logger.d('[PnP]: Mock (Unconfigured) - autoConfigurationCheck called'); - return Future.value(const AutoConfigurationSettings( - isAutoConfigurationSupported: true, - userAcknowledgedAutoConfiguration: false, // This triggers PnP - autoConfigurationMethod: AutoConfigurationMethod.preConfigured, + return Future.value(const AutoConfigurationUIModel( + isSupported: true, + userAcknowledged: false, // This triggers PnP + method: AutoConfigurationMethodUI.preConfigured, )); } @@ -374,12 +373,12 @@ final fwUpdatePnpProvider = NotifierProvider( class FwUpdateMockPnpNotifier extends BaseMockPnpNotifier { @override - Future autoConfigurationCheck() { + Future autoConfigurationCheck() { logger.d('[PnP]: Mock (FW Update) - autoConfigurationCheck called'); - return Future.value(const AutoConfigurationSettings( - isAutoConfigurationSupported: true, - userAcknowledgedAutoConfiguration: false, // This triggers PnP - autoConfigurationMethod: AutoConfigurationMethod.preConfigured, + return Future.value(const AutoConfigurationUIModel( + isSupported: true, + userAcknowledged: false, // This triggers PnP + method: AutoConfigurationMethodUI.preConfigured, )); } @@ -415,13 +414,13 @@ final unconfiguredFwUpdatePnpProvider = class UnconfiguredFwUpdateMockPnpNotifier extends BaseMockPnpNotifier { @override - Future autoConfigurationCheck() { + Future autoConfigurationCheck() { logger.d( '[PnP]: Mock (Unconfigured FW Update) - autoConfigurationCheck called'); - return Future.value(const AutoConfigurationSettings( - isAutoConfigurationSupported: true, - userAcknowledgedAutoConfiguration: false, // This triggers PnP - autoConfigurationMethod: AutoConfigurationMethod.preConfigured, + return Future.value(const AutoConfigurationUIModel( + isSupported: true, + userAcknowledged: false, // This triggers PnP + method: AutoConfigurationMethodUI.preConfigured, )); } diff --git a/lib/page/instant_setup/providers/pnp_provider.dart b/lib/page/instant_setup/providers/pnp_provider.dart index c507db140..6933e3e24 100644 --- a/lib/page/instant_setup/providers/pnp_provider.dart +++ b/lib/page/instant_setup/providers/pnp_provider.dart @@ -6,7 +6,7 @@ import 'package:privacy_gui/page/instant_setup/providers/pnp_exception.dart'; import 'package:privacy_gui/page/instant_setup/providers/pnp_state.dart'; import 'package:privacy_gui/page/instant_setup/providers/pnp_step_state.dart'; import 'package:privacy_gui/page/instant_setup/services/pnp_service.dart'; -import 'package:privacy_gui/core/jnap/models/auto_configuration_settings.dart'; +import 'package:privacy_gui/page/instant_setup/models/pnp_ui_models.dart'; import '../troubleshooter/providers/pnp_troubleshooter_provider.dart'; /// The main Riverpod provider for the PnP feature. @@ -140,7 +140,7 @@ abstract class BasePnpNotifier extends Notifier { Future checkRouterConfigured(); /// Checks the auto-configuration status of the router. - Future autoConfigurationCheck(); + Future autoConfigurationCheck(); /// Checks if the admin password has been set by the user. Future isRouterPasswordSet(); @@ -450,7 +450,7 @@ class PnpNotifier extends BasePnpNotifier { } @override - Future autoConfigurationCheck() { + Future autoConfigurationCheck() { final pnpService = ref.read(pnpServiceProvider); return pnpService.autoConfigurationCheck(); diff --git a/lib/page/instant_setup/services/pnp_service.dart b/lib/page/instant_setup/services/pnp_service.dart index 5a8f165f9..6b956c46b 100644 --- a/lib/page/instant_setup/services/pnp_service.dart +++ b/lib/page/instant_setup/services/pnp_service.dart @@ -207,7 +207,10 @@ class PnpService with AvailabilityChecker { } /// Checks the auto-configuration status of the router. - Future autoConfigurationCheck() async { + /// + /// Returns an [AutoConfigurationUIModel] containing the auto-configuration + /// settings, or null if PnP is not supported or an error occurs. + Future autoConfigurationCheck() async { if (!serviceHelper.isSupportPnP(_rawDeviceInfo?.services)) { logger.i('[PnP]: Service - The router does NOT support PNP!'); return null; @@ -215,17 +218,40 @@ class PnpService with AvailabilityChecker { final repo = _ref.read(routerRepositoryProvider); final result = await repo .send( - JNAPAction.getAutoConfigurationSettings, - fetchRemote: true, - cacheLevel: CacheLevel.noCache, - ) - .then( - (data) => AutoConfigurationSettings.fromMap(data.output)) - .onError((error, stackTrace) => null); + JNAPAction.getAutoConfigurationSettings, + fetchRemote: true, + cacheLevel: CacheLevel.noCache, + ) + .then((data) { + final jnapModel = AutoConfigurationSettings.fromMap(data.output); + return _convertToUIModel(jnapModel); + }).onError((error, stackTrace) => null); logger.d('[PnP]: Service - Auto Configuration Check result: $result'); return result; } + /// Converts a JNAP [AutoConfigurationSettings] to an [AutoConfigurationUIModel]. + AutoConfigurationUIModel _convertToUIModel( + AutoConfigurationSettings jnapModel) { + return AutoConfigurationUIModel( + isSupported: jnapModel.isAutoConfigurationSupported, + userAcknowledged: jnapModel.userAcknowledgedAutoConfiguration, + method: _convertMethod(jnapModel.autoConfigurationMethod), + ); + } + + /// Converts JNAP AutoConfigurationMethod to UI AutoConfigurationMethodUI. + AutoConfigurationMethodUI? _convertMethod( + AutoConfigurationMethod? jnapMethod) { + if (jnapMethod == null) return null; + return switch (jnapMethod) { + AutoConfigurationMethod.preConfigured => + AutoConfigurationMethodUI.preConfigured, + AutoConfigurationMethod.autoParent => + AutoConfigurationMethodUI.autoParent, + }; + } + /// Checks if the admin password has been set by the user. Future isRouterPasswordSet() { final transaction = JNAPTransactionBuilder( diff --git a/lib/route/router_provider.dart b/lib/route/router_provider.dart index daad82d64..1e4a29d7a 100644 --- a/lib/route/router_provider.dart +++ b/lib/route/router_provider.dart @@ -7,7 +7,7 @@ import 'package:privacy_gui/constants/build_config.dart'; import 'package:privacy_gui/constants/pref_key.dart'; import 'package:privacy_gui/core/cache/linksys_cache_manager.dart'; import 'package:privacy_gui/core/jnap/actions/better_action.dart'; -import 'package:privacy_gui/core/jnap/models/auto_configuration_settings.dart'; +import 'package:privacy_gui/page/instant_setup/models/pnp_ui_models.dart'; import 'package:privacy_gui/core/jnap/models/device_info.dart'; import 'package:privacy_gui/core/data/providers/dashboard_manager_provider.dart'; import 'package:privacy_gui/core/data/providers/polling_provider.dart'; @@ -175,8 +175,8 @@ class RouterNotifier extends ChangeNotifier { if (config == null) { return LocalWhereToGo.login; } - // If isAutoConfigurationSupported is not true, then go to Login - if (config.isAutoConfigurationSupported != true) { + // If isSupported is not true, then go to Login + if (config.isSupported != true) { // Retail factory reset case - Check admin password final isAdminPasswordSet = await pnpNotifier.isRouterPasswordSet(); return isAdminPasswordSet == false @@ -186,23 +186,21 @@ class RouterNotifier extends ChangeNotifier { // AutoConfigurationSupported is true case - // AutoParent case - - if (config.autoConfigurationMethod == - AutoConfigurationMethod.autoParent) { + if (config.method == AutoConfigurationMethodUI.autoParent) { // AutoParent case - - // First Time Login -> AutoConfigurationSupported is true and userAcknowledgedAutoConfiguration is false + // First Time Login -> isSupported is true and userAcknowledged is false // Login -> else - return config.userAcknowledgedAutoConfiguration == false + return config.userAcknowledged == false ? LocalWhereToGo.firstTimeLogin : LocalWhereToGo.login; } - // Prepair case - Check isAutoConfigurationSupported and userAcknowledgedAutoConfiguration + // Prepair case - Check isSupported and userAcknowledged - final userAcknowledgedAutoConfiguration = - config.userAcknowledgedAutoConfiguration; - if (userAcknowledgedAutoConfiguration == false) { + final userAcknowledged = config.userAcknowledged; + if (userAcknowledged == false) { // PnP case - - // Go PnP -> AutoConfigurationSupported is true and userAcknowledgedAutoConfiguration is false + // Go PnP -> isSupported is true and userAcknowledged is false // Login -> else return LocalWhereToGo.pnp; } From bcab6936a6ad0d6683d2b5fbed8312c7ef578193 Mon Sep 17 00:00:00 2001 From: Hank Yu Date: Thu, 8 Jan 2026 15:41:56 +0800 Subject: [PATCH 2/2] fix(test): Update mock files for AutoConfigurationUIModel refactor - Update MockPnpNotifier.autoConfigurationCheck() return type to Future - Update MockPnpService.autoConfigurationCheck() return type to Future - Update FakePnpNotifier.autoConfigurationCheck() return type in pnp_setup_view_test.dart - Remove unused AutoConfigurationSettings import from pnp_service_mocks.dart - Update imports to use pnp_ui_models.dart instead of auto_configuration_settings.dart --- test/mocks/pnp_notifier_mocks.dart | 10 +++++----- test/mocks/pnp_service_mocks.dart | 10 ++++------ .../localizations/pnp_setup_view_test.dart | 3 +-- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/test/mocks/pnp_notifier_mocks.dart b/test/mocks/pnp_notifier_mocks.dart index 3b3a5d214..5d79831f4 100644 --- a/test/mocks/pnp_notifier_mocks.dart +++ b/test/mocks/pnp_notifier_mocks.dart @@ -8,7 +8,7 @@ import 'dart:async' as _i7; import 'package:flutter_riverpod/flutter_riverpod.dart' as _i2; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i8; -import 'package:privacy_gui/core/jnap/models/auto_configuration_settings.dart' +import 'package:privacy_gui/page/instant_setup/models/pnp_ui_models.dart' as _i9; import 'package:privacy_gui/page/instant_setup/model/pnp_step.dart' as _i10; import 'package:privacy_gui/page/instant_setup/providers/pnp_provider.dart' @@ -361,16 +361,16 @@ class MockPnpNotifier extends _i2.Notifier<_i3.PnpState> ) as _i7.Future<_i4.ConfigurationResult>); @override - _i7.Future<_i9.AutoConfigurationSettings?> autoConfigurationCheck() => + _i7.Future<_i9.AutoConfigurationUIModel?> autoConfigurationCheck() => (super.noSuchMethod( Invocation.method( #autoConfigurationCheck, [], ), - returnValue: _i7.Future<_i9.AutoConfigurationSettings?>.value(), + returnValue: _i7.Future<_i9.AutoConfigurationUIModel?>.value(), returnValueForMissingStub: - _i7.Future<_i9.AutoConfigurationSettings?>.value(), - ) as _i7.Future<_i9.AutoConfigurationSettings?>); + _i7.Future<_i9.AutoConfigurationUIModel?>.value(), + ) as _i7.Future<_i9.AutoConfigurationUIModel?>); @override _i7.Future fetchDevices() => (super.noSuchMethod( diff --git a/test/mocks/pnp_service_mocks.dart b/test/mocks/pnp_service_mocks.dart index 047a86c05..76b364604 100644 --- a/test/mocks/pnp_service_mocks.dart +++ b/test/mocks/pnp_service_mocks.dart @@ -7,8 +7,6 @@ import 'dart:async' as _i6; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i10; -import 'package:privacy_gui/core/jnap/models/auto_configuration_settings.dart' - as _i8; import 'package:privacy_gui/page/instant_setup/models/pnp_ui_models.dart' as _i2; import 'package:privacy_gui/page/instant_setup/providers/pnp_state.dart' as _i9; @@ -270,16 +268,16 @@ class MockPnpService extends _i1.Mock implements _i3.PnpService { ) as _i6.Future<_i3.ConfigurationResult>); @override - _i6.Future<_i8.AutoConfigurationSettings?> autoConfigurationCheck() => + _i6.Future<_i2.AutoConfigurationUIModel?> autoConfigurationCheck() => (super.noSuchMethod( Invocation.method( #autoConfigurationCheck, [], ), - returnValue: _i6.Future<_i8.AutoConfigurationSettings?>.value(), + returnValue: _i6.Future<_i2.AutoConfigurationUIModel?>.value(), returnValueForMissingStub: - _i6.Future<_i8.AutoConfigurationSettings?>.value(), - ) as _i6.Future<_i8.AutoConfigurationSettings?>); + _i6.Future<_i2.AutoConfigurationUIModel?>.value(), + ) as _i6.Future<_i2.AutoConfigurationUIModel?>); @override _i6.Future isRouterPasswordSet() => (super.noSuchMethod( diff --git a/test/page/instant_setup/localizations/pnp_setup_view_test.dart b/test/page/instant_setup/localizations/pnp_setup_view_test.dart index 59cfe9c2d..87c4bfb11 100644 --- a/test/page/instant_setup/localizations/pnp_setup_view_test.dart +++ b/test/page/instant_setup/localizations/pnp_setup_view_test.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:privacy_gui/core/jnap/models/auto_configuration_settings.dart'; import 'package:privacy_gui/page/instant_setup/model/pnp_step.dart'; import 'package:privacy_gui/page/instant_setup/models/pnp_ui_models.dart'; import 'package:privacy_gui/page/instant_setup/pnp_setup_view.dart'; @@ -105,7 +104,7 @@ class FakePnpNotifier extends BasePnpNotifier { Future checkRouterConfigured() async => ConfigurationResult(status: ConfigStatus.configured); @override - Future autoConfigurationCheck() async => null; + Future autoConfigurationCheck() async => null; @override Future isRouterPasswordSet() async => true; @override