Support AGH without DHCP server

This commit is contained in:
Juan Gilsanz Polo 2023-12-08 20:43:45 +01:00
parent 2511ac2c24
commit 83ea589187
9 changed files with 291 additions and 274 deletions

View file

@ -688,5 +688,7 @@
"yourVersion": "Your version: {version}", "yourVersion": "Your version: {version}",
"minimumRequiredVersion": "Minimum required version: {version}", "minimumRequiredVersion": "Minimum required version: {version}",
"topUpstreams": "Top upstreams", "topUpstreams": "Top upstreams",
"averageUpstreamResponseTime": "Average upstream response time" "averageUpstreamResponseTime": "Average upstream response time",
"dhcpNotAvailable": "The DHCP server is not available.",
"osServerInstalledIncompatible": "The OS where the server is installed is not compatible with this feature."
} }

View file

@ -688,5 +688,7 @@
"yourVersion": "Tu versión: {version}", "yourVersion": "Tu versión: {version}",
"minimumRequiredVersion": "Versión mínima requerida: {version}", "minimumRequiredVersion": "Versión mínima requerida: {version}",
"topUpstreams": "DNS de subida más frecuentes", "topUpstreams": "DNS de subida más frecuentes",
"averageUpstreamResponseTime": "Tiempo promedio de respuesta upstream" "averageUpstreamResponseTime": "Tiempo promedio de respuesta upstream",
"dhcpNotAvailable": "El servidor DHCP no está disponible.",
"osServerInstalledIncompatible": "El SO donde el servidor está instalado no es compatible con esta característica."
} }

View file

