Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import 'dart:convert';

import 'package:equatable/equatable.dart';

/// UI Model for DHCP Reservation display and manipulation
///
/// This is the presentation layer model used by both:
/// - DHCPReservationsProvider (reservation management)
/// - LocalNetworkSettingsProvider (LAN settings with reservations)
///
/// Represents a single DHCP reservation with MAC address, IP address, and description.
class DHCPReservationUIModel extends Equatable {
final String macAddress;
final String ipAddress;
final String description;

const DHCPReservationUIModel({
required this.macAddress,
required this.ipAddress,
required this.description,
});

DHCPReservationUIModel copyWith({
String? macAddress,
String? ipAddress,
String? description,
}) {
return DHCPReservationUIModel(
macAddress: macAddress ?? this.macAddress,
ipAddress: ipAddress ?? this.ipAddress,
description: description ?? this.description,
);
}

Map<String, dynamic> toMap() {
return {
'macAddress': macAddress,
'ipAddress': ipAddress,
'description': description,
};
}

factory DHCPReservationUIModel.fromMap(Map<String, dynamic> map) {
return DHCPReservationUIModel(
macAddress: map['macAddress'] as String,
ipAddress: map['ipAddress'] as String,
description: map['description'] as String,
);
}

String toJson() => json.encode(toMap());

factory DHCPReservationUIModel.fromJson(String source) =>
DHCPReservationUIModel.fromMap(
json.decode(source) as Map<String, dynamic>);

@override
List<Object> get props => [macAddress, ipAddress, description];

@override
bool get stringify => true;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'package:collection/collection.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:privacy_gui/core/jnap/models/lan_settings.dart';
import 'package:privacy_gui/page/advanced_settings/local_network_settings/models/dhcp_reservation_ui_model.dart';
import 'package:privacy_gui/page/advanced_settings/local_network_settings/providers/dhcp_reservations_state.dart';
import 'package:privacy_gui/page/advanced_settings/local_network_settings/providers/local_network_settings_provider.dart';
import 'package:privacy_gui/page/advanced_settings/local_network_settings/services/dhcp_reservations_service.dart';
import 'package:privacy_gui/page/instant_device/_instant_device.dart';
import 'package:privacy_gui/providers/preservable.dart';
import 'package:privacy_gui/providers/preservable_contract.dart';
Expand Down Expand Up @@ -48,17 +48,15 @@ class DHCPReservationsNotifier extends AutoDisposeNotifier<DHCPReservationState>

@override
Future<void> performSave() async {
final settings = state.settings.current;
final reservations = settings.reservations
final service = ref.read(dhcpReservationsServiceProvider);
final reservedItems = state.settings.current.reservations
.where((e) => e.reserved)
.map((e) => e.data)
.toList();
await ref
.read(localNetworkSettingProvider.notifier)
.saveReservations(reservations);
await service.saveReservations(ref, reservedItems);
}

void setInitialReservations(List<DHCPReservation> reservedList) {
void setInitialReservations(List<DHCPReservationUIModel> reservedList) {
final initialSettings = DHCPReservationsSettings(
reservations: reservedList
.map((e) => ReservedListItem(reserved: true, data: e))
Expand All @@ -80,7 +78,7 @@ class DHCPReservationsNotifier extends AutoDisposeNotifier<DHCPReservationState>
devices: deviceList
.map((e) => ReservedListItem(
reserved: false,
data: DHCPReservation(
data: DHCPReservationUIModel(
macAddress: e.macAddress,
ipAddress: e.ipv4Address,
description: e.name)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'dart:convert';

import 'package:equatable/equatable.dart';

import 'package:privacy_gui/core/jnap/models/lan_settings.dart';
import 'package:privacy_gui/page/advanced_settings/local_network_settings/models/dhcp_reservation_ui_model.dart';
import 'package:privacy_gui/providers/feature_state.dart';
import 'package:privacy_gui/providers/preservable.dart';

Expand Down Expand Up @@ -137,6 +137,7 @@ class DHCPReservationState
);
}

@override
String toJson() => json.encode(toMap());

factory DHCPReservationState.fromJson(String source) =>
Expand All @@ -148,15 +149,15 @@ class DHCPReservationState

class ReservedListItem extends Equatable {
final bool reserved;
final DHCPReservation data;
final DHCPReservationUIModel data;
const ReservedListItem({
required this.reserved,
required this.data,
});

ReservedListItem copyWith({
bool? reserved,
DHCPReservation? data,
DHCPReservationUIModel? data,
}) {
return ReservedListItem(
reserved: reserved ?? this.reserved,
Expand All @@ -174,7 +175,7 @@ class ReservedListItem extends Equatable {
factory ReservedListItem.fromMap(Map<String, dynamic> map) {
return ReservedListItem(
reserved: map['reserved'] ?? false,
data: DHCPReservation.fromMap(map['data']),
data: DHCPReservationUIModel.fromMap(map['data']),
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:privacy_gui/core/jnap/actions/better_action.dart';
import 'package:privacy_gui/core/jnap/models/lan_settings.dart';
import 'package:privacy_gui/core/jnap/models/set_lan_settings.dart';
import 'package:privacy_gui/core/jnap/providers/side_effect_provider.dart';
import 'package:privacy_gui/core/jnap/router_repository.dart';
import 'package:privacy_gui/core/utils/logger.dart';
import 'package:privacy_gui/page/advanced_settings/local_network_settings/models/dhcp_reservation_ui_model.dart';
import 'package:privacy_gui/page/advanced_settings/local_network_settings/providers/local_network_settings_state.dart';
import 'package:privacy_gui/page/advanced_settings/local_network_settings/services/local_network_settings_service.dart';
import 'package:privacy_gui/page/instant_safety/providers/instant_safety_provider.dart';
import 'package:privacy_gui/providers/preservable_contract.dart';
import 'package:privacy_gui/providers/preservable_notifier_mixin.dart';
Expand Down Expand Up @@ -55,52 +52,10 @@ class LocalNetworkSettingsNotifier extends Notifier<LocalNetworkSettingsState>
@override
Future<(LocalNetworkSettings?, LocalNetworkStatus?)> performFetch(
{bool forceRemote = false, bool updateStatusOnly = false}) async {
final repo = ref.read(routerRepositoryProvider);
final lanSettings = await repo
.send(
JNAPAction.getLANSettings,
fetchRemote: forceRemote,
auth: true,
)
.then((value) => RouterLANSettings.fromMap(value.output));

final subnetMaskString = NetworkUtils.prefixLengthToSubnetMask(
lanSettings.networkPrefixLength,
);
final maxUserAllowed = NetworkUtils.getMaxUserAllowedInDHCPRange(
lanSettings.ipAddress,
lanSettings.dhcpSettings.firstClientIPAddress,
lanSettings.dhcpSettings.lastClientIPAddress,
);
final maxUserLimit = NetworkUtils.getMaxUserLimit(
lanSettings.ipAddress,
lanSettings.dhcpSettings.firstClientIPAddress,
subnetMaskString,
maxUserAllowed,
);

final newSettings = LocalNetworkSettings(
hostName: lanSettings.hostName,
ipAddress: lanSettings.ipAddress,
subnetMask: subnetMaskString,
isDHCPEnabled: lanSettings.isDHCPEnabled,
firstIPAddress: lanSettings.dhcpSettings.firstClientIPAddress,
lastIPAddress: lanSettings.dhcpSettings.lastClientIPAddress,
maxUserAllowed: maxUserAllowed,
clientLeaseTime: lanSettings.dhcpSettings.leaseMinutes,
dns1: lanSettings.dhcpSettings.dnsServer1,
dns2: lanSettings.dhcpSettings.dnsServer2,
dns3: lanSettings.dhcpSettings.dnsServer3,
wins: lanSettings.dhcpSettings.winsServer,
);

final newStatus = state.status.copyWith(
maxUserLimit: maxUserLimit,
minNetworkPrefixLength: lanSettings.minNetworkPrefixLength,
maxNetworkPrefixLength: lanSettings.maxNetworkPrefixLength,
minAllowDHCPLeaseMinutes: lanSettings.minAllowedDHCPLeaseMinutes,
maxAllowDHCPLeaseMinutes: lanSettings.maxAllowedDHCPLeaseMinutes,
dhcpReservationList: lanSettings.dhcpSettings.reservations,
final service = ref.read(localNetworkSettingsServiceProvider);
final (newSettings, newStatus) = await service.fetchLANSettingsWithUIModels(
forceRemote: forceRemote,
currentStatus: state.status,
);

_updateValidators(state.copyWith(
Expand All @@ -112,65 +67,51 @@ class LocalNetworkSettingsNotifier extends Notifier<LocalNetworkSettingsState>

@override
Future<void> performSave() async {
final service = ref.read(localNetworkSettingsServiceProvider);
final settings = state.settings.current;
final newSettings = SetRouterLANSettings(
ipAddress: settings.ipAddress,

// Clear reservations if router IP changed
final reservations =
(state.settings.original.ipAddress != settings.ipAddress)
? <DHCPReservationUIModel>[]
: state.status.dhcpReservationList;

await service.saveReservations(
routerIp: settings.ipAddress,
networkPrefixLength:
NetworkUtils.subnetMaskToPrefixLength(settings.subnetMask),
hostName: settings.hostName,
isDHCPEnabled: settings.isDHCPEnabled,
dhcpSettings: DHCPSettings(
firstClientIPAddress: settings.firstIPAddress,
lastClientIPAddress: settings.lastIPAddress,
leaseMinutes: settings.clientLeaseTime,
dnsServer1: settings.dns1?.isEmpty == true ? null : settings.dns1,
dnsServer2: settings.dns2?.isEmpty == true ? null : settings.dns2,
dnsServer3: settings.dns3?.isEmpty == true ? null : settings.dns3,
winsServer: settings.wins?.isEmpty == true ? null : settings.wins,
reservations: (state.settings.original.ipAddress != settings.ipAddress)
? []
: state.status.dhcpReservationList,
),
);
final routerRepository = ref.read(routerRepositoryProvider);
await routerRepository.send(
JNAPAction.setLANSettings,
auth: true,
data: newSettings.toMap()..removeWhere((key, value) => value == null),
sideEffectOverrides: const JNAPSideEffectOverrides(maxRetry: 5),
firstClientIP: settings.firstIPAddress,
lastClientIP: settings.lastIPAddress,
leaseMinutes: settings.clientLeaseTime,
dns1: settings.dns1,
dns2: settings.dns2,
dns3: settings.dns3,
wins: settings.wins,
reservations: reservations,
);
await ref.read(instantSafetyProvider.notifier).fetch();
}

Future<void> saveReservations(List<DHCPReservation> list) async {
Future<void> saveReservations(List<DHCPReservationUIModel> list) async {
final service = ref.read(localNetworkSettingsServiceProvider);
final currentSettings = state.settings.current;
final newSettings = SetRouterLANSettings(
ipAddress: currentSettings.ipAddress,

await service.saveReservations(
routerIp: currentSettings.ipAddress,
networkPrefixLength:
NetworkUtils.subnetMaskToPrefixLength(currentSettings.subnetMask),
hostName: currentSettings.hostName,
isDHCPEnabled: currentSettings.isDHCPEnabled,
dhcpSettings: DHCPSettings(
firstClientIPAddress: currentSettings.firstIPAddress,
lastClientIPAddress: currentSettings.lastIPAddress,
leaseMinutes: currentSettings.clientLeaseTime,
dnsServer1:
currentSettings.dns1?.isEmpty == true ? null : currentSettings.dns1,
dnsServer2:
currentSettings.dns2?.isEmpty == true ? null : currentSettings.dns2,
dnsServer3:
currentSettings.dns3?.isEmpty == true ? null : currentSettings.dns3,
winsServer:
currentSettings.wins?.isEmpty == true ? null : currentSettings.wins,
reservations: list,
),
);
final routerRepository = ref.read(routerRepositoryProvider);
await routerRepository.send(
JNAPAction.setLANSettings,
auth: true,
data: newSettings.toMap()..removeWhere((key, value) => value == null),
sideEffectOverrides: const JNAPSideEffectOverrides(maxRetry: 5),
firstClientIP: currentSettings.firstIPAddress,
lastClientIP: currentSettings.lastIPAddress,
leaseMinutes: currentSettings.clientLeaseTime,
dns1: currentSettings.dns1,
dns2: currentSettings.dns2,
dns3: currentSettings.dns3,
wins: currentSettings.wins,
reservations: list,
);
// After saving, we need to refetch the local network settings to get the updated state.
await fetch(forceRemote: true);
Expand Down Expand Up @@ -246,19 +187,20 @@ class LocalNetworkSettingsNotifier extends Notifier<LocalNetworkSettingsState>
}

void updateDHCPReservationList(
List<DHCPReservation> addedDHCPReservationList) {
List<DHCPReservationUIModel> addedDHCPReservationList) {
final filteredList = addedDHCPReservationList
.where((element) => !isReservationOverlap(item: element));
final List<DHCPReservation> newList = [
final List<DHCPReservationUIModel> newList = [
...state.status.dhcpReservationList,
...filteredList
];
state = state.copyWith(
status: state.status.copyWith(dhcpReservationList: newList));
}

bool updateDHCPReservationOfIndex(DHCPReservation item, int index) {
List<DHCPReservation> newList = List.from(state.status.dhcpReservationList);
bool updateDHCPReservationOfIndex(DHCPReservationUIModel item, int index) {
List<DHCPReservationUIModel> newList =
List.from(state.status.dhcpReservationList);
bool succeed = false;
if (item.ipAddress == 'DELETE') {
newList.removeAt(index);
Expand All @@ -274,7 +216,8 @@ class LocalNetworkSettingsNotifier extends Notifier<LocalNetworkSettingsState>
return succeed;
}

bool isReservationOverlap({required DHCPReservation item, int? index}) {
bool isReservationOverlap(
{required DHCPReservationUIModel item, int? index}) {
final overlap = state.status.dhcpReservationList.where((element) {
// Not compare with self if on editing
if (index != null &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import 'package:collection/collection.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/widgets.dart';

import 'package:privacy_gui/core/jnap/models/lan_settings.dart';
import 'package:privacy_gui/localization/localization_hook.dart';
import 'package:privacy_gui/page/advanced_settings/local_network_settings/models/dhcp_reservation_ui_model.dart';
import 'package:privacy_gui/providers/feature_state.dart';
import 'package:privacy_gui/providers/preservable.dart';
import 'package:privacy_gui/utils.dart';
Expand Down Expand Up @@ -201,7 +201,7 @@ class LocalNetworkStatus extends Equatable {
final int maxAllowDHCPLeaseMinutes;
final int minNetworkPrefixLength;
final int maxNetworkPrefixLength;
final List<DHCPReservation> dhcpReservationList;
final List<DHCPReservationUIModel> dhcpReservationList;
final Map<String, String> errorTextMap;
final bool hasErrorOnHostNameTab;
final bool hasErrorOnIPAddressTab;
Expand Down Expand Up @@ -248,7 +248,7 @@ class LocalNetworkStatus extends Equatable {
int? maxAllowDHCPLeaseMinutes,
int? minNetworkPrefixLength,
int? maxNetworkPrefixLength,
List<DHCPReservation>? dhcpReservationList,
List<DHCPReservationUIModel>? dhcpReservationList,
Map<String, String>? errorTextMap,
bool? hasErrorOnHostNameTab,
bool? hasErrorOnIPAddressTab,
Expand Down Expand Up @@ -297,8 +297,9 @@ class LocalNetworkStatus extends Equatable {
maxAllowDHCPLeaseMinutes: map['maxAllowDHCPLeaseMinutes']?.toInt() ?? 0,
minNetworkPrefixLength: map['minNetworkPrefixLength']?.toInt() ?? 0,
maxNetworkPrefixLength: map['maxNetworkPrefixLength']?.toInt() ?? 0,
dhcpReservationList: List<DHCPReservation>.from(
map['dhcpReservationList']?.map((x) => DHCPReservation.fromMap(x))),
dhcpReservationList: List<DHCPReservationUIModel>.from(
map['dhcpReservationList']
?.map((x) => DHCPReservationUIModel.fromMap(x))),
errorTextMap: Map<String, String>.from(map['errorTextMap'] ?? {}),
hasErrorOnHostNameTab: map['hasErrorOnHostNameTab'] ?? false,
hasErrorOnIPAddressTab: map['hasErrorOnIPAddressTab'] ?? false,
Expand Down
Loading