Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
9316e31
fix: resolve all flutter analyze warnings and regenerate mocks
AustinChangLinksys Jan 5, 2026
5fb98fe
refactor: Move agent env config and fix lints
AustinChangLinksys Jan 6, 2026
a956be5
fix: Resolve mock signature warnings and fix node detail test
AustinChangLinksys Jan 6, 2026
818b963
style: Format code with dart format
AustinChangLinksys Jan 6, 2026
4b2e4bf
Merge branch 'dev-2.0.0' into austin/fix-analyze-warnings
AustinChangLinksys Jan 6, 2026
4b2f588
chore: Restore polling_provider and cleanup obsolete mocks
AustinChangLinksys Jan 6, 2026
15ad9af
feat(ui): UI improvements - dialog scroll, input fixes, WiFi card ali…
AustinChangLinksys Jan 7, 2026
6d2a0c2
refactor(dashboard): unify layout logic and extract components
AustinChangLinksys Jan 7, 2026
34cf4b7
refactor(instant_verify): use shared PortStatusWidget
AustinChangLinksys Jan 7, 2026
cfe28ea
refactor(port_and_speed): extract speed test components
AustinChangLinksys Jan 7, 2026
119b8cc
fix(dashboard): align WiFi grid horizontal spacing with layout gutter
AustinChangLinksys Jan 7, 2026
7053cbd
fix(dashboard): resolve multiple render overflow issues in mobile/nar…
AustinChangLinksys Jan 7, 2026
32fc9a9
fix(dashboard): resolve mobile layout crash and optimize spacing
AustinChangLinksys Jan 7, 2026
1de2bc4
fix(dashboard): resolve layout crash by removing Flexible wrapper in …
AustinChangLinksys Jan 7, 2026
928e8a4
feat(dashboard): implement optimized tablet layout
AustinChangLinksys Jan 7, 2026
3157550
fix(dashboard): resolve persistent desktop crash using IntrinsicHeight
AustinChangLinksys Jan 7, 2026
ae6a642
fix(dashboard): optimize Tablet wifi grid and fix height calc overflow
AustinChangLinksys Jan 7, 2026
29dbb47
fix(dashboard): adjust networks list height limit
AustinChangLinksys Jan 7, 2026
cc6b723
refactor(dashboard): centralize layout variant logic
AustinChangLinksys Jan 7, 2026
77895da
chore(dashboard): remove unused DashboardLayoutVariantX extension
AustinChangLinksys Jan 7, 2026
83c1c5e
refactor(dashboard): implement Strategy pattern + IoC for layout
AustinChangLinksys Jan 7, 2026
02e020a
feat(dashboard): Add layout constraint system for customizable dashbo…
AustinChangLinksys Jan 7, 2026
6f81fed
feat(dashboard): Add DisplayMode support to all components and settin…
AustinChangLinksys Jan 7, 2026
cd78309
feat(dashboard): Integrate preferences into DashboardHomeView
AustinChangLinksys Jan 7, 2026
6c66bcb
feat(dashboard): Add Dashboard Layout settings to menu
AustinChangLinksys Jan 7, 2026
4864d2d
fix(ui): resolve phase 2 and 3 issues including topology clipping, de…
AustinChangLinksys Jan 8, 2026
681ff93
fix(ui): resolve renderflex overflow in instant verify and upgrade ui…
AustinChangLinksys Jan 8, 2026
72ee684
Merge branch 'dev-2.0.0' into austin/dashboard-layout-constraints
AustinChangLinksys Jan 8, 2026
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
3 changes: 2 additions & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ void main() async {
// TODO Revisit again until Flutter SDK 3.27.x
// https://github.com/flutter/engine/commit/35af5fe80e0212caff4b34b583232d833b5a2596
//
if (defaultTargetPlatform != TargetPlatform.iOS &&
if (!kDebugMode &&
defaultTargetPlatform != TargetPlatform.iOS &&
defaultTargetPlatform != TargetPlatform.android) {
SemanticsBinding.instance.ensureSemantics();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,27 @@ Future<String?> showSelectProtocolModal(
String selected = value;
return showSimpleAppDialog<String?>(context,
title: loc(context).channel,
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AppRadioList<String>(
selected: value,
itemHeight: 56,
items: ['TCP', 'UDP', 'Both']
.map((e) => AppRadioListItem(
title: getProtocolTitle(context, e),
value: e,
))
.toList(),
onChanged: (index, selectedType) {
if (selectedType != null) {
selected = selectedType;
}
},
),
],
),
scrollable: true,
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AppRadioList<String>(
selected: value,
itemHeight: 56,
items: ['TCP', 'UDP', 'Both']
.map((e) => AppRadioListItem(
title: getProtocolTitle(context, e),
value: e,
))
.toList(),
onChanged: (index, selectedType) {
if (selectedType != null) {
selected = selectedType;
}
},
),
],
),
actions: [
AppButton.text(
Expand Down
37 changes: 15 additions & 22 deletions lib/page/advanced_settings/dmz/views/dmz_settings_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -261,29 +261,22 @@ class _DMZSettingsViewState extends ConsumerState<DMZSettingsView> {
DMZDestinationType.ip
? Container(
constraints: const BoxConstraints(maxWidth: 429),
child: Focus(
onFocusChange: (value) {
if (!value) {
_checkDestinationIPAdress();
}
},
child: AppIpv4TextField(
key: const Key('destinationIP'),
controller: _destinationIPController,
readOnly: SegmentReadOnly(
segment1: subnetMask[0] == '255',
segment2: subnetMask[1] == '255',
segment3: subnetMask[2] == '255',
),
onChanged: (value) {
ref
.read(dmzSettingsProvider.notifier)
.setSettings(state.settings.current
.copyWith(
destinationIPAddress: () => value));
},
errorText: _destinationError,
child: AppIpv4TextField(
key: const Key('destinationIP'),
controller: _destinationIPController,
readOnly: SegmentReadOnly(
segment0: subnetMask[0] == '255',
segment1: subnetMask[1] == '255',
segment2: subnetMask[2] == '255',
),
onChanged: (value) {
ref
.read(dmzSettingsProvider.notifier)
.setSettings(state.settings.current.copyWith(
destinationIPAddress: () => value));
_checkDestinationIPAdress();
},
errorText: _destinationError,
),
)
: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ class _Ipv6PortServiceListViewState

// Clear editing state
_editingRule = null;
_sheetStateSetter = null; // Fix: Add button state error
_clearControllers();
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,20 +209,39 @@ class _OptionalSettingsFormState extends ConsumerState<OptionalSettingsForm> {
child: Focus(
onFocusChange: (hasFocus) {
if (!hasFocus) {
final max = _getMaxMtu(ipv4Setting.ipv4ConnectionType);
final min = _getMinMtu(ipv4Setting.ipv4ConnectionType);
if (_mtuSizeController.text.isEmpty ||
(int.parse(_mtuSizeController.text) < min)) {
_mtuSizeController.text = min.toString();
_mtuSizeController.selection = TextSelection.fromPosition(
TextPosition(offset: _mtuSizeController.text.length),
);
notifier.updateMtu(min);
return;
} else if (_mtuSizeController.text.isNotEmpty &&
(int.parse(_mtuSizeController.text) > max)) {
_mtuSizeController.text = max.toString();
_mtuSizeController.selection = TextSelection.fromPosition(
TextPosition(offset: _mtuSizeController.text.length),
);
notifier.updateMtu(max);
}
setState(() => _mtuSizeTouched = true);
}
},
child: AppMinMaxInput(
key: ValueKey('mtuManualSizeText_$isMtuAuto'),
value: int.tryParse(_mtuSizeController.text),
controller: _mtuSizeController,
enabled: !isMtuAuto,
label: loc(context).size,
min: 576,
min: _getMinMtu(ipv4Setting.ipv4ConnectionType),
max: _getMaxMtu(ipv4Setting.ipv4ConnectionType),
errorText: _mtuSizeTouched &&
!isMtuAuto &&
_isMtuInvalid(
_mtuSizeController.text,
_getMinMtu(ipv4Setting.ipv4ConnectionType),
_getMaxMtu(ipv4Setting.ipv4ConnectionType),
)
? loc(context).invalidInput
Expand All @@ -233,7 +252,6 @@ class _OptionalSettingsFormState extends ConsumerState<OptionalSettingsForm> {
_mtuSizeTouched = true;
});
}
_mtuSizeController.text = value?.toString() ?? '';
notifier.updateMtu(value ?? 0);
},
),
Expand Down Expand Up @@ -399,9 +417,13 @@ class _OptionalSettingsFormState extends ConsumerState<OptionalSettingsForm> {
return NetworkUtils.getMaxMtu(wanType);
}

bool _isMtuInvalid(String text, int max) {
int _getMinMtu(String wanType) {
return NetworkUtils.getMinMtu(wanType);
}

bool _isMtuInvalid(String text, int min, int max) {
final value = int.tryParse(text);
if (value == null) return true;
return value < 576 || value > max;
return value < min || value > max;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ class _BridgeFormState extends BaseWanFormState<BridgeForm> {
children: [
buildDisplayFields(context), // Display same info as in display mode
AppGap.md(),
AppGap.md(),
AppStyledText(
text: '<b>${loc(context).toLogInLocallyWhileInBridgeMode}</b>',
text: loc(context).toLogInLocallyWhileInBridgeMode,
key: const ValueKey('toLogInLocallyWhileInBridgeMode'),
),
AppGap.sm(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,25 +162,33 @@ class _AutomaticIPv6FormState extends BaseIPv6WanFormState<AutomaticIPv6Form> {
children: [
Padding(
padding: inputPadding,
child: AppDropdown<IPv6rdTunnelMode>(
key: const ValueKey('ipv6TunnelDropdown'),
label: loc(context).sixrdTunnel,
value: ipv6Setting.ipv6rdTunnelMode ?? IPv6rdTunnelMode.disabled,
items: const [
IPv6rdTunnelMode.disabled,
IPv6rdTunnelMode.automatic,
IPv6rdTunnelMode.manual,
],
itemAsString: (item) {
return getIpv6rdTunnelModeLoc(context, item);
},
onChanged: widget.isEditing && !ipv6Setting.isIPv6AutomaticEnabled
? (value) {
if (value == null) return;
notifier.updateIpv6Settings(
ipv6Setting.copyWith(ipv6rdTunnelMode: () => value));
}
: null,
child: Opacity(
opacity: widget.isEditing && !ipv6Setting.isIPv6AutomaticEnabled
? 1.0
: 0.5,
child: IgnorePointer(
ignoring:
!(widget.isEditing && !ipv6Setting.isIPv6AutomaticEnabled),
child: AppDropdown<IPv6rdTunnelMode>(
key: const ValueKey('ipv6TunnelDropdown'),
label: loc(context).sixrdTunnel,
value:
ipv6Setting.ipv6rdTunnelMode ?? IPv6rdTunnelMode.disabled,
items: const [
IPv6rdTunnelMode.disabled,
IPv6rdTunnelMode.automatic,
IPv6rdTunnelMode.manual,
],
itemAsString: (item) {
return getIpv6rdTunnelModeLoc(context, item);
},
onChanged: (value) {
if (value == null) return;
notifier.updateIpv6Settings(
ipv6Setting.copyWith(ipv6rdTunnelMode: () => value));
},
),
),
),
),
_manualSixrdTunnel(ipv6Setting, context),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ class _PppoeFormState extends BaseWanFormState<PppoeForm> {
// Fix: Compare against current controller text to avoid cursor reset
// Only update controller if the new value is actually different from what's currently in the input
if ((newIpv4Setting.username ?? '') != _usernameController.text) {
debugPrint(
'PppoeForm: Syncing Username. State=${newIpv4Setting.username}, Ctrl=${_usernameController.text}');
_usernameController.text = newIpv4Setting.username ?? '';
}
if ((newIpv4Setting.password ?? '') != _passwordController.text) {
Expand All @@ -105,6 +107,8 @@ class _PppoeFormState extends BaseWanFormState<PppoeForm> {
final newVlanStr =
newIpv4Setting.vlanId != null ? '${newIpv4Setting.vlanId}' : '';
if (newVlanStr != _vlanIdController.text) {
debugPrint(
'PppoeForm: Syncing VlanId. State=$newVlanStr, Ctrl=${_vlanIdController.text}');
_vlanIdController.text = newVlanStr;
}

Expand Down Expand Up @@ -187,12 +191,12 @@ class _PppoeFormState extends BaseWanFormState<PppoeForm> {
padding: inputPadding,
child: AppMinMaxInput(
key: const ValueKey('pppoeVlanId'),
controller: _vlanIdController,
min: 5,
max: 4094,
label: loc(context).vlanIdOptional,
value: int.tryParse(_vlanIdController.text),
onChanged: (value) {
_vlanIdController.text = value?.toString() ?? '';
// controller is updated by AppMinMaxInput internally
notifier.updateIpv4Settings(ipv4Setting.copyWith(
vlanId: () => value,
));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,9 @@ class _DHCPReservationsContentViewState
onTap: () {
showSimpleAppOkDialog(
context,
content: SingleChildScrollView(
child: DevicesFilterWidget(
onlineOnly: true,
),
scrollable: true,
content: DevicesFilterWidget(
onlineOnly: true,
),
);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,32 @@ class _DHCPServerViewState extends ConsumerState<DHCPServerView> {
}

void _updateControllers(LocalNetworkSettingsState state) {
_startIpAddressController.text = state.settings.current.firstIPAddress;
_maxUserAllowedController.text = '${state.settings.current.maxUserAllowed}';
_clientLeaseTimeController.text =
'${state.settings.current.clientLeaseTime}';
_dns1Controller.text = state.settings.current.dns1 ?? '';
_dns2Controller.text = state.settings.current.dns2 ?? '';
_dns3Controller.text = state.settings.current.dns3 ?? '';
_winsController.text = state.settings.current.wins ?? '';
if (_startIpAddressController.text !=
state.settings.current.firstIPAddress) {
_startIpAddressController.text = state.settings.current.firstIPAddress;
}
if (_maxUserAllowedController.text !=
'${state.settings.current.maxUserAllowed}') {
_maxUserAllowedController.text =
'${state.settings.current.maxUserAllowed}';
}
if (_clientLeaseTimeController.text !=
'${state.settings.current.clientLeaseTime}') {
_clientLeaseTimeController.text =
'${state.settings.current.clientLeaseTime}';
}
if (_dns1Controller.text != (state.settings.current.dns1 ?? '')) {
_dns1Controller.text = state.settings.current.dns1 ?? '';
}
if (_dns2Controller.text != (state.settings.current.dns2 ?? '')) {
_dns2Controller.text = state.settings.current.dns2 ?? '';
}
if (_dns3Controller.text != (state.settings.current.dns3 ?? '')) {
_dns3Controller.text = state.settings.current.dns3 ?? '';
}
if (_winsController.text != (state.settings.current.wins ?? '')) {
_winsController.text = state.settings.current.wins ?? '';
}
}

@override
Expand Down Expand Up @@ -144,6 +162,9 @@ class _DHCPServerViewState extends ConsumerState<DHCPServerView> {
),
),
AppGap.sm(),
Padding(
padding: EdgeInsets.symmetric(vertical: AppSpacing.xs),
child: AppText.labelLarge(loc(context).maximumNumberOfUsers)),
Padding(
padding: inputPadding,
child: AppTextField(
Expand All @@ -163,6 +184,9 @@ class _DHCPServerViewState extends ConsumerState<DHCPServerView> {
AppGap.xs(),
_ipAddressRange(state),
AppGap.xl(),
Padding(
padding: EdgeInsets.symmetric(vertical: AppSpacing.xs),
child: AppText.labelLarge(loc(context).clientLeaseTime)),
Padding(
padding: inputPadding,
child: AppTextField(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ class _StaticRoutingViewState extends ConsumerState<StaticRoutingView>
if (shouldInitialize) {
_editingRule = rule;
Future.microtask(() {
_selectedInterface = RoutingSettingInterface.resolve(rule.interface);
_isInitializing = true;
try {
final state = ref.read(staticRoutingProvider);
Expand Down Expand Up @@ -307,13 +308,13 @@ class _StaticRoutingViewState extends ConsumerState<StaticRoutingView>
_destinationIPController.text = rule.destinationIP;
_subnetMaskController.text = rule.subnetMask;
_gatewayController.text = rule.gateway;
_selectedInterface = RoutingSettingInterface.resolve(rule.interface);

// Clear errors
_nameError = null;
_destIpError = null;
_subnetError = null;
_gatewayError = null;
_sheetStateSetter?.call(() {});
} finally {
_isInitializing = false;
}
Expand Down
4 changes: 3 additions & 1 deletion lib/page/components/shortcuts/dialogs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,9 @@ Future<T?> showSubmitAppDialog<T>(
content: SizedBox(
width: width ?? kDefaultDialogWidth,
child: switch (isLoading) {
true => loadingWidget ?? const AppLoader(),
true => loadingWidget ??
const Center(
child: SizedBox(width: 40, child: AppLoader())),
false => contentBuilder(context, setState, onSubmit),
}),
actions: isLoading
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:privacy_gui/core/cloud/providers/remote_assistance/remote_client
import 'package:privacy_gui/core/data/providers/polling_provider.dart';
import 'package:privacy_gui/localization/localization_hook.dart';
import 'package:privacy_gui/page/components/customs/timer_countdown_widget.dart';
import 'package:privacy_gui/page/dashboard/views/components/remote_assistance_animation.dart';
import 'package:privacy_gui/page/dashboard/views/components/_components.dart';
import 'package:ui_kit_library/ui_kit.dart';
import 'package:privacy_gui/core/cloud/model/guardians_remote_assistance.dart';
import 'package:url_launcher/url_launcher.dart';
Expand Down
2 changes: 2 additions & 0 deletions lib/page/dashboard/_dashboard.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export 'views/_views.dart';
export 'providers/_providers.dart';
export 'models/_models.dart';
export 'strategies/_strategies.dart';
Loading