@ -1,9 +1,11 @@
import 'dart:convert'; import 'dart:convert';
class DhcpModel { class DhcpModel {
bool dhcpAvailable;
List<NetworkInterface> networkInterfaces; List<NetworkInterface> networkInterfaces;
DhcpStatus dhcpStatus; DhcpStatus? dhcpStatus;
DhcpModel({ DhcpModel({
required this.dhcpAvailable,
required this.networkInterfaces, required this.networkInterfaces,
required this.dhcpStatus, required this.dhcpStatus,
}); });

View file

@ -69,7 +69,7 @@ class DhcpProvider with ChangeNotifier {
if (result.successful == true) { if (result.successful == true) {
DhcpModel data = dhcp!; DhcpModel data = dhcp!;
data.dhcpStatus.staticLeases = data.dhcpStatus.staticLeases.where((l) => l.mac != lease.mac).toList(); data.dhcpStatus!.staticLeases = data.dhcpStatus!.staticLeases.where((l) => l.mac != lease.mac).toList();
setDhcpData(data); setDhcpData(data);
return true; return true;
} }
@ -90,7 +90,7 @@ class DhcpProvider with ChangeNotifier {
if (result.successful == true) { if (result.successful == true) {
DhcpModel data = dhcp!; DhcpModel data = dhcp!;
data.dhcpStatus.staticLeases.add(lease); data.dhcpStatus!.staticLeases.add(lease);
setDhcpData(data); setDhcpData(data);
return result; return result;
} }

View file

@ -7,8 +7,10 @@ import 'package:flutter_split_view/flutter_split_view.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/widgets/section_label.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_not_available.dart';
import 'package:adguard_home_manager/widgets/confirm_action_modal.dart'; import 'package:adguard_home_manager/widgets/confirm_action_modal.dart';
import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_main_button.dart';
import 'package:adguard_home_manager/widgets/section_label.dart';
import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_leases.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/dhcp_leases.dart';
import 'package:adguard_home_manager/screens/settings/dhcp/select_interface_modal.dart'; import 'package:adguard_home_manager/screens/settings/dhcp/select_interface_modal.dart';
@ -22,7 +24,7 @@ import 'package:adguard_home_manager/providers/app_config_provider.dart';
import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart';
class DhcpScreen extends StatefulWidget { class DhcpScreen extends StatefulWidget {
const DhcpScreen({Key? key}) : super(key: key); const DhcpScreen({super.key});
@override @override
State<DhcpScreen> createState() => _DhcpScreenState(); State<DhcpScreen> createState() => _DhcpScreenState();
@ -55,24 +57,25 @@ class _DhcpScreenState extends State<DhcpScreen> {
void loadDhcpStatus() async { void loadDhcpStatus() async {
final result = await Provider.of<DhcpProvider>(context, listen: false).loadDhcpStatus(); final result = await Provider.of<DhcpProvider>(context, listen: false).loadDhcpStatus();
if (mounted && result == true) { if (!mounted || result == false) return;
final dhcpProvider = Provider.of<DhcpProvider>(context, listen: false);
if (dhcpProvider.dhcp != null) { final dhcpProvider = Provider.of<DhcpProvider>(context, listen: false);
setState(() { if (dhcpProvider.dhcp == null) return;
if (dhcpProvider.dhcp!.dhcpStatus.interfaceName != null && dhcpProvider.dhcp!.dhcpStatus.interfaceName != '') {
try {selectedInterface = dhcpProvider.dhcp!.networkInterfaces.firstWhere((iface) => iface.name == dhcpProvider.dhcp!.dhcpStatus.interfaceName);} catch (_) {} setState(() {
enabled = dhcpProvider.dhcp!.dhcpStatus.enabled; if (dhcpProvider.dhcp!.dhcpStatus!.interfaceName != null && dhcpProvider.dhcp!.dhcpStatus!.interfaceName != '') {
if (dhcpProvider.dhcp!.dhcpStatus.v4 != null) { try {selectedInterface = dhcpProvider.dhcp!.networkInterfaces.firstWhere((iface) => iface.name == dhcpProvider.dhcp!.dhcpStatus!.interfaceName);} catch (_) {}
ipv4StartRangeController.text = dhcpProvider.dhcp!.dhcpStatus.v4!.rangeStart; enabled = dhcpProvider.dhcp!.dhcpStatus!.enabled;
ipv4EndRangeController.text = dhcpProvider.dhcp!.dhcpStatus.v4!.rangeEnd ?? ''; if (dhcpProvider.dhcp!.dhcpStatus!.v4 != null) {
ipv4SubnetMaskController.text = dhcpProvider.dhcp!.dhcpStatus.v4!.subnetMask ?? ''; ipv4StartRangeController.text = dhcpProvider.dhcp!.dhcpStatus!.v4!.rangeStart;
ipv4GatewayController.text = dhcpProvider.dhcp!.dhcpStatus.v4!.gatewayIp ?? ''; ipv4EndRangeController.text = dhcpProvider.dhcp!.dhcpStatus!.v4!.rangeEnd ?? '';
ipv4LeaseTimeController.text = dhcpProvider.dhcp!.dhcpStatus.v4!.leaseDuration.toString(); ipv4SubnetMaskController.text = dhcpProvider.dhcp!.dhcpStatus!.v4!.subnetMask ?? '';
} ipv4GatewayController.text = dhcpProvider.dhcp!.dhcpStatus!.v4!.gatewayIp ?? '';
} ipv4LeaseTimeController.text = dhcpProvider.dhcp!.dhcpStatus!.v4!.leaseDuration.toString();
}); }
} }
} });
checkDataValid(); checkDataValid();
} }
@ -266,8 +269,8 @@ class _DhcpScreenState extends State<DhcpScreen> {
if (result.successful == true) { if (result.successful == true) {
DhcpModel data = dhcpProvider.dhcp!; DhcpModel data = dhcpProvider.dhcp!;
data.dhcpStatus.staticLeases = []; data.dhcpStatus!.staticLeases = [];
data.dhcpStatus.leases = []; data.dhcpStatus!.leases = [];
dhcpProvider.setDhcpData(data); dhcpProvider.setDhcpData(data);
showSnacbkar( showSnacbkar(
@ -350,6 +353,14 @@ class _DhcpScreenState extends State<DhcpScreen> {
}); });
} }
if (
dhcpProvider.loadStatus == LoadStatus.loaded &&
dhcpProvider.dhcp != null &&
dhcpProvider.dhcp!.dhcpAvailable == false
) {
return const DhcpNotAvailable();
}
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(AppLocalizations.of(context)!.dhcpSettings), title: Text(AppLocalizations.of(context)!.dhcpSettings),
@ -429,60 +440,10 @@ class _DhcpScreenState extends State<DhcpScreen> {
return SingleChildScrollView( return SingleChildScrollView(
child: Wrap( child: Wrap(
children: [ children: [
Padding( DhcpMainButton(
padding: const EdgeInsets.only( selectedInterface: selectedInterface,
top: 10, enabled: enabled,
left: 16, setEnabled: (v) => setState(() => enabled = v)
right: 16
),
child: Material(
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(28),
child: InkWell(
onTap: selectedInterface != null
? () => setState(() => enabled = !enabled)
: null,
borderRadius: BorderRadius.circular(28),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 12
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppLocalizations.of(context)!.enableDhcpServer,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface
),
),
if (selectedInterface != null) ...[
Text(
selectedInterface!.name,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).listTileTheme.textColor
),
)
]
],
),
Switch(
value: enabled,
onChanged: selectedInterface != null
? (value) => setState(() => enabled = value)
: null,
),
],
),
),
),
),
), ),
if (selectedInterface!.ipv4Addresses.isNotEmpty) ...[ if (selectedInterface!.ipv4Addresses.isNotEmpty) ...[
SectionLabel( SectionLabel(
@ -491,125 +452,47 @@ class _DhcpScreenState extends State<DhcpScreen> {
top: 24, left: 16, right: 16, bottom: 8 top: 24, left: 16, right: 16, bottom: 8
) )
), ),
FractionallySizedBox( _DhcpField(
widthFactor: width > 900 ? 0.5 : 1, icon: Icons.skip_previous_rounded,
child: Padding( label: AppLocalizations.of(context)!.startOfRange,
padding: width > 900 controller: ipv4StartRangeController,
? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8) onChanged: (value) => validateIpV4(value, 'ipv4StartRangeError', AppLocalizations.of(context)!.ipNotValid),
: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), error: ipv4StartRangeError
child: TextFormField(
controller: ipv4StartRangeController,
onChanged: (value) => validateIpV4(value, 'ipv4StartRangeError', AppLocalizations.of(context)!.ipNotValid),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.skip_previous_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv4StartRangeError,
labelText: AppLocalizations.of(context)!.startOfRange,
),
keyboardType: TextInputType.number,
),
),
), ),
FractionallySizedBox( _DhcpField(
widthFactor: width > 900 ? 0.5 : 1, icon: Icons.skip_next_rounded,
child: Padding( label: AppLocalizations.of(context)!.endOfRange,
padding: width > 900 controller: ipv4EndRangeController,
? const EdgeInsets.only(top: 12, bottom: 12, left: 8, right: 16) onChanged: (value) => validateIpV4(value, 'ipv4EndRangeError', AppLocalizations.of(context)!.ipNotValid),
: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), error: ipv4EndRangeError
child: TextFormField(
controller: ipv4EndRangeController,
onChanged: (value) => validateIpV4(value, 'ipv4EndRangeError', AppLocalizations.of(context)!.ipNotValid),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.skip_next_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv4EndRangeError,
labelText: AppLocalizations.of(context)!.endOfRange,
),
keyboardType: TextInputType.number,
),
),
), ),
FractionallySizedBox( _DhcpField(
widthFactor: width > 900 ? 0.5 : 1, icon: Icons.hub_rounded,
child: Padding( label: AppLocalizations.of(context)!.subnetMask,
padding: width > 900 controller: ipv4SubnetMaskController,
? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8) onChanged: (value) => validateIpV4(value, 'ipv4SubnetMaskError', AppLocalizations.of(context)!.subnetMaskNotValid),
: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), error: ipv4SubnetMaskError
child: TextFormField(
controller: ipv4SubnetMaskController,
onChanged: (value) => validateIpV4(value, 'ipv4SubnetMaskError', AppLocalizations.of(context)!.subnetMaskNotValid),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.hub_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv4SubnetMaskError,
labelText: AppLocalizations.of(context)!.subnetMask,
),
keyboardType: TextInputType.number,
),
),
), ),
FractionallySizedBox( _DhcpField(
widthFactor: width > 900 ? 0.5 : 1, icon: Icons.router_rounded,
child: Padding( label: AppLocalizations.of(context)!.gateway,
padding: width > 900 controller: ipv4GatewayController,
? const EdgeInsets.only(top: 12, bottom: 12, left: 8, right: 16) onChanged: (value) => validateIpV4(value, 'ipv4GatewayError', AppLocalizations.of(context)!.gatewayNotValid),
: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), error: ipv4GatewayError
child: TextFormField(
controller: ipv4GatewayController,
onChanged: (value) => validateIpV4(value, 'ipv4GatewayError', AppLocalizations.of(context)!.gatewayNotValid),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.router_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv4GatewayError,
labelText: AppLocalizations.of(context)!.gateway,
),
keyboardType: TextInputType.number,
),
),
), ),
FractionallySizedBox( _DhcpField(
widthFactor: 1, icon: Icons.timer,
child: Padding( label: AppLocalizations.of(context)!.leaseTime,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), controller: ipv4LeaseTimeController,
child: TextFormField( onChanged: (value) {
controller: ipv4LeaseTimeController, if (int.tryParse(value).runtimeType == int) {
onChanged: (value) { setState(() => ipv4LeaseTimeError = null);
if (int.tryParse(value).runtimeType == int) { }
setState(() => ipv4LeaseTimeError = null); else {
} setState(() => ipv4LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid);
else { }
setState(() => ipv4LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid); },
} error: ipv4LeaseTimeError
},
decoration: InputDecoration(
prefixIcon: const Icon(Icons.timer),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv4LeaseTimeError,
labelText: AppLocalizations.of(context)!.leaseTime,
),
keyboardType: TextInputType.number,
),
),
), ),
], ],
if (selectedInterface!.ipv6Addresses.isNotEmpty) ...[ if (selectedInterface!.ipv6Addresses.isNotEmpty) ...[
@ -617,80 +500,34 @@ class _DhcpScreenState extends State<DhcpScreen> {
label: AppLocalizations.of(context)!.ipv6settings, label: AppLocalizations.of(context)!.ipv6settings,
padding: const EdgeInsets.all(16) padding: const EdgeInsets.all(16)
), ),
FractionallySizedBox( _DhcpField(
widthFactor: width > 900 ? 0.5 : 1, icon: Icons.skip_next_rounded,
child: Padding( label: AppLocalizations.of(context)!.startOfRange,
padding: width > 900 controller: ipv6StartRangeController,
? const EdgeInsets.only(top: 8, bottom: 12, left: 16, right: 8) onChanged: (value) => validateIpV6(value, 'ipv6StartRangeError', AppLocalizations.of(context)!.ipNotValid),
: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), error: ipv6StartRangeError
child: TextFormField(
controller: ipv6StartRangeController,
onChanged: (value) => validateIpV4(value, 'ipv6StartRangeError', AppLocalizations.of(context)!.ipNotValid),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.skip_next_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv6StartRangeError,
labelText: AppLocalizations.of(context)!.startOfRange,
),
keyboardType: TextInputType.number,
),
),
), ),
FractionallySizedBox( _DhcpField(
widthFactor: width > 900 ? 0.5 : 1, icon: Icons.skip_previous_rounded,
child: Padding( label: AppLocalizations.of(context)!.endOfRange,
padding: width > 900 controller: ipv6EndRangeController,
? const EdgeInsets.only(top: 8, bottom: 12, left: 8, right: 16) onChanged: (value) => validateIpV6(value, 'ipv6EndRangeError', AppLocalizations.of(context)!.ipNotValid),
: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), error: ipv6EndRangeError
child: TextFormField(
controller: ipv6EndRangeController,
onChanged: (value) => validateIpV4(value, 'ipv6EndRangeError', AppLocalizations.of(context)!.ipNotValid),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.skip_previous_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv6EndRangeError,
labelText: AppLocalizations.of(context)!.endOfRange,
),
keyboardType: TextInputType.number,
),
),
),
FractionallySizedBox(
widthFactor: 1,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: TextFormField(
controller: ipv6LeaseTimeController,
onChanged: (value) {
if (int.tryParse(value).runtimeType == int) {
setState(() => ipv6LeaseTimeError = null);
}
else {
setState(() => ipv6LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid);
}
},
decoration: InputDecoration(
prefixIcon: const Icon(Icons.timer),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv6LeaseTimeError,
labelText: AppLocalizations.of(context)!.leaseTime,
),
keyboardType: TextInputType.number,
),
),
), ),
_DhcpField(
icon: Icons.timer,
label: AppLocalizations.of(context)!.leaseTime,
controller: ipv6LeaseTimeController,
onChanged: (value) {
if (int.tryParse(value).runtimeType == int) {
setState(() => ipv6LeaseTimeError = null);
}
else {
setState(() => ipv6LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid);
}
},
error: ipv6LeaseTimeError
)
], ],
const SizedBox(height: 20), const SizedBox(height: 20),
SectionLabel( SectionLabel(
@ -704,7 +541,7 @@ class _DhcpScreenState extends State<DhcpScreen> {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => DhcpLeases( builder: (context) => DhcpLeases(
items: dhcpProvider.dhcp!.dhcpStatus.leases, items: dhcpProvider.dhcp!.dhcpStatus!.leases,
staticLeases: false, staticLeases: false,
) )
) )
@ -739,7 +576,7 @@ class _DhcpScreenState extends State<DhcpScreen> {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => DhcpLeases( builder: (context) => DhcpLeases(
items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases,
staticLeases: true, staticLeases: true,
) )
) )
@ -775,7 +612,7 @@ class _DhcpScreenState extends State<DhcpScreen> {
if (!(Platform.isAndroid || Platform.isIOS)) { if (!(Platform.isAndroid || Platform.isIOS)) {
SplitView.of(context).push( SplitView.of(context).push(
DhcpLeases( DhcpLeases(
items: dhcpProvider.dhcp!.dhcpStatus.leases, items: dhcpProvider.dhcp!.dhcpStatus!.leases,
staticLeases: false, staticLeases: false,
) )
); );
@ -784,7 +621,7 @@ class _DhcpScreenState extends State<DhcpScreen> {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => DhcpLeases( builder: (context) => DhcpLeases(
items: dhcpProvider.dhcp!.dhcpStatus.leases, items: dhcpProvider.dhcp!.dhcpStatus!.leases,
staticLeases: false, staticLeases: false,
) )
) )
@ -804,7 +641,7 @@ class _DhcpScreenState extends State<DhcpScreen> {
if (!(Platform.isAndroid || Platform.isIOS)) { if (!(Platform.isAndroid || Platform.isIOS)) {
SplitView.of(context).push( SplitView.of(context).push(
DhcpLeases( DhcpLeases(
items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases,
staticLeases: true, staticLeases: true,
) )
); );
@ -813,7 +650,7 @@ class _DhcpScreenState extends State<DhcpScreen> {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => DhcpLeases( builder: (context) => DhcpLeases(
items: dhcpProvider.dhcp!.dhcpStatus.staticLeases, items: dhcpProvider.dhcp!.dhcpStatus!.staticLeases,
staticLeases: true, staticLeases: true,
) )
) )
@ -900,3 +737,48 @@ class _DhcpScreenState extends State<DhcpScreen> {
); );
} }
} }
class _DhcpField extends StatelessWidget {
final IconData icon;
final String label;
final TextEditingController controller;
final void Function(String) onChanged;
final String? error;
const _DhcpField({
required this.icon,
required this.label,
required this.controller,
required this.onChanged,
required this.error,
});
@override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return FractionallySizedBox(
widthFactor: width > 900 ? 0.5 : 1,
child: Padding(
padding: width > 900
? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 8)
: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: TextFormField(
controller: controller,
onChanged: onChanged,
decoration: InputDecoration(
prefixIcon: Icon(icon),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: error,
labelText: label,
),
keyboardType: TextInputType.number,
),
),
);
}
}

