mirror of
https://github.com/TrustTunnel/TrustTunnelFlutterClient.git
synced 2026-05-22 19:40:35 +00:00
Pull request 51: TRUST-136 routing profile validation fix
Squashed commit of the following: commit 453267837d6777a257d30375e761593d813bca8d Author: Konstantin Gorynin <k.goryinin@adguard.com> Date: Wed Dec 10 12:28:11 2025 +0200 typo fix commit d77bda94cc8fb9956a70caa2e14cc005c4405c05 Merge: 75eea68b945f3eAuthor: Konstantin Gorynin <k.goryinin@adguard.com> Date: Wed Dec 10 12:06:16 2025 +0200 Merge branch 'master' of ssh://bit.int.agrd.dev:7999/adguard-core-libs/vpn-oss-gui into bugfix/TRUST-136-routing-profile-validation-fix commit 75eea6878f724bf1fb0595b28810be34f7ac8f9d Merge: 2f2951a92dc049Author: Konstantin Gorynin <k.goryinin@adguard.com> Date: Tue Dec 9 16:08:14 2025 +0200 Merge branch 'master' of ssh://bit.int.agrd.dev:7999/adguard-core-libs/vpn-oss-gui into bugfix/TRUST-136-routing-profile-validation-fix commit 2f2951a1b094dcc0faf2c4d852aa731765c2d6c5 Author: Konstantin Gorynin <k.goryinin@adguard.com> Date: Tue Dec 9 13:39:59 2025 +0200 routing details validation fixed commit d15ba04f2efce5d59b6530a3eb2f5365ac7d0e1a Merge: 729a4ba33ec26bAuthor: Konstantin Gorynin <k.goryinin@adguard.com> Date: Tue Dec 9 13:10:57 2025 +0200 Merge branch 'master' of ssh://bit.int.agrd.dev:7999/adguard-core-libs/vpn-oss-gui into bugfix/TRUST-136-routing-profile-validation-fix commit 729a4ba90f8f99010bb604e30d57dd96977d3fec Author: Konstantin Gorynin <k.goryinin@adguard.com> Date: Tue Dec 9 09:36:24 2025 +0200 domain validation fixed in parsing method commit 5184cb3845672d443b50cf234749dd967115be5b Author: Konstantin Gorynin <k.goryinin@adguard.com> Date: Mon Dec 8 20:54:44 2025 +0200 fix missing code part commit e931458386ac7c193134577a6793cddc72e7d182 Author: Konstantin Gorynin <k.goryinin@adguard.com> Date: Mon Dec 8 20:49:55 2025 +0200 profile routes refactoring commit e6f0130fd591bbc80a8140b97a5bd6a017682fea Author: Konstantin Gorynin <k.goryinin@adguard.com> Date: Mon Dec 8 20:21:10 2025 +0200 wildcard update commit b1233b7419bdd12a9f559da513f9e48a05603a13 Author: Konstantin Gorynin <k.goryinin@adguard.com> Date: Mon Dec 8 19:45:13 2025 +0200 wildcard update commit be04e7b87f8ea7c5303a3281711a174a02187cb0 Author: Konstantin Gorynin <k.goryinin@adguard.com> Date: Mon Dec 8 19:33:41 2025 +0200 cidr validation fix commit 3dfadc9ea4e1a2ba53507d4d97f9e5281b86e871 Merge: ea6bd89 d20019a Author: Konstantin Gorynin <k.goryinin@adguard.com> Date: Mon Dec 8 18:27:06 2025 +0200 Merge branch 'bugfix/TRUST-130-address-field-fix' of ssh://bit.int.agrd.dev:7999/adguard-core-libs/vpn-oss-gui into bugfix/TRUST-136-routing-profile-validation-fix commit ea6bd89998c1888335912ed3248d39224f5f9610 Merge: 3c2d0c5fddefafAuthor: Konstantin Gorynin <k.goryinin@adguard.com> Date: Mon Dec 8 18:21:26 2025 +0200 Merge branch 'master' of ssh://bit.int.agrd.dev:7999/adguard-core-libs/vpn-oss-gui into bugfix/TRUST-136-routing-profile-validation-fix commit d20019a9329d2b1f65b4f0bbc75a52c90fd07b24 Author: Konstantin Gorynin <k.goryinin@adguard.com> Date: Mon Dec 8 18:20:52 2025 +0200 dns addresses fix commit 4fed2c4eed335502fa398111c4169f03980ffa93 Author: Konstantin Gorynin <k.goryinin@adguard.com> Date: Mon Dec 8 15:47:12 2025 +0200 fixed server modification commit 185cca84bfb59bb10b802e9a4e397390a4b0617b Merge: ff0ecfdfddefafAuthor: Konstantin Gorynin <k.goryinin@adguard.com> Date: Mon Dec 8 14:54:53 2025 +0200 Merge branch 'master' of ssh://bit.int.agrd.dev:7999/adguard-core-libs/vpn-oss-gui into bugfix/TRUST-130-address-field-fix commit ff0ecfd2cd5afa05bedaf9b134478fe79c034b80 Merge: b01fb28cde86b9Author: Atlassian Bamboo <bamboo@example.com> Date: Fri Dec 5 20:55:55 2025 +0300 [bamboo] Automated branch merge (from master:cde86b9302ae37f8cacb70f44440d67e0f32ab57) commit b01fb28bf856410f0f03e8d451642708220ef3a6 Author: Konstantin Gorynin <k.goryinin@adguard.com> Date: Fri Dec 5 19:50:27 2025 +0200 parsing fixed commit 3c2d0c5560bbe164cf7bd6129e7200c7e66ce763 Merge: 1821cebebdaed1Author: Atlassian Bamboo <bamboo@example.com> Date: Fri Dec 5 15:58:00 2025 +0300 [bamboo] Automated branch merge (from master:ebdaed157bcab6af1cb0a1a82805018ab18f9991) commit 1821cebe3a1cc9112c1fce4c82c6d7412f1b5da6 Author: Konstantin Gorynin <k.goryinin@adguard.com> Date: Fri Dec 5 14:46:06 2025 +0200 validation fixed ... and 3 more commits
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:vpn/common/extensions/model_extensions.dart';
|
||||
import 'package:vpn/common/utils/upstream_protocol_encoder.dart';
|
||||
import 'package:vpn/common/utils/validation_utils.dart';
|
||||
@@ -58,8 +57,6 @@ class VpnDataSourceImpl implements VpnDataSource {
|
||||
required RoutingProfile routingProfile,
|
||||
required List<String> excludedRoutes,
|
||||
}) {
|
||||
final routingProfile = server.routingProfile;
|
||||
|
||||
final exclusions = _getExclusionsByMode(routingProfile);
|
||||
|
||||
final endPoint = Endpoint(
|
||||
@@ -72,7 +69,6 @@ class VpnDataSourceImpl implements VpnDataSource {
|
||||
],
|
||||
exclusions: exclusions,
|
||||
dnsUpStreams: server.dnsServers,
|
||||
|
||||
upStreamProtocol: UpStreamProtocolEncoder().convert(
|
||||
server.vpnProtocol,
|
||||
),
|
||||
@@ -112,13 +108,29 @@ class VpnDataSourceImpl implements VpnDataSource {
|
||||
case RoutingMode.vpn:
|
||||
exclusions = profile.bypassRules;
|
||||
}
|
||||
|
||||
final parsedExclusions = exclusions.map(ValidationUtils.tryParseDomain).nonNulls.toList();
|
||||
final wildCard = '*.';
|
||||
|
||||
final Set<String> parsedDomains = {};
|
||||
final Set<String> parsedAddresses = {};
|
||||
|
||||
for (final exclusion in exclusions) {
|
||||
final domainValue = ValidationUtils.tryParseDomain(exclusion);
|
||||
|
||||
if (domainValue == null) {
|
||||
parsedAddresses.add(exclusion);
|
||||
continue;
|
||||
}
|
||||
|
||||
parsedDomains.add(domainValue);
|
||||
bool hasWildCard = domainValue.startsWith(wildCard);
|
||||
if (!hasWildCard) {
|
||||
parsedDomains.add('$wildCard$domainValue');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...parsedExclusions,
|
||||
...parsedExclusions.whereNot((e) => e.startsWith(wildCard)).map((e) => '$wildCard$e'),
|
||||
...parsedAddresses,
|
||||
...parsedDomains,
|
||||
}.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:vpn/common/extensions/context_extensions.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:vpn/feature/routing/routing/bloc/routing_bloc.dart';
|
||||
import 'package:vpn/feature/routing/routing/view/widget/routing_screen_view.dart';
|
||||
|
||||
@@ -14,7 +14,7 @@ class _RoutingScreenState extends State<RoutingScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
context.blocFactory.routingBloc().add(
|
||||
context.read<RoutingBloc>().add(
|
||||
const RoutingEvent.fetch(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:vpn/common/error/error_utils.dart';
|
||||
@@ -172,7 +173,7 @@ class RoutingDetailsBloc extends Bloc<RoutingDetailsEvent, RoutingDetailsState>
|
||||
_ChangeDefaultMode event,
|
||||
Emitter<RoutingDetailsState> emit,
|
||||
) async {
|
||||
final updatedData = state.data.copyWith(
|
||||
final updatedData = state.initialData.copyWith(
|
||||
defaultMode: event.defaultMode,
|
||||
);
|
||||
|
||||
@@ -184,7 +185,9 @@ class RoutingDetailsBloc extends Bloc<RoutingDetailsEvent, RoutingDetailsState>
|
||||
emit(
|
||||
state.copyWith(
|
||||
initialData: updatedData,
|
||||
data: updatedData,
|
||||
data: state.data.copyWith(
|
||||
defaultMode: event.defaultMode,
|
||||
),
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(action: const RoutingDetailsAction.defaultModeChanged()));
|
||||
|
||||
@@ -14,7 +14,15 @@ sealed class RoutingDetailsState with _$RoutingDetailsState {
|
||||
|
||||
const RoutingDetailsState._();
|
||||
|
||||
bool get hasChanges => data != initialData;
|
||||
bool get hasChanges {
|
||||
print('''
|
||||
Bypass equals ${listEquals(data.bypassRules, initialData.bypassRules)},
|
||||
Vpn equals ${listEquals(data.vpnRules, initialData.vpnRules)},
|
||||
Default mode equals ${data.defaultMode == initialData.defaultMode}
|
||||
''');
|
||||
|
||||
return !listEquals(data.bypassRules, initialData.bypassRules) || !listEquals(data.vpnRules, initialData.vpnRules);
|
||||
}
|
||||
|
||||
bool get isEditing => routingId != null;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:vpn/common/extensions/context_extensions.dart';
|
||||
import 'package:vpn/common/localization/extensions/locale_enum_extension.dart';
|
||||
@@ -8,9 +9,40 @@ import 'package:vpn/feature/routing/routing_details/bloc/routing_details_bloc.da
|
||||
import 'package:vpn/feature/routing/routing_details/domain/routing_spell_check_service.dart';
|
||||
import 'package:vpn/view/inputs/custom_text_field.dart';
|
||||
|
||||
class RoutingDetailsForm extends StatelessWidget {
|
||||
class RoutingDetailsForm extends StatefulWidget {
|
||||
const RoutingDetailsForm({super.key});
|
||||
|
||||
@override
|
||||
State<RoutingDetailsForm> createState() => _RoutingDetailsFormState();
|
||||
}
|
||||
|
||||
class _RoutingDetailsFormState extends State<RoutingDetailsForm> {
|
||||
bool _bypassValid = true;
|
||||
bool _vpnValid = true;
|
||||
|
||||
late final SpellCheckService _bypassSpellCheckService;
|
||||
late final SpellCheckService _vpnSpellCheckService;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_bypassSpellCheckService = RoutingSpellCheckService(
|
||||
onChecked: (spellValid) => _onDataChanged(
|
||||
context,
|
||||
RoutingMode.bypass,
|
||||
spellValid: spellValid,
|
||||
),
|
||||
);
|
||||
_vpnSpellCheckService = RoutingSpellCheckService(
|
||||
onChecked: (spellValid) => _onDataChanged(
|
||||
context,
|
||||
RoutingMode.vpn,
|
||||
spellValid: spellValid,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
@@ -47,17 +79,13 @@ class RoutingDetailsForm extends StatelessWidget {
|
||||
value: state.data.vpnRules.join('\n'),
|
||||
autofocus: true,
|
||||
hint: context.ln.enterRulesHint,
|
||||
spellCheckService: RoutingSpellCheckService(
|
||||
onChecked: (spellValid) => _onDataChanged(
|
||||
context,
|
||||
hasInvalidRules: !spellValid,
|
||||
),
|
||||
),
|
||||
spellCheckService: _vpnSpellCheckService,
|
||||
minLines: 40,
|
||||
maxLines: 40,
|
||||
showClearButton: false,
|
||||
onChanged: (vpnRules) => _onDataChanged(
|
||||
context,
|
||||
RoutingMode.vpn,
|
||||
vpnRules: vpnRules.split('\n').map((r) => r.trim()).where((r) => r.isNotEmpty).toList(),
|
||||
),
|
||||
);
|
||||
@@ -73,31 +101,36 @@ class RoutingDetailsForm extends StatelessWidget {
|
||||
hint: context.ln.enterRulesHint,
|
||||
minLines: 40,
|
||||
maxLines: 40,
|
||||
spellCheckService: RoutingSpellCheckService(
|
||||
onChecked: (spellValid) => _onDataChanged(
|
||||
context,
|
||||
hasInvalidRules: !spellValid,
|
||||
),
|
||||
),
|
||||
spellCheckService: _bypassSpellCheckService,
|
||||
showClearButton: false,
|
||||
onChanged: (bypassRules) => _onDataChanged(
|
||||
context,
|
||||
RoutingMode.bypass,
|
||||
bypassRules: bypassRules.split('\n').map((r) => r.trim()).where((r) => r.isNotEmpty).toList(),
|
||||
),
|
||||
);
|
||||
|
||||
void _onDataChanged(
|
||||
BuildContext context, {
|
||||
RoutingMode? mode,
|
||||
BuildContext context,
|
||||
RoutingMode mode, {
|
||||
List<String>? vpnRules,
|
||||
List<String>? bypassRules,
|
||||
bool? hasInvalidRules,
|
||||
}) => context.read<RoutingDetailsBloc>().add(
|
||||
RoutingDetailsEvent.dataChanged(
|
||||
defaultMode: mode,
|
||||
vpnRules: vpnRules,
|
||||
bypassRules: bypassRules,
|
||||
hasInvalidRules: hasInvalidRules,
|
||||
),
|
||||
);
|
||||
bool? spellValid,
|
||||
}) {
|
||||
if (mode == RoutingMode.bypass) {
|
||||
_bypassValid = spellValid ?? (_bypassValid || bypassRules?.isEmpty == true);
|
||||
}
|
||||
|
||||
if (mode == RoutingMode.vpn) {
|
||||
_vpnValid = spellValid ?? (_vpnValid || vpnRules?.isEmpty == true);
|
||||
}
|
||||
|
||||
context.read<RoutingDetailsBloc>().add(
|
||||
RoutingDetailsEvent.dataChanged(
|
||||
vpnRules: vpnRules,
|
||||
bypassRules: bypassRules,
|
||||
hasInvalidRules: !(_vpnValid && _bypassValid),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:vpn/common/extensions/context_extensions.dart';
|
||||
import 'package:vpn/common/localization/extensions/locale_enum_extension.dart';
|
||||
import 'package:vpn/common/localization/localization.dart';
|
||||
import 'package:vpn/data/model/routing_mode.dart';
|
||||
import 'package:vpn/data/model/routing_profile.dart';
|
||||
import 'package:vpn/data/model/vpn_state.dart';
|
||||
import 'package:vpn/feature/routing/routing/bloc/routing_bloc.dart';
|
||||
import 'package:vpn/feature/routing/routing/common/routing_profile_utils.dart';
|
||||
import 'package:vpn/feature/routing/routing_details/bloc/routing_details_bloc.dart';
|
||||
import 'package:vpn/feature/routing/routing_details/view/widget/routing_details_discard_changes_dialog.dart';
|
||||
import 'package:vpn/feature/routing/routing_details/view/widget/routing_details_form.dart';
|
||||
import 'package:vpn/feature/routing/routing_details/view/widget/routing_details_screen_app_bar_action.dart';
|
||||
import 'package:vpn/feature/routing/routing_details/view/widget/routing_details_submit_button_section.dart';
|
||||
import 'package:vpn/feature/server/servers/bloc/servers_bloc.dart';
|
||||
import 'package:vpn/feature/settings/excluded_routes/bloc/excluded_routes_bloc.dart';
|
||||
import 'package:vpn/feature/vpn/domain/entity/vpn_controller.dart';
|
||||
import 'package:vpn/feature/vpn/widgets/vpn_scope.dart';
|
||||
import 'package:vpn/view/custom_app_bar.dart';
|
||||
import 'package:vpn/view/scaffold_wrapper.dart';
|
||||
|
||||
@@ -23,22 +32,55 @@ class RoutingDetailsScreenView extends StatelessWidget {
|
||||
child: BlocConsumer<RoutingDetailsBloc, RoutingDetailsState>(
|
||||
listenWhen: (previous, current) => current.action != const RoutingDetailsAction.none(),
|
||||
listener: (innerContext, state) {
|
||||
final vpnController = VpnScope.vpnControllerOf(context);
|
||||
final serversBloc = context.read<ServersBloc>();
|
||||
final excludedRoutesBloc = context.read<ExcludedRoutesBloc>();
|
||||
final routingProfile = RoutingProfile(
|
||||
id: state.routingId ?? -1,
|
||||
name: state.routingName,
|
||||
defaultMode: state.data.defaultMode,
|
||||
bypassRules: state.data.bypassRules,
|
||||
vpnRules: state.data.vpnRules,
|
||||
);
|
||||
|
||||
switch (state.action) {
|
||||
case RoutingDetailsPresentationError(:final error):
|
||||
context.showInfoSnackBar(message: error.toLocalizedString(context));
|
||||
case RoutingDetailsSaved():
|
||||
onUpdated(
|
||||
vpnController,
|
||||
serversBloc,
|
||||
routingProfile,
|
||||
excludedRoutesBloc,
|
||||
);
|
||||
context.pop();
|
||||
context.showInfoSnackBar(message: context.ln.changesSavedSnackbar);
|
||||
case RoutingDetailsCreated(:final name):
|
||||
context.pop();
|
||||
context.showInfoSnackBar(message: context.ln.profileCreatedSnackbar(name));
|
||||
case RoutingDetailsDeleted(:final name):
|
||||
final defaultProfile = context.read<RoutingBloc>().state.routingList.firstWhere(
|
||||
(element) => element.id == RoutingProfileUtils.defaultRoutingProfileId,
|
||||
);
|
||||
onUpdated(
|
||||
vpnController,
|
||||
serversBloc,
|
||||
defaultProfile,
|
||||
excludedRoutesBloc,
|
||||
);
|
||||
context.pop();
|
||||
context.showInfoSnackBar(message: context.ln.profileDeletedSnackbar(name));
|
||||
|
||||
case RoutingDetailsCleared():
|
||||
innerContext.showInfoSnackBar(message: context.ln.allRulesDeleted);
|
||||
case RoutingDetailsDefaultModeChanged():
|
||||
innerContext.showInfoSnackBar(message: context.ln.changesSavedSnackbar);
|
||||
onUpdated(
|
||||
vpnController,
|
||||
serversBloc,
|
||||
routingProfile,
|
||||
excludedRoutesBloc,
|
||||
);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -84,6 +126,27 @@ class RoutingDetailsScreenView extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
|
||||
void onUpdated(
|
||||
VpnController controller,
|
||||
ServersBloc bloc,
|
||||
RoutingProfile profile,
|
||||
ExcludedRoutesBloc excludedRoutesBloc,
|
||||
) {
|
||||
final selectedServer = bloc.state.serverList.firstWhereOrNull((server) => server.id == bloc.state.selectedServerId);
|
||||
if (selectedServer != null) {
|
||||
bool picked = selectedServer.routingProfile.id == profile.id;
|
||||
bool running = controller.state != VpnState.disconnected;
|
||||
|
||||
if (picked && running) {
|
||||
controller.start(
|
||||
server: selectedServer,
|
||||
routingProfile: profile,
|
||||
excludedRoutes: excludedRoutesBloc.state.excludedRoutes,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _showNotSavedChangesWarning(BuildContext context) => showDialog(
|
||||
context: context,
|
||||
builder: (_) => RoutingDetailsDiscardChangesDialog(
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ class _Button extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) => BlocBuilder<RoutingDetailsBloc, RoutingDetailsState>(
|
||||
buildWhen: (previous, current) => previous.action == current.action,
|
||||
builder: (context, state) => FilledButton(
|
||||
builder: (context,state) => FilledButton(
|
||||
onPressed: state.hasChanges && !state.hasInvalidRules ? () => _addRouting(context) : null,
|
||||
child: Text(
|
||||
state.isEditing ? context.ln.save : context.ln.add,
|
||||
|
||||
Reference in New Issue
Block a user