View file

@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/models/dhcp.dart';
class DhcpMainButton extends StatelessWidget {
final NetworkInterface? selectedInterface;
final bool enabled;
final void Function(bool) setEnabled;
const DhcpMainButton({
super.key,
required this.selectedInterface,
required this.enabled,
required this.setEnabled,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(
top: 10,
left: 16,
right: 16
),
child: Material(
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(28),
child: InkWell(
onTap: selectedInterface != null
? () => setEnabled(!enabled)
: null,
borderRadius: BorderRadius.circular(28),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 12
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppLocalizations.of(context)!.enableDhcpServer,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface
),
),
if (selectedInterface != null) ...[
Text(
selectedInterface!.name,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).listTileTheme.textColor
),
)
]
],
),
Switch(
value: enabled,
onChanged: selectedInterface != null
? (value) => setEnabled(value)
: null,
),
],
),
),
),
),
);
}
}

View file

@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/functions/desktop_mode.dart';
class DhcpNotAvailable extends StatelessWidget {
const DhcpNotAvailable({super.key});
@override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.dhcpSettings),
centerTitle: false,
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
AppLocalizations.of(context)!.dhcpNotAvailable,
textAlign: TextAlign.center,
style: const TextStyle(
fontWeight: FontWeight.w400,
fontSize: 24
),
),
const SizedBox(height: 20),
Text(
AppLocalizations.of(context)!.osServerInstalledIncompatible,
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant
),
),
],
),
),
);
}
}

View file

@ -482,8 +482,13 @@ class ApiClientV2 {
return ApiResponse( return ApiResponse(
successful: true, successful: true,
content: DhcpModel( content: DhcpModel(
dhcpAvailable: jsonDecode(results[1].body!)['message'] != null
? false
: true,
networkInterfaces: interfaces, networkInterfaces: interfaces,
dhcpStatus: DhcpStatus.fromJson(jsonDecode(results[1].body!)) dhcpStatus: jsonDecode(results[1].body!)['message'] != null
? null
: DhcpStatus.fromJson(jsonDecode(results[1].body!))
) )
); );
} catch (e, stackTrace) { } catch (e, stackTrace) {

View file

@ -1352,6 +1352,7 @@ class ApiClient {
return { return {
'result': 'success', 'result': 'success',
'data': DhcpModel( 'data': DhcpModel(
dhcpAvailable: true,
networkInterfaces: interfaces, networkInterfaces: interfaces,
dhcpStatus: DhcpStatus.fromJson(jsonDecode(result[1]['body'])) dhcpStatus: DhcpStatus.fromJson(jsonDecode(result[1]['body']))
